/* * 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.apache.wicket.extensions.markup.html.form.select; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import org.apache.wicket.WicketRuntimeException; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.form.FormComponent; import org.apache.wicket.model.IModel; import org.apache.wicket.util.lang.Args; import org.apache.wicket.util.lang.Objects; import org.apache.wicket.util.string.Strings; import org.apache.wicket.util.visit.IVisit; import org.apache.wicket.util.visit.IVisitor; /** * Component that represents a <code><select></code> box. Elements are provided by one or more * <code>SelectOptions</code> components in the hierarchy below the <code>Select</code> component. * * Advantages to the standard choice components is that the user has a lot more control over the * markup between the <select> tag and its children <option> tags: allowing for such * things as <optgroup> tags. * * <p> * Example HTML: * * <pre> * <select wicket:id="select" multiple="multiple"> * <wicket:container wicket:id="options"> * <option wicket:id="option">Option Label</option> * </wicket:container> * </select> * </pre> * * Related Java Code: * * <pre> * Select select = new Select("select", selectionModel); * add(select); * SelectOptions options = new SelectOptions("options", elements, renderer); * select.add(options); * </pre> * * Note that you don't need to add component(s) for the <option> tag - they are created by * SelectOptions * </p> * <p> * <strong>Note</strong>: due to the usage of a SelectOption for each <option> the memory * footprint of the page will grow with the number of <option>s you need. Consider using * {@link org.apache.wicket.markup.html.form.DropDownChoice} component if it is able to fulfill your * requirements. * </p> * * @see SelectOption * @see SelectOptions * @see org.apache.wicket.markup.html.form.DropDownChoice * * @author Igor Vaynberg * @param <T> */ public class Select<T> extends FormComponent<T> { private static final long serialVersionUID = 1L; /** * Constructor that will create a default model collection * * @param id * component id */ public Select(final String id) { super(id); } /** * @param id * @param model * @see WebMarkupContainer#WebMarkupContainer(String, IModel) */ public Select(final String id, final IModel<T> model) { super(id, model); } @Override protected String getModelValue() { final StringBuilder builder = new StringBuilder(); visitChildren(SelectOption.class, new IVisitor<SelectOption<T>, Void>() { @Override public void component(SelectOption<T> option, IVisit<Void> visit) { if (isSelected(option.getDefaultModel())) { if (builder.length() > 0) { builder.append(VALUE_SEPARATOR); } builder.append(option.getValue()); } } }); return builder.toString(); } @Override public void convertInput() { boolean supportsMultiple = getModelObject() instanceof Collection; /* * + * the input contains an array of values of the selected option components unless * nothing was selected in which case the input contains null */ String[] values = getInputAsArray(); if ((values == null) || (values.length == 0)) { setConvertedInput(null); return; } if (!supportsMultiple && (values.length > 1)) { throw new WicketRuntimeException( "The model of Select component [" + getPath() + "] is not of type java.util.Collection, but more then one SelectOption component has been selected. Either remove the multiple attribute from the select tag or make the model of the Select component a collection"); } List<Object> converted = new ArrayList<>(values.length); /* * if the input is null we do not need to do anything since the model collection has already * been cleared */ for (int i = 0; i < values.length; i++) { final String value = values[i]; if (!Strings.isEmpty(value)) { SelectOption<T> option = visitChildren(SelectOption.class, new IVisitor<SelectOption<T>, SelectOption<T>>() { @Override public void component(SelectOption<T> option, IVisit<SelectOption<T>> visit) { if (String.valueOf(option.getValue()).equals(value)) { visit.stop(option); } } }); if (option == null) { throw new WicketRuntimeException( "submitted http post value [" + Arrays.toString(values) + "] for SelectOption component [" + getPath() + "] contains an illegal value [" + value + "] which does not point to a SelectOption component. Due to this the Select component cannot resolve the selected SelectOption component pointed to by the illegal value. A possible reason is that component hierarchy changed between rendering and form submission."); } converted.add(option.getDefaultModelObject()); } } if (converted.isEmpty()) { setConvertedInput(null); } else if (!supportsMultiple) { @SuppressWarnings("unchecked") T convertedInput = (T)converted.get(0); setConvertedInput(convertedInput); } else { @SuppressWarnings("unchecked") T convertedInput = (T)converted; setConvertedInput(convertedInput); } } /** * @see FormComponent#updateModel() */ @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public void updateModel() { T object = getModelObject(); boolean supportsMultiple = object instanceof Collection; Object converted = getConvertedInput(); /* * update the model */ if (supportsMultiple) { Collection<?> modelCollection = (Collection<?>)object; modelChanging(); modelCollection.clear(); if (converted != null) { modelCollection.addAll((Collection)converted); } modelChanged(); // force notify of model update via setObject() setDefaultModelObject(modelCollection); } else { setDefaultModelObject(converted); } } /** * Checks if the specified option is selected based on raw input * * @param option * @return {@code true} if the option is selected, {@code false} otherwise */ boolean isSelected(final SelectOption<?> option) { Args.notNull(option, "option"); // if the raw input is specified use that, otherwise use model if (hasRawInput()) { final String raw = getRawInput(); if (!Strings.isEmpty(raw)) { String[] values = raw.split(VALUE_SEPARATOR); for (int i = 0; i < values.length; i++) { String value = values[i]; if (value.equals(option.getValue())) { return true; } } } return false; } return isSelected(option.getDefaultModel()); } /** * Does the given model contain a selected value. * * @param model * model to test on selection * @return selected */ protected boolean isSelected(IModel<?> model) { return compareModels(getDefaultModelObject(), model.getObject()); } private boolean compareModels(final Object selected, final Object value) { if ((selected != null) && (selected instanceof Collection)) { if (value instanceof Collection) { return ((Collection<?>)selected).containsAll((Collection<?>)value); } else { return ((Collection<?>)selected).contains(value); } } else { return Objects.equal(selected, value); } } }