001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.extensions.markup.html.form.select;
018
019import java.util.Collection;
020
021import org.apache.wicket.markup.ComponentTag;
022import org.apache.wicket.markup.MarkupStream;
023import org.apache.wicket.markup.html.WebMarkupContainer;
024import org.apache.wicket.markup.parser.XmlTag.TagType;
025import org.apache.wicket.markup.repeater.RepeatingView;
026import org.apache.wicket.model.IModel;
027import org.apache.wicket.model.util.CollectionModel;
028import org.apache.wicket.util.string.Strings;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032
033/**
034 * Component that makes it easy to produce a list of SelectOption components.
035 * <p>
036 * Has to be attached to a &lt;option&gt; markup tag.
037 * <p>
038 * Note: The following pre Wicket 9 markup is deprecated and results in a log warning. Its support
039 * will be removed in Wicket 10:
040 *
041 * <pre>
042 * <code>
043 * &lt;wicket:container wicket:id=&quot;selectOptions&quot;&gt;&lt;option wicket:id=&quot;option&quot;&gt;&lt;/option&gt;&lt;/wicket:container&gt;
044 * </code>
045 * </pre>
046 * 
047 * @param <T>
048 *            type of elements contained in the model's collection
049 * @author Igor Vaynberg (ivaynberg)
050 */
051public class SelectOptions<T> extends RepeatingView
052{
053        private static final long serialVersionUID = 1L;
054
055        private static final Logger log = LoggerFactory.getLogger(SelectOptions.class);
056
057        private boolean recreateChoices = false;
058
059        private final IOptionRenderer<T> renderer;
060
061        /**
062         * Constructor
063         * 
064         * @param id
065         * @param model
066         * @param renderer
067         */
068        public SelectOptions(final String id, final IModel<? extends Collection<? extends T>> model,
069                final IOptionRenderer<T> renderer)
070        {
071                super(id, model);
072                this.renderer = renderer;
073                setRenderBodyOnly(true);
074        }
075
076        /**
077         * Constructor
078         * 
079         * @param id
080         * @param elements
081         * @param renderer
082         */
083        public SelectOptions(final String id, final Collection<? extends T> elements,
084                final IOptionRenderer<T> renderer)
085        {
086                this(id, new CollectionModel<>(elements), renderer);
087        }
088
089        /**
090         * Controls whether {@link SelectOption}s are recreated on each render.
091         * <p>
092         * Note: When recreating on each render, {@link #newOption(String, IModel)} should return
093         * {@link SelectOption}s with stable values, i.e. {@link SelectOption#getValue()} should return
094         * a value based on its model object instead of the default auto index. Otherwise the current
095         * selection will be lost on form errors.
096         * 
097         * @param refresh
098         * @return this for chaining
099         * 
100         * @see SelectOption#getValue()
101         */
102        public SelectOptions<T> setRecreateChoices(final boolean refresh)
103        {
104                recreateChoices = refresh;
105                return this;
106        }
107
108        /**
109         * {@inheritDoc}
110         */
111        @SuppressWarnings("unchecked")
112        @Override
113        protected final void onPopulate()
114        {
115                if ((size() == 0) || recreateChoices)
116                {
117                        // populate this repeating view with SelectOption components
118                        removeAll();
119
120                        Collection<? extends T> modelObject = (Collection<? extends T>)getDefaultModelObject();
121                        if (modelObject != null)
122                        {
123                                // TODO remove in Wicket 10
124                                boolean option = "option".equalsIgnoreCase(getMarkupTag().getName());
125                                if (option == false)
126                                {
127                                        log.warn("Since version 9.0.0 you should use an option tag");
128                                }
129
130                                for (T value : modelObject)
131                                {
132                                        // we add our actual SelectOption component to the row
133                                        String text = renderer.getDisplayValue(value);
134                                        IModel<T> model = renderer.getModel(value);
135
136                                        if (option)
137                                        {
138                                                add(newOption(newChildId(), text, model));
139                                        }
140                                        else
141                                        {
142                                                // pre Wicket 9 a container is used to represent a row in repeater
143                                                WebMarkupContainer row = new WebMarkupContainer(newChildId());
144                                                row.setRenderBodyOnly(true);
145                                                add(row);
146
147                                                // we add our actual SelectOption component to the row
148                                                row.add(newOption(text, model));
149                                        }
150                                }
151                        }
152                }
153        }
154
155        /**
156         * @deprecated override {@link #newOption(String, String, IModel)} instead.
157         */
158        protected SelectOption<T> newOption(final String text, final IModel<T> model)
159        {
160                return newOption("option",  text, model);
161        }
162
163        /**
164         * Factory method for creating a new <code>SelectOption</code>. Override to add your own
165         * extensions, such as Ajax behaviors.
166         * 
167         * @param id
168         *            component id
169         * @param text
170         * @param model
171         * @return a {@link SelectOption}
172         */
173        protected SelectOption<T> newOption(final String id, final String text, final IModel<T> model)
174        {
175                SimpleSelectOption<T> option = new SimpleSelectOption<>(id, model, text);
176                option.setEscapeModelStrings(this.getEscapeModelStrings());
177                return option;
178        }
179
180        /**
181         * 
182         * @param <V>
183         */
184        private static class SimpleSelectOption<V> extends SelectOption<V>
185        {
186                private static final long serialVersionUID = 1L;
187
188                private final String text;
189
190                /**
191                 * @param id
192                 * @param model
193                 * @param text
194                 */
195                public SimpleSelectOption(final String id, final IModel<V> model, final String text)
196                {
197                        super(id, model);
198                        this.text = text;
199                }
200
201                /**
202                 * {@inheritDoc}
203                 */
204                @Override
205                public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag)
206                {
207                        CharSequence escaped = text;
208                        if (getEscapeModelStrings())
209                        {
210                                escaped = Strings.escapeMarkup(text);
211                        }
212
213                        replaceComponentTagBody(markupStream, openTag, escaped);
214                }
215
216                /**
217                 * {@inheritDoc}
218                 */
219                @Override
220                protected void onComponentTag(ComponentTag tag)
221                {
222                        super.onComponentTag(tag);
223
224                        // always transform the tag to <label></label> so even markup defined as <label/>
225                        // render
226                        tag.setType(TagType.OPEN);
227                }
228        }
229
230        @Override
231        protected void onDetach()
232        {
233                renderer.detach();
234
235                super.onDetach();
236        }
237}