Skip to content

Commit

Permalink
[GEOS-8571] Autocomplete support for dropdowns with long lists of values
Browse files Browse the repository at this point in the history
  • Loading branch information
aaime committed Feb 10, 2018
1 parent d111a94 commit fe0b5ea
Show file tree
Hide file tree
Showing 12 changed files with 268 additions and 17 deletions.
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.geoserver.catalog.FeatureTypeInfo; import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.LayerInfo; import org.geoserver.catalog.LayerInfo;
import org.geoserver.web.GeoServerApplication; import org.geoserver.web.GeoServerApplication;
import org.geoserver.web.wicket.Select2DropDownChoice;
import org.geoserver.wps.web.InputParameterValues.ParameterType; import org.geoserver.wps.web.InputParameterValues.ParameterType;
import org.geotools.geometry.jts.ReferencedEnvelope; import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.util.logging.Logging; import org.geotools.util.logging.Logging;
Expand Down Expand Up @@ -121,7 +122,7 @@ void updateEditor() {


new PropertyModel(getDefaultModel(), "mime").setObject("text/xml"); new PropertyModel(getDefaultModel(), "mime").setObject("text/xml");
Fragment f = new Fragment("editor", "vectorLayer", this); Fragment f = new Fragment("editor", "vectorLayer", this);
DropDownChoice layer = new DropDownChoice("layer", new PropertyModel(valueModel, DropDownChoice layer = new Select2DropDownChoice("layer", new PropertyModel(valueModel,
"layerName"), getVectorLayerNames()); "layerName"), getVectorLayerNames());
f.add(layer); f.add(layer);
add(f); add(f);
Expand All @@ -132,7 +133,7 @@ void updateEditor() {
} }


Fragment f = new Fragment("editor", "rasterLayer", this); Fragment f = new Fragment("editor", "rasterLayer", this);
final DropDownChoice layer = new DropDownChoice("layer", new PropertyModel(valueModel, final DropDownChoice layer = new Select2DropDownChoice("layer", new PropertyModel(valueModel,
"layerName"), getRasterLayerNames()); "layerName"), getRasterLayerNames());
f.add(layer); f.add(layer);
add(f); add(f);
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.geoserver.web.wicket.CRSPanel; import org.geoserver.web.wicket.CRSPanel;
import org.geoserver.web.wicket.EnvelopePanel; import org.geoserver.web.wicket.EnvelopePanel;
import org.geoserver.web.wicket.GeoServerAjaxFormLink; import org.geoserver.web.wicket.GeoServerAjaxFormLink;
import org.geoserver.web.wicket.Select2DropDownChoice;
import org.geoserver.wps.process.GeoServerProcessors; import org.geoserver.wps.process.GeoServerProcessors;
import org.geotools.data.Parameter; import org.geotools.data.Parameter;
import org.geotools.process.ProcessFactory; import org.geotools.process.ProcessFactory;
Expand Down Expand Up @@ -105,7 +106,7 @@ public WPSRequestBuilderPanel(String id, ExecuteRequest executeRequest) {
setOutputMarkupId(true); setOutputMarkupId(true);
this.execute = executeRequest; this.execute = executeRequest;


final DropDownChoice<String> processChoice = new DropDownChoice<String>("process", new PropertyModel<String>( final DropDownChoice<String> processChoice = new Select2DropDownChoice<String>("process", new PropertyModel<String>(
execute, "processName"), buildProcessList()); execute, "processName"), buildProcessList());
add(processChoice); add(processChoice);


Expand Down
5 changes: 5 additions & 0 deletions src/pom.xml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -909,6 +909,11 @@
<artifactId>wicket-extensions</artifactId> <artifactId>wicket-extensions</artifactId>
<version>${wicket.version}</version> <version>${wicket.version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.wicketstuff</groupId>
<artifactId>wicketstuff-select2</artifactId>
<version>${wicket.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
Expand Down
4 changes: 4 additions & 0 deletions src/web/core/pom.xml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@
<groupId>org.apache.wicket</groupId> <groupId>org.apache.wicket</groupId>
<artifactId>wicket-extensions</artifactId> <artifactId>wicket-extensions</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.wicketstuff</groupId>
<artifactId>wicketstuff-select2</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId> <artifactId>slf4j-log4j12</artifactId>
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import org.geoserver.web.data.settings.SettingsPluginPanelInfo; import org.geoserver.web.data.settings.SettingsPluginPanelInfo;
import org.geoserver.web.wicket.LocalizedChoiceRenderer; import org.geoserver.web.wicket.LocalizedChoiceRenderer;
import org.geoserver.web.wicket.ParamResourceModel; import org.geoserver.web.wicket.ParamResourceModel;
import org.geoserver.web.wicket.Select2DropDownChoice;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;


public class GlobalSettingsPage extends ServerAdminPage { public class GlobalSettingsPage extends ServerAdminPage {
Expand All @@ -67,8 +68,8 @@ public GlobalSettingsPage() {
form.add(new CheckBox("verboseExceptions")); form.add(new CheckBox("verboseExceptions"));
form.add(new CheckBox("globalServices")); form.add(new CheckBox("globalServices"));
form.add(new TextField<Integer>("numDecimals").add(RangeValidator.minimum(0))); form.add(new TextField<Integer>("numDecimals").add(RangeValidator.minimum(0)));
form.add(new DropDownChoice<String>("charset", AVAILABLE_CHARSETS)); form.add(new Select2DropDownChoice<>("charset", AVAILABLE_CHARSETS));
form.add(new DropDownChoice<ResourceErrorHandling>("resourceErrorHandling", Arrays.asList(ResourceErrorHandling.values()), form.add(new Select2DropDownChoice<>("resourceErrorHandling", Arrays.asList(ResourceErrorHandling.values()),
new ResourceErrorHandlingRenderer())); new ResourceErrorHandlingRenderer()));
form.add(new TextField<String>("proxyBaseUrl").add(new UrlValidator())); form.add(new TextField<String>("proxyBaseUrl").add(new UrlValidator()));


Expand All @@ -91,15 +92,15 @@ public GlobalSettingsPage() {
providers.remove("lockProvider"); // remove the global lock provider providers.remove("lockProvider"); // remove the global lock provider
Collections.sort(providers);; Collections.sort(providers);;


DropDownChoice<String> lockProviderChoice = new DropDownChoice<String>("lockProvider", lockProviderModel, providers, new LocalizedChoiceRenderer(this)); DropDownChoice<String> lockProviderChoice = new Select2DropDownChoice<>("lockProvider", lockProviderModel, providers, new LocalizedChoiceRenderer(this));


form.add( lockProviderChoice ); form.add( lockProviderChoice );


IModel<GeoServerInfo.WebUIMode> webUIModeModel = new PropertyModel<GeoServerInfo.WebUIMode>(globalInfoModel, "webUIMode"); IModel<GeoServerInfo.WebUIMode> webUIModeModel = new PropertyModel<GeoServerInfo.WebUIMode>(globalInfoModel, "webUIMode");
if (webUIModeModel.getObject() == null) { if (webUIModeModel.getObject() == null) {
webUIModeModel.setObject(GeoServerInfo.WebUIMode.DEFAULT); webUIModeModel.setObject(GeoServerInfo.WebUIMode.DEFAULT);
} }
DropDownChoice<GeoServerInfo.WebUIMode> webUIModeChoice = new DropDownChoice<GeoServerInfo.WebUIMode>("webUIMode", DropDownChoice<GeoServerInfo.WebUIMode> webUIModeChoice = new Select2DropDownChoice<>("webUIMode",
webUIModeModel, Arrays.asList(GeoServerInfo.WebUIMode.values())); webUIModeModel, Arrays.asList(GeoServerInfo.WebUIMode.values()));


form.add(webUIModeChoice); form.add(webUIModeChoice);
Expand Down
39 changes: 39 additions & 0 deletions src/web/core/src/main/java/org/geoserver/web/css/geoserver.css
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1251,3 +1251,42 @@ div.wicket-aa ul {
div.wicket-aa ul li.selected { div.wicket-aa ul li.selected {
background-color:#CCCCCC; background-color:#CCCCCC;
} }

/*----------------------------
Customize Select2 appearance
------------------------------*/
.select2-container .select2-selection--single {
height: 19px;
margin-top: -2px;
}
.select2-container--default .select2-selection--single {
border: 1px solid #aaa;
border-radius: 0px;
}
.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
border-color: #000 transparent;
}
.select2-container--default .select2-selection--single .select2-selection__arrow {
top: -2px;
}
.select2-container--default .select2-selection--single .select2-selection__arrow b {
border-color: #000 transparent transparent transparent;
}
.select2-results__option {
color: black;
padding: 1px;
}
.select2-container--default .select2-results > .select2-results__options {
max-height: 300px;
}
.select2-container .select2-selection--single .select2-selection__rendered {
padding-left: 4px;
}
.select2-container--default .select2-selection--single .select2-selection__rendered {
color: black;
line-height: 19px;
}
.select2-container--default .select2-selection--single .select2-selection__arrow {
height: 18px;
}

Original file line number Original file line Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@


import java.io.IOException; import java.io.IOException;


import org.geoserver.web.wicket.Select2DropDownChoice;
import org.geotools.decorate.Wrapper; import org.geotools.decorate.Wrapper;
import java.util.logging.Level; import java.util.logging.Level;


Expand All @@ -16,7 +17,6 @@
import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.image.Image; import org.apache.wicket.markup.html.image.Image;
import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.markup.html.panel.Fragment;
Expand Down Expand Up @@ -250,8 +250,8 @@ public void onClick(AjaxRequestTarget target) {
} }
}; };
} }
private DropDownChoice<StoreInfo> storesDropDown() { private Select2DropDownChoice<StoreInfo> storesDropDown() {
final DropDownChoice<StoreInfo> stores = new DropDownChoice<>("storesDropDown", new Model<StoreInfo>(), final Select2DropDownChoice<StoreInfo> stores = new Select2DropDownChoice<>("storesDropDown", new Model<>(),
new StoreListModel(), new StoreListChoiceRenderer()); new StoreListModel(), new StoreListChoiceRenderer());
stores.setOutputMarkupId(true); stores.setOutputMarkupId(true);
stores.add(new AjaxFormComponentUpdatingBehavior("change") { stores.add(new AjaxFormComponentUpdatingBehavior("change") {
Expand Down
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,92 @@
/* (c) 2018 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.web.wicket;

import org.apache.wicket.Component;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.OnLoadHeaderItem;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.IChoiceRenderer;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.wicketstuff.select2.Select2Behavior;

import java.util.List;

/**
* Auto-complete version of a {@link org.apache.wicket.markup.html.form.DropDownChoice}
*/
public class Select2DropDownChoice<T> extends DropDownChoice<T> {

private static final PackageResourceReference SELECT2_KEYBOARD_JS = new PackageResourceReference(
Select2DropDownChoice.class, "js/select2/select2-keyboard.js");


public Select2DropDownChoice(String id, IModel<T> model, IModel<List<T>> choices, IChoiceRenderer<T> renderer) {
super(id, model, choices, renderer);
initBehaviors();
}

public Select2DropDownChoice(String id, IModel<T> model, List<T> choices, IChoiceRenderer<T> renderer) {
super(id, model, choices, renderer);
initBehaviors();
}

public Select2DropDownChoice(String id, List<? extends T> choices) {
super(id, choices);
initBehaviors();
}

public Select2DropDownChoice(String id, List<? extends T> choices, IChoiceRenderer<? super T> renderer) {
super(id, choices, renderer);
initBehaviors();
}

public Select2DropDownChoice(String id, IModel<T> model, List<? extends T> choices) {
super(id, model, choices);
initBehaviors();
}

public Select2DropDownChoice(String id, IModel<? extends List<? extends T>> choices) {
super(id, choices);
initBehaviors();
}

public Select2DropDownChoice(String id, IModel<T> model, IModel<? extends List<? extends T>> choices) {
super(id, model, choices);
initBehaviors();
}

public Select2DropDownChoice(String id, IModel<? extends List<? extends T>> choices, IChoiceRenderer<? super T>
renderer) {
super(id, choices, renderer);
initBehaviors();
}

private void initBehaviors() {
add(new Select2Behavior());
add(new KeyboardBehavior());
}


/**
* Mimics keyboard behavior of native drop down choices
*/
private static class KeyboardBehavior extends Behavior {

public KeyboardBehavior() {
}

public void renderHead(Component component, IHeaderResponse response) {
super.renderHead(component, response);
response.render(JavaScriptHeaderItem.forReference(SELECT2_KEYBOARD_JS));
String enabler = "enableSelect2Keyboard('" + component.getMarkupId() + "');";
response.render(OnLoadHeaderItem.forScript(enabler));
}
}

}
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,105 @@
/* (c) 2018 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/

function isCharacterKeyPress(evt) {
if (typeof evt.which == "undefined") {
// This is IE, which only fires keypress events for printable keys
return true;
} else if (typeof evt.which == "number" && evt.which > 0) {
// In other browsers except old versions of WebKit, evt.which is
// only greater than zero if the keypress is a printable key.
// We need to filter out non printable chars and ctrl/alt/meta key combinations
return !evt.ctrlKey && !evt.metaKey && !evt.altKey && evt.which > 32;
}
return false;
}

function enableSelect2Keyboard(dropDownId) {
var dropDown = $('#' + dropDownId);
var s2Obj = dropDown.data('select2');
delete s2Obj.listeners.keypress;
s2Obj.on('keypress', function (evt) {
var key = evt.which;
var KEYS = {
BACKSPACE: 8,
TAB: 9,
ENTER: 13,
SHIFT: 16,
CTRL: 17,
ALT: 18,
ESC: 27,
SPACE: 32,
PAGE_UP: 33,
PAGE_DOWN: 34,
END: 35,
HOME: 36,
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
DELETE: 46
};
if (this.isOpen()) {
if (key === KEYS.ENTER) {
this.trigger('results:select');
evt.preventDefault();
} else if ((key === KEYS.SPACE && evt.ctrlKey)) {
this.trigger('results:toggle');

evt.preventDefault();
} else if (key === KEYS.UP) {
this.trigger('results:previous');

evt.preventDefault();
} else if (key === KEYS.DOWN) {
this.trigger('results:next');

evt.preventDefault();
} else if (key === KEYS.ESC || key === KEYS.TAB) {
this.close();

evt.preventDefault();
}
} else {
if (key === KEYS.ENTER || key === KEYS.SPACE ||
((key === KEYS.DOWN || key === KEYS.UP) && evt.altKey)) {
this.open();

evt.preventDefault();
} else if (key === KEYS.DOWN) {
var val = s2Obj.$element.find('option:selected').next().val();
if (undefined !== val) {
s2Obj.$element.val(val);
s2Obj.$element.trigger('change');
}
evt.preventDefault();
} else if (key === KEYS.UP) {
if (undefined != this.$element.find('option:selected').prev().val()) {
this.$element.val(this.$element.find('option:selected').prev().val());
this.$element.trigger('change');
}
evt.preventDefault();
} else if (isCharacterKeyPress(evt)) {
// mimics typing in directly by matching the first letter typed (a browser collects
// all typed chars and allows going beyond first letter)
var option = this.$element.find('option').first();
var theChar = String.fromCharCode(key).toLowerCase()
while (undefined != option.val() && option.text().toLowerCase().indexOf(theChar) !== 0) {
option = option.next()
}
if (undefined != option.val()) {
this.$element.val(option.val());
this.$element.trigger('change');
}
evt.preventDefault();
}
}
}
);

s2Obj.on('close', function () {
this.$element.trigger('focus');
});
}
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import org.geoserver.web.GeoServerApplication; import org.geoserver.web.GeoServerApplication;
import org.geoserver.web.GeoServerBasePage; import org.geoserver.web.GeoServerBasePage;
import org.geoserver.web.wicket.CodeMirrorEditor; import org.geoserver.web.wicket.CodeMirrorEditor;
import org.geoserver.web.wicket.Select2DropDownChoice;
import org.geotools.util.logging.Logging; import org.geotools.util.logging.Logging;
import org.vfny.geoserver.global.ConfigurationException; import org.vfny.geoserver.global.ConfigurationException;
import org.geoserver.config.GeoServer; import org.geoserver.config.GeoServer;
Expand Down Expand Up @@ -134,7 +135,7 @@ private void setUpDemoRequestsForm(final Resource demoDir) {
final List<String> demoList = getDemoList(demoDir); final List<String> demoList = getDemoList(demoDir);
final DropDownChoice demoRequestsList; final DropDownChoice demoRequestsList;
final IModel reqFileNameModel = new PropertyModel(requestModel, "requestFileName"); final IModel reqFileNameModel = new PropertyModel(requestModel, "requestFileName");
demoRequestsList = new DropDownChoice("demoRequestsList", reqFileNameModel, demoList, demoRequestsList = new Select2DropDownChoice("demoRequestsList", reqFileNameModel, demoList,
new ChoiceRenderer() { new ChoiceRenderer() {
public String getIdValue(Object obj, int index) { public String getIdValue(Object obj, int index) {
return String.valueOf(obj); return String.valueOf(obj);
Expand Down

0 comments on commit fe0b5ea

Please sign in to comment.