diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-core/src/main/java/org/wicketstuff/jquery/core/ajax/JQueryAjaxBehavior.java b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-core/src/main/java/org/wicketstuff/jquery/core/ajax/JQueryAjaxBehavior.java index 5feac31056..28e1bfb567 100644 --- a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-core/src/main/java/org/wicketstuff/jquery/core/ajax/JQueryAjaxBehavior.java +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-core/src/main/java/org/wicketstuff/jquery/core/ajax/JQueryAjaxBehavior.java @@ -157,7 +157,7 @@ public JQueryAjaxBehavior(IJQueryAjaxAware source) * Constructor * * @param source {@link Behavior} to which the event - returned by {@link #newEvent()} - will be broadcasted. - * @param duration {@link Duration}. If different than {@link Duration#NONE}, an {@link ThrottlingSettings} will be added with the specified {@link Duration}. + * @param duration {@link Duration}. If different than {@link Duration#ZERO}, an {@link ThrottlingSettings} will be added with the specified {@link Duration}. */ public JQueryAjaxBehavior(IJQueryAjaxAware source, Duration duration) { diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-core/src/main/java/org/wicketstuff/jquery/core/utils/ListUtils.java b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-core/src/main/java/org/wicketstuff/jquery/core/utils/ListUtils.java index 95d2a728f2..aa58d7a869 100644 --- a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-core/src/main/java/org/wicketstuff/jquery/core/utils/ListUtils.java +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-core/src/main/java/org/wicketstuff/jquery/core/utils/ListUtils.java @@ -313,7 +313,7 @@ public static boolean isEmpty(List list) /** * Converts a {@link StringValue} to a list of {@code String} * - * @param value the {@link StringValue} + * @param values the {@link StringValue} * @return a list of {@code String} */ public static List toStringList(StringValue values) diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/SiteMapPage.java b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/SiteMapPage.java index dbbd3e28bf..112691777f 100644 --- a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/SiteMapPage.java +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/SiteMapPage.java @@ -20,6 +20,7 @@ import org.wicketstuff.jquery.ui.samples.jqueryui.autocomplete.ConverterAutoCompletePage; import org.wicketstuff.jquery.ui.samples.jqueryui.autocomplete.CustomAutoCompletePage; import org.wicketstuff.jquery.ui.samples.jqueryui.autocomplete.DefaultAutoCompletePage; +import org.wicketstuff.jquery.ui.samples.jqueryui.autocomplete.IdBasedAutoCompletePage; import org.wicketstuff.jquery.ui.samples.jqueryui.autocomplete.RendererAutoCompletePage; import org.wicketstuff.jquery.ui.samples.jqueryui.autocomplete.TemplateAutoCompletePage; import org.wicketstuff.jquery.ui.samples.jqueryui.button.AjaxButtonPage; @@ -191,6 +192,7 @@ public class SiteMapPage extends WebPage ContextMenuPage.class, ConverterAutoCompletePage.class, CustomAutoCompletePage.class, + IdBasedAutoCompletePage.class, CustomCalendarPage.class, CustomCalendar6Page.class, CustomComboBoxPage.class, diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/jqueryui/autocomplete/AbstractAutoCompletePage.java b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/jqueryui/autocomplete/AbstractAutoCompletePage.java index 38ee8c58d3..304d421086 100644 --- a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/jqueryui/autocomplete/AbstractAutoCompletePage.java +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/jqueryui/autocomplete/AbstractAutoCompletePage.java @@ -20,7 +20,8 @@ protected List getDemoLinks() new DemoLink(CustomAutoCompletePage.class, "Auto-complete: custom beans"), // lf new DemoLink(TemplateAutoCompletePage.class, "Auto-complete: custom template"), // lf new DemoLink(RendererAutoCompletePage.class, "Auto-complete: custom renderer"), // lf - new DemoLink(ConverterAutoCompletePage.class, "Auto-complete: form submit") // lf + new DemoLink(ConverterAutoCompletePage.class, "Auto-complete: form submit"), // lf + new DemoLink(IdBasedAutoCompletePage.class, "Auto-complete: id based selection") // lf ); } } diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/jqueryui/autocomplete/IdBasedAutoCompletePage.css b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/jqueryui/autocomplete/IdBasedAutoCompletePage.css new file mode 100644 index 0000000000..e7323a51d7 --- /dev/null +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/jqueryui/autocomplete/IdBasedAutoCompletePage.css @@ -0,0 +1,17 @@ +/* Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 */ +table { + width: 400px; + border-spacing: 0; + border-collapse: collapse; +} + +th, td { + padding: 5px; +} + +.ui-autocomplete { + max-height: 200px; + overflow-y: auto; + overflow-x: hidden; + padding-right: 20px; +} diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/jqueryui/autocomplete/IdBasedAutoCompletePage.html b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/jqueryui/autocomplete/IdBasedAutoCompletePage.html new file mode 100644 index 0000000000..fb0542c08f --- /dev/null +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/jqueryui/autocomplete/IdBasedAutoCompletePage.html @@ -0,0 +1,32 @@ + + + + + + Wicket jQuery UI: auto-complete (custom bean) + + + + +
+
+ + + + + +
+
Select a metal genre: (containing any char)
+
+ +
+ +
+
+
+
+
+ + diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/jqueryui/autocomplete/IdBasedAutoCompletePage.java b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/jqueryui/autocomplete/IdBasedAutoCompletePage.java new file mode 100644 index 0000000000..bad3f8f8b9 --- /dev/null +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/java/org/wicketstuff/jquery/ui/samples/jqueryui/autocomplete/IdBasedAutoCompletePage.java @@ -0,0 +1,88 @@ +/* +Licensed under the Apache License, Version 2.0 (the "License") http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.wicketstuff.jquery.ui.samples.jqueryui.autocomplete; + +import java.util.List; + +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.html.WebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.Form; +import org.apache.wicket.markup.html.image.ContextImage; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.LoadableDetachableModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.PropertyModel; +import org.wicketstuff.jquery.core.resource.StyleSheetPackageHeaderItem; +import org.wicketstuff.jquery.core.utils.ListUtils; +import org.wicketstuff.jquery.ui.form.autocomplete.AbstractAutoCompleteTextField; +import org.wicketstuff.jquery.ui.form.autocomplete.AutoCompleteTextField; +import org.wicketstuff.jquery.ui.form.autocomplete.IElementSelectionStrategy; +import org.wicketstuff.jquery.ui.form.autocomplete.PropertyBasedElementSelectionStrategy; +import org.wicketstuff.jquery.ui.samples.data.bean.Genre; +import org.wicketstuff.jquery.ui.samples.data.dao.GenresDAO; + +public class IdBasedAutoCompletePage extends AbstractAutoCompletePage +{ + private static final long serialVersionUID = 1L; + + public IdBasedAutoCompletePage() + { + // Model // + final IModel model = Model.of(GenresDAO.newGenre()); + + // Form // + final Form form = new Form("form"); + this.add(form); + + // Container for selected genre (name & cover) // + final WebMarkupContainer container = new WebMarkupContainer("container"); + form.add(container.setOutputMarkupId(true)); + + container.add(new ContextImage("cover", new PropertyModel(model, "cover"))); + container.add(new Label("name", new PropertyModel(model, "name"))); + + // Auto-complete // + form.add(new AbstractAutoCompleteTextField("autocomplete", model) { + + private static final long serialVersionUID = 1L; + + @Override + protected IModel> getChoicesModel(String input) + { + return new LoadableDetachableModel<>() + { + @Override + protected List load() + { + return ListUtils.contains(input, GenresDAO.all()); + } + }; + } + + @Override + protected IElementSelectionStrategy newElementSelectionStrategy() + { + return new PropertyBasedElementSelectionStrategy<>("id"); + } + + @Override + protected void onSelected(AjaxRequestTarget target) + { + target.add(container); // the model has already been updated + } + }); + } + + // Methods // + + @Override + public void renderHead(IHeaderResponse response) + { + super.renderHead(response); + + response.render(new StyleSheetPackageHeaderItem(IdBasedAutoCompletePage.class)); + } +} diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/resources/org/wicketstuff/jquery/ui/samples/SampleApplication.properties b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/resources/org/wicketstuff/jquery/ui/samples/SampleApplication.properties index cdc22247c4..14fdae69cf 100644 --- a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/resources/org/wicketstuff/jquery/ui/samples/SampleApplication.properties +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui-samples/src/main/resources/org/wicketstuff/jquery/ui/samples/SampleApplication.properties @@ -6,6 +6,7 @@ AccordionPanelPage.title=AccordionPanel # Auto-complete DefaultAutoCompletePage.title=Auto-complete CustomAutoCompletePage.title=Auto-complete: custom beans +IdBasedAutoCompletePage.title=Auto-complete: ID based selection TemplateAutoCompletePage.title=Auto-complete: custom template RendererAutoCompletePage.title=Auto-complete: custom renderer ConverterAutoCompletePage.title=Auto-complete: form submit diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/AbstractAutoCompleteTextField.java b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/AbstractAutoCompleteTextField.java index 6b66866bae..d07a9bfe3f 100644 --- a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/AbstractAutoCompleteTextField.java +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/AbstractAutoCompleteTextField.java @@ -16,6 +16,7 @@ */ package org.wicketstuff.jquery.ui.form.autocomplete; +import java.util.Collections; import java.util.List; import java.util.Locale; import org.apache.wicket.ajax.AjaxRequestTarget; @@ -23,7 +24,7 @@ import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.model.IModel; import org.apache.wicket.util.convert.IConverter; -import org.apache.wicket.util.lang.Args; +import org.danekja.java.util.function.serializable.SerializableSupplier; import org.wicketstuff.jquery.core.IJQueryWidget; import org.wicketstuff.jquery.core.JQueryBehavior; import org.wicketstuff.jquery.core.renderer.ITextRenderer; @@ -40,7 +41,7 @@ * @author Sebastien Briquet - sebfz1 * @author reiern70 */ -public abstract class AbstractAutoCompleteTextField extends TextField implements IJQueryWidget, IAutoCompleteListener // NOSONAR +public abstract class AbstractAutoCompleteTextField extends TextField implements IJQueryWidget, IAutoCompleteListener // NOSONAR { private static final long serialVersionUID = 1L; @@ -51,6 +52,7 @@ public abstract class AbstractAutoCompleteTextField extends TextField impl private final ITextRenderer renderer; private final IConverter converter; + private final IElementSelectionStrategy elementSelectionStrategy; private final IJQueryTemplate template; private JQueryAbstractTemplateBehavior templateBehavior = null; @@ -108,6 +110,7 @@ public AbstractAutoCompleteTextField(String id, ITextRenderer rendere this.renderer = renderer; this.template = this.newTemplate(); this.converter = this.newConverter(); + this.elementSelectionStrategy = this.newElementSelectionStrategy(); } /** @@ -145,7 +148,7 @@ public AbstractAutoCompleteTextField(String id, IModel model, ITextRenderer model, ITextRenderer getRenderer() return this.renderer; } + /** + * Gets the {@link IElementSelectionStrategy} used to identify and resolve selected choices. + * + * @return the selection strategy, never {@code null} + */ + public IElementSelectionStrategy getElementSelectionStrategy() + { + return this.elementSelectionStrategy; + } + // Events // @Override @@ -292,17 +306,30 @@ protected void onComponentTag(final ComponentTag tag) tag.put("autocomplete", "off"); // disable browser's autocomplete } - @Override - public final void onSelect(AjaxRequestTarget target, int index) - { - if (-1 < index && index < this.choices.getObject().size()) - { - T choice = this.choices.getObject().get(index); - - this.setModelObject(choice); - this.onSelected(target); - } - } + @Override + public final void onSelect(AjaxRequestTarget target, T choice, String identifier) + { + this.setModelObject(choice); + if (choice == null) + { + LOG.debug("Cannot select choice with ID: {}", identifier); + onSelectionFailed(target, identifier); + } + else + { + this.onSelected(target); + } + } + + /** + * Triggered when it was not possible to find an element to select + * + * @param target the {@link AjaxRequestTarget} + * @param identifier THe id of the Choice that could not be retrieved. + */ + protected void onSelectionFailed(AjaxRequestTarget target, String identifier) + { + } /** * Triggered when the user selects an item from results that matched its input @@ -318,7 +345,13 @@ protected void onSelected(AjaxRequestTarget target) @Override public JQueryBehavior newWidgetBehavior(String selector) { - return new AutoCompleteBehavior(selector, this) { // NOSONAR + return new AutoCompleteBehavior(selector, this, new SerializableSupplier>() { + + @Override + public List get() { + return choices != null ? choices.getObject() : Collections.emptyList(); + } + }) { // NOSONAR private static final long serialVersionUID = 1L; @@ -394,6 +427,16 @@ public String convertToString(T value, Locale locale) }; } + /** + * Gets a new {@link IElementSelectionStrategy}. Index-based selection is used by default. + * + * @return the selection strategy + */ + protected IElementSelectionStrategy newElementSelectionStrategy() + { + return IndexBasedElementSelectionStrategy.get(); + } + /** * Gets a new {@link AutoCompleteChoiceModelBehavior} * @@ -401,11 +444,17 @@ public String convertToString(T value, Locale locale) */ private AutoCompleteChoiceModelBehavior newChoiceModelBehavior() { - return new AutoCompleteChoiceModelBehavior(this.renderer, this.template) { // NOSONAR - + return new AutoCompleteChoiceModelBehavior(this.renderer, this.template) // NOSONAR + { private static final long serialVersionUID = 1L; private static final String TERM = "term"; + @Override + protected IElementSelectionStrategy getElementSelectionStrategy() + { + return AbstractAutoCompleteTextField.this.getElementSelectionStrategy(); + } + @Override public List getChoices() { @@ -415,4 +464,34 @@ public List getChoices() } }; } + + /** + * Gets the {@link AutoCompleteChoiceModelBehavior} that serves the choices to the widget + * + * @return the {@link AutoCompleteChoiceModelBehavior}, or {@code null} if the component has not been initialized yet + */ + public final AutoCompleteChoiceModelBehavior getChoiceModelBehavior() + { + return this.choiceModelBehavior; + } + + /** + * Gets the cached choices of the last query, used to resolve the user selected object + * + * @return the {@link IModel} of choices, or {@code null} if no query has been performed yet + */ + public final IModel> getChoices() + { + return this.choices; + } + + /** + * Gets the {@link JQueryAbstractTemplateBehavior} supplied by {@link #newTemplate()} + * + * @return the {@link JQueryAbstractTemplateBehavior}, or {@code null} if there is no template + */ + public final JQueryAbstractTemplateBehavior getTemplateBehavior() + { + return this.templateBehavior; + } } diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/AutoCompleteBehavior.java b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/AutoCompleteBehavior.java index d2654d90b3..94570865f6 100644 --- a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/AutoCompleteBehavior.java +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/AutoCompleteBehavior.java @@ -16,10 +16,14 @@ */ package org.wicketstuff.jquery.ui.form.autocomplete; +import java.util.List; + import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.attributes.CallbackParameter; +import org.apache.wicket.model.IModel; import org.apache.wicket.util.lang.Args; +import org.danekja.java.util.function.serializable.SerializableSupplier; import org.wicketstuff.jquery.core.JQueryEvent; import org.wicketstuff.jquery.core.Options; import org.wicketstuff.jquery.core.ajax.IJQueryAjaxAware; @@ -32,40 +36,45 @@ * * @author Sebastien Briquet - sebfz1 */ -public abstract class AutoCompleteBehavior extends JQueryUIBehavior implements IJQueryAjaxAware +public abstract class AutoCompleteBehavior extends JQueryUIBehavior implements IJQueryAjaxAware { private static final long serialVersionUID = 1L; public static final String METHOD = "autocomplete"; /** event listener */ - private final IAutoCompleteListener listener; + private final IAutoCompleteListener listener; + + /** the model producing values */ + + private final SerializableSupplier> supplier; private JQueryAjaxBehavior onSelectAjaxBehavior = null; /** * Constructor * - * @param selector the html selector (ie: "#myId") + * @param selector the HTML selector (ie: "#myId") * @param listener the {@link IAutoCompleteListener} */ - public AutoCompleteBehavior(String selector, IAutoCompleteListener listener) + public AutoCompleteBehavior(String selector, IAutoCompleteListener listener, SerializableSupplier> supplier) { - this(selector, new Options(), listener); + this(selector, new Options(), listener, supplier); } /** * Constructor * - * @param selector the html selector (ie: "#myId") + * @param selector the HTML selector (ie: "#myId") * @param options the {@link Options} * @param listener the {@link IAutoCompleteListener} */ - public AutoCompleteBehavior(String selector, Options options, IAutoCompleteListener listener) + public AutoCompleteBehavior(String selector, Options options, IAutoCompleteListener listener, SerializableSupplier> supplier) { super(selector, METHOD, options); this.listener = Args.notNull(listener, "listener"); - } + this.supplier = supplier; + } // Methods // @@ -108,16 +117,16 @@ public void onConfigure(Component component) @Override public void onAjax(AjaxRequestTarget target, JQueryEvent event) { - if (event instanceof SelectEvent) + if (event instanceof SelectEvent selectEvent) { - this.listener.onSelect(target, ((SelectEvent) event).getIndex()); + this.listener.onSelect(target, listener.getElementSelectionStrategy().findChoice(supplier.get(), selectEvent.getIdentifier()), selectEvent.getIdentifier()); } } // Factories // /** - * Gets a new {@link JQueryAjaxBehavior} that will be wired to the 'select' event + * Gets a new {@link JQueryAjaxBehavior} that will be wired to the 'select' eventta * * @param source the {@link IJQueryAjaxAware} * @return a new {@code OnSelectAjaxBehavior} by default @@ -127,7 +136,7 @@ protected JQueryAjaxBehavior newOnSelectAjaxBehavior(IJQueryAjaxAware source) return new OnSelectAjaxBehavior(source); } - // Ajax classes // + // Ajax classes //, /** * Provides a {@link JQueryAjaxBehavior} that aims to be wired to the 'select' event @@ -163,16 +172,31 @@ protected JQueryEvent newEvent() */ protected static class SelectEvent extends JQueryEvent { - private final int index; + private final String identifier; public SelectEvent() { - this.index = RequestCycleUtils.getQueryParameterValue("index").toInt(0); + this.identifier = RequestCycleUtils.getQueryParameterValue("index").toString(); } - public int getIndex() + /** + * Gets the selected item identifier (JSON {@code id}). For the default index strategy this is the list index. + * + * @return the identifier + */ + public String getIdentifier() { - return this.index; + return this.identifier; } } + + /** + * Gets the {@link JQueryAjaxBehavior} wired to the 'select' event + * + * @return the {@code OnSelectAjaxBehavior}, or {@code null} if the behavior has not been bound yet + */ + public final JQueryAjaxBehavior getOnSelectAjaxBehavior() + { + return this.onSelectAjaxBehavior; + } } diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/AutoCompleteChoiceModelBehavior.java b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/AutoCompleteChoiceModelBehavior.java index 619e4c3829..3953a6fc69 100644 --- a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/AutoCompleteChoiceModelBehavior.java +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/AutoCompleteChoiceModelBehavior.java @@ -33,7 +33,7 @@ * @param the model object type * @author Sebastien Briquet - sebfz1 */ -abstract class AutoCompleteChoiceModelBehavior extends ChoiceModelBehavior +public abstract class AutoCompleteChoiceModelBehavior extends ChoiceModelBehavior { private static final long serialVersionUID = 1L; @@ -47,6 +47,16 @@ public AutoCompleteChoiceModelBehavior(ITextRenderer renderer, IJQuer super(renderer, template); } + /** + * Gets the {@link IElementSelectionStrategy}. Index-based selection is used by default. + * + * @return the selection strategy + */ + protected IElementSelectionStrategy getElementSelectionStrategy() + { + return IndexBasedElementSelectionStrategy.get(); + } + @Override protected String getResponse(IRequestParameters parameters) { @@ -61,7 +71,8 @@ protected String getResponse(IRequestParameters parameters) // ITextRenderer // final JSONObject object = this.renderer.render(choice); - object.put("id", Integer.toString(index)); /* 'id' is a reserved word */ + String identifier = this.getElementSelectionStrategy().getIdentifier(choice, index); + object.put("id", identifier != null ? identifier : Integer.toString(index)); /* 'id' is a reserved word */ object.put("value", this.renderer.getText(choice)); /* 'value' is a reserved word */ // Additional properties (like template properties) // diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/IAutoCompleteListener.java b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/IAutoCompleteListener.java index 4c091fc58f..50cebbd539 100644 --- a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/IAutoCompleteListener.java +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/IAutoCompleteListener.java @@ -16,8 +16,12 @@ */ package org.wicketstuff.jquery.ui.form.autocomplete; +import java.util.List; + import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.util.io.IClusterable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Event listener shared by the {@link AutoCompleteTextField} widget and the {@link AutoCompleteBehavior} @@ -25,13 +29,40 @@ * @author Sebastien Briquet - sebfz1 * */ -public interface IAutoCompleteListener extends IClusterable +public interface IAutoCompleteListener extends IClusterable { + Logger LOG = LoggerFactory.getLogger(IAutoCompleteListener.class); + + /** + * Gets the {@link IElementSelectionStrategy} used to identify a selected choice and to resolve it from the cached choice list. + * + * @return the {@link IElementSelectionStrategy} + */ + IElementSelectionStrategy getElementSelectionStrategy(); + /** * Triggered when a selection has been made * * @param target the {@link AjaxRequestTarget} - * @param index the index of the selected item + * @param choice the selected choice, or {@code null} if the identifier could not be resolved + * @param identifier the identifier of the selected item (JSON {@code id}; list index by default, or a business id) + */ + void onSelect(AjaxRequestTarget target, T choice, String identifier); + + /** + * Triggered when a selection has been made, using the identifier posted by the client. + *

+ * The default implementation resolves the choice from {@code choiceList} via + * {@link #getElementSelectionStrategy()} and {@link IElementSelectionStrategy#findChoice(List, String)}, + * then delegates to {@link #onSelect(AjaxRequestTarget, Object, String)}. + * + * @param target the {@link AjaxRequestTarget} + * @param choiceList the cached list of choices matching the last query + * @param identifier the identifier of the selected item (JSON {@code id}; list index by default, or a business id) */ - void onSelect(AjaxRequestTarget target, int index); + default void onSelect(AjaxRequestTarget target, List choiceList, String identifier) + { + T choice = getElementSelectionStrategy().findChoice(choiceList, identifier); + onSelect(target, choice, identifier); + } } diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/IElementSelectionStrategy.java b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/IElementSelectionStrategy.java new file mode 100644 index 0000000000..7a1426d634 --- /dev/null +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/IElementSelectionStrategy.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wicketstuff.jquery.ui.form.autocomplete; + +import java.io.Serializable; +import java.util.List; + +import org.apache.wicket.util.io.IClusterable; + +/** + * Strategy used to identify a selected auto-complete choice and to resolve it from the cached choice list. + *

+ * The identifier is written to the JSON {@code id} field and posted back when the user selects an item. + * {@link IndexBasedElementSelectionStrategy} is the default implementation. + *

+ * Implementations must be {@link Serializable}: the strategy is stored on a Wicket component and is + * serialized with the page/session. + * + * @param the choice type + * @author reiern70 + */ +public interface IElementSelectionStrategy extends IClusterable, Serializable +{ + /** + * Gets the identifier that uniquely represents the given choice in the current result set. + * + * @param choice the choice + * @param index the index of the choice in the current result list + * @return the identifier posted to the client (JSON {@code id}) + */ + String getIdentifier(T choice, int index); + + /** + * Resolves the selected choice from the cached list using the identifier posted by the client. + * + * @param choices the cached list of choices + * @param identifier the identifier posted by the client + * @return the matching choice, or {@code null} if none matches + */ + T findChoice(List choices, String identifier); +} diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/IdBasedElementSelectionStrategy.java b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/IdBasedElementSelectionStrategy.java new file mode 100644 index 0000000000..e585f30f73 --- /dev/null +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/IdBasedElementSelectionStrategy.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wicketstuff.jquery.ui.form.autocomplete; + +import java.util.List; +import java.util.Objects; + +/** + * {@link IElementSelectionStrategy} that identifies choices by an id value. + * Subclasses implement {@link #getValue(Object, int)} to extract that id. + * + * @param the choice type + * @author reiern70 + */ +public abstract class IdBasedElementSelectionStrategy implements IElementSelectionStrategy +{ + private static final long serialVersionUID = 1L; + + /** + * Gets the id value that uniquely represents the given choice. + * + * @param choice the choice + * @param index the index of the choice in the current result list + * @return the id value, or {@code null} if none + */ + public abstract String getValue(T choice, int index); + + @Override + public String getIdentifier(T choice, int index) + { + return this.getValue(choice, index); + } + + @Override + public T findChoice(List choices, String identifier) + { + if (choices == null || identifier == null) + { + return null; + } + + for (int index = 0; index < choices.size(); ++index) + { + T choice = choices.get(index); + + if (Objects.equals(identifier, this.getValue(choice, index))) + { + return choice; + } + } + + return null; + } +} diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/IndexBasedElementSelectionStrategy.java b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/IndexBasedElementSelectionStrategy.java new file mode 100644 index 0000000000..3d6d055340 --- /dev/null +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/IndexBasedElementSelectionStrategy.java @@ -0,0 +1,86 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wicketstuff.jquery.ui.form.autocomplete; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * {@link IElementSelectionStrategy} that identifies choices by their index in the current result list. + * This is the default strategy used by {@link AbstractAutoCompleteTextField}. + * + * @param the choice type + * @author reiern70 + */ +public class IndexBasedElementSelectionStrategy implements IElementSelectionStrategy +{ + private static final long serialVersionUID = 1L; + private static final Logger logger = LoggerFactory.getLogger(IndexBasedElementSelectionStrategy.class); + + private static final IndexBasedElementSelectionStrategy INSTANCE = new IndexBasedElementSelectionStrategy<>(); + + /** + * Gets a shared instance of this strategy. + * + * @param the choice type + * @return the shared strategy + */ + @SuppressWarnings("unchecked") + public static IndexBasedElementSelectionStrategy get() + { + return (IndexBasedElementSelectionStrategy) INSTANCE; + } + + private Object readResolve() + { + return INSTANCE; + } + + @Override + public String getIdentifier(T choice, int index) + { + return Integer.toString(index); + } + + @Override + public T findChoice(List choices, String identifier) + { + if (choices == null || identifier == null) + { + return null; + } + + try + { + int index = Integer.parseInt(identifier); + + if (-1 < index && index < choices.size()) + { + return choices.get(index); + } + } + catch (NumberFormatException e) + { + logger.warn("Failed to parse identifier '{}' as integer for choices lookup.", identifier, e); + return null; + } + + return null; + } +} diff --git a/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/PropertyBasedElementSelectionStrategy.java b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/PropertyBasedElementSelectionStrategy.java new file mode 100644 index 0000000000..63ee541793 --- /dev/null +++ b/wicketstuff-jquery-ui-parent/wicketstuff-jquery-ui/src/main/java/org/wicketstuff/jquery/ui/form/autocomplete/PropertyBasedElementSelectionStrategy.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.wicketstuff.jquery.ui.form.autocomplete; + +import org.apache.wicket.core.util.lang.PropertyResolver; +import org.apache.wicket.util.lang.Args; + +/** + * {@link IdBasedElementSelectionStrategy} that resolves the id from a bean property expression. + * + * @param the choice type + * @author reiern70 + */ +public class PropertyBasedElementSelectionStrategy extends IdBasedElementSelectionStrategy +{ + private static final long serialVersionUID = 1L; + + private final String idExpression; + + /** + * Constructor + * + * @param idExpression the property expression resolved on each choice (e.g. {@code "id"}) + */ + public PropertyBasedElementSelectionStrategy(String idExpression) + { + this.idExpression = Args.notEmpty(idExpression, "idExpression"); + } + + @Override + public String getValue(T choice, int index) + { + if (choice == null) + { + return null; + } + + Object value = PropertyResolver.getValue(this.idExpression, choice); + + return value != null ? value.toString() : null; + } +}