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.parser.XmlTag.TagType;
024import org.apache.wicket.markup.repeater.RepeatingView;
025import org.apache.wicket.model.IModel;
026import org.apache.wicket.model.util.CollectionModel;
027import org.apache.wicket.util.string.Strings;
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031
032/**
033 * Component that makes it easy to produce a list of SelectOption components.
034 * <p>
035 * Has to be attached to a &lt;option&gt; markup tag.
036 * 
037 * @param <T>
038 *            type of elements contained in the model's collection
039 * @author Igor Vaynberg (ivaynberg)
040 */
041public class SelectOptions<T> extends RepeatingView
042{
043        private static final long serialVersionUID = 1L;
044
045        private static final Logger log = LoggerFactory.getLogger(SelectOptions.class);
046
047        private boolean recreateChoices = false;
048
049        private final IOptionRenderer<T> renderer;
050
051        /**
052         * Constructor
053         * 
054         * @param id
055         * @param model
056         * @param renderer
057         */
058        public SelectOptions(final String id, final IModel<? extends Collection<? extends T>> model,
059                final IOptionRenderer<T> renderer)
060        {
061                super(id, model);
062                this.renderer = renderer;
063                setRenderBodyOnly(true);
064        }
065
066        /**
067         * Constructor
068         * 
069         * @param id
070         * @param elements
071         * @param renderer
072         */
073        public SelectOptions(final String id, final Collection<? extends T> elements,
074                final IOptionRenderer<T> renderer)
075        {
076                this(id, new CollectionModel<>(elements), renderer);
077        }
078
079        /**
080         * Controls whether {@link SelectOption}s are recreated on each render.
081         * <p>
082         * Note: When recreating on each render, {@link #newOption(String, String, IModel)} should return
083         * {@link SelectOption}s with stable values, i.e. {@link SelectOption#getValue()} should return
084         * a value based on its model object instead of the default auto index, otherwise the current
085         * selection will be lost on form errors.
086         * 
087         * @param refresh
088         * @return this for chaining
089         * 
090         * @see SelectOption#getValue()
091         */
092        public SelectOptions<T> setRecreateChoices(final boolean refresh)
093        {
094                recreateChoices = refresh;
095                return this;
096        }
097
098        /**
099         * {@inheritDoc}
100         */
101        @SuppressWarnings("unchecked")
102        @Override
103        protected final void onPopulate()
104        {
105                if ((size() == 0) || recreateChoices)
106                {
107                        // populate this repeating view with SelectOption components
108                        removeAll();
109
110                        Collection<? extends T> modelObject = (Collection<? extends T>)getDefaultModelObject();
111                        if (modelObject != null)
112                        {
113                                for (T value : modelObject)
114                                {
115                                        // we add our actual SelectOption component to the row
116                                        String text = renderer.getDisplayValue(value);
117                                        IModel<T> model = renderer.getModel(value);
118
119                                        add(newOption(newChildId(), text, model));
120                                }
121                        }
122                }
123        }
124
125        /**
126         * Factory method for creating a new <code>SelectOption</code>. Override to add your own
127         * extensions, such as Ajax behaviors.
128         * 
129         * @param id
130         *            component id
131         * @param text
132         * @param model
133         * @return a {@link SelectOption}
134         */
135        protected SelectOption<T> newOption(final String id, final String text, final IModel<T> model)
136        {
137                SimpleSelectOption<T> option = new SimpleSelectOption<>(id, model, text);
138                option.setEscapeModelStrings(this.getEscapeModelStrings());
139                return option;
140        }
141
142        /**
143         * 
144         * @param <V>
145         */
146        private static class SimpleSelectOption<V> extends SelectOption<V>
147        {
148                private static final long serialVersionUID = 1L;
149
150                private final String text;
151
152                /**
153                 * @param id
154                 * @param model
155                 * @param text
156                 */
157                public SimpleSelectOption(final String id, final IModel<V> model, final String text)
158                {
159                        super(id, model);
160                        this.text = text;
161                }
162
163                /**
164                 * {@inheritDoc}
165                 */
166                @Override
167                public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag)
168                {
169                        CharSequence escaped = text;
170                        if (getEscapeModelStrings())
171                        {
172                                escaped = Strings.escapeMarkup(text);
173                        }
174
175                        replaceComponentTagBody(markupStream, openTag, escaped);
176                }
177
178                /**
179                 * {@inheritDoc}
180                 */
181                @Override
182                protected void onComponentTag(ComponentTag tag)
183                {
184                        super.onComponentTag(tag);
185
186                        // always transform the tag to <label></label> so even markup defined as <label/>
187                        // render
188                        tag.setType(TagType.OPEN);
189                }
190        }
191
192        @Override
193        protected void onDetach()
194        {
195                renderer.detach();
196
197                super.onDetach();
198        }
199}