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.markup.html.form;
018
019import java.util.Collection;
020import java.util.List;
021import java.util.Map;
022
023import org.apache.wicket.Page;
024import org.apache.wicket.markup.ComponentTag;
025import org.apache.wicket.model.IModel;
026import org.apache.wicket.settings.DebugSettings;
027import org.apache.wicket.util.lang.Args;
028import org.apache.wicket.util.string.AppendingStringBuffer;
029import org.apache.wicket.util.string.Strings;
030import org.apache.wicket.util.value.IValueMap;
031
032
033/**
034 * A choice subclass that shows choices via checkboxes.
035 * <p>
036 * Java:
037 * 
038 * <pre>
039 * List SITES = Arrays.asList(new String[] { &quot;The Server Side&quot;, &quot;Java Lobby&quot;, &quot;Java.Net&quot; });
040 * // Add a set of checkboxes uses Input's 'site' property to designate the
041 * // current selections, and that uses the SITES list for the available options.
042 * form.add(new CheckBoxMultipleChoice(&quot;site&quot;, SITES));
043 * </pre>
044 * 
045 * HTML:
046 * 
047 * <pre>
048 *    &lt;span valign=&quot;top&quot; wicket:id=&quot;site&quot;&gt;
049 *      &lt;input type=&quot;checkbox&quot;&gt;site 1&lt;/input&gt;
050 *      &lt;input type=&quot;checkbox&quot;&gt;site 2&lt;/input&gt;
051 *    &lt;/span&gt;
052 * </pre>
053 * 
054 * </p>
055 * 
056 * @author Jonathan Locke
057 * @author Johan Compagner
058 * @author Martijn Dashorst
059 * @author Gwyn Evans
060 * @author Igor Vaynberg (ivaynberg)
061 * 
062 * @param <T>
063 *            The model object type
064 */
065public class CheckBoxMultipleChoice<T> extends ListMultipleChoice<T>
066{
067        private static final long serialVersionUID = 1L;
068
069        private String prefix = "";
070        private String suffix = "";
071
072        private LabelPosition labelPosition = LabelPosition.AFTER;
073
074        /**
075         * Constructor
076         * 
077         * @param id
078         *            See Component
079         * @see org.apache.wicket.Component#Component(String)
080         * @see AbstractChoice#AbstractChoice(String)
081         */
082        public CheckBoxMultipleChoice(final String id)
083        {
084                super(id);
085        }
086
087        /**
088         * Constructor
089         * 
090         * @param id
091         *            See Component
092         * @param choices
093         *            The collection of choices in the radio choice
094         * @see org.apache.wicket.Component#Component(String)
095         * @see AbstractChoice#AbstractChoice(String, java.util.List)
096         */
097        public CheckBoxMultipleChoice(final String id, final List<? extends T> choices)
098        {
099                super(id, choices);
100        }
101
102        /**
103         * Constructor
104         * 
105         * @param id
106         *            See Component
107         * @param renderer
108         *            The rendering engine
109         * @param choices
110         *            The collection of choices in the radio choice
111         * @see org.apache.wicket.Component#Component(String)
112         * @see AbstractChoice#AbstractChoice(String,
113         *      java.util.List,org.apache.wicket.markup.html.form.IChoiceRenderer)
114         */
115        public CheckBoxMultipleChoice(final String id, final List<? extends T> choices,
116                final IChoiceRenderer<? super T> renderer)
117        {
118                super(id, choices, renderer);
119        }
120
121        /**
122         * Constructor
123         * 
124         * @param id
125         *            See Component
126         * @param model
127         *            See Component
128         * @param choices
129         *            The collection of choices in the radio choice
130         * @see org.apache.wicket.Component#Component(String, org.apache.wicket.model.IModel)
131         * @see AbstractChoice#AbstractChoice(String, org.apache.wicket.model.IModel, java.util.List)
132         */
133        public CheckBoxMultipleChoice(final String id, IModel<? extends Collection<T>> model,
134                final List<? extends T> choices)
135        {
136                super(id, model, choices);
137        }
138
139        /**
140         * Constructor
141         * 
142         * @param id
143         *            See Component
144         * @param model
145         *            See Component
146         * @param choices
147         *            The collection of choices in the radio choice
148         * @param renderer
149         *            The rendering engine
150         * @see org.apache.wicket.Component#Component(String, org.apache.wicket.model.IModel)
151         * @see AbstractChoice#AbstractChoice(String, org.apache.wicket.model.IModel,
152         *      java.util.List,org.apache.wicket.markup.html.form.IChoiceRenderer)
153         */
154        public CheckBoxMultipleChoice(final String id, IModel<? extends Collection<T>> model,
155                final List<? extends T> choices, final IChoiceRenderer<? super T> renderer)
156        {
157                super(id, model, choices, renderer);
158        }
159
160        /**
161         * Constructor
162         * 
163         * @param id
164         *            See Component
165         * @param choices
166         *            The collection of choices in the radio choice
167         * @see org.apache.wicket.Component#Component(String)
168         * @see AbstractChoice#AbstractChoice(String, org.apache.wicket.model.IModel)
169         */
170        public CheckBoxMultipleChoice(String id, IModel<? extends List<? extends T>> choices)
171        {
172                super(id, choices);
173        }
174
175        /**
176         * Constructor
177         * 
178         * @param id
179         *            See Component
180         * @param model
181         *            The model that is updated with changes in this component. See Component
182         * @param choices
183         *            The collection of choices in the radio choice
184         * @see AbstractChoice#AbstractChoice(String,
185         *      org.apache.wicket.model.IModel,org.apache.wicket.model.IModel)
186         * @see org.apache.wicket.Component#Component(String, org.apache.wicket.model.IModel)
187         */
188        public CheckBoxMultipleChoice(String id, IModel<? extends Collection<T>> model,
189                IModel<? extends List<? extends T>> choices)
190        {
191                super(id, model, choices);
192        }
193
194        /**
195         * Constructor
196         * 
197         * @param id
198         *            See Component
199         * @param choices
200         *            The collection of choices in the radio choice
201         * @param renderer
202         *            The rendering engine
203         * @see AbstractChoice#AbstractChoice(String,
204         *      org.apache.wicket.model.IModel,org.apache.wicket.markup.html.form.IChoiceRenderer)
205         * @see org.apache.wicket.Component#Component(String)
206         */
207        public CheckBoxMultipleChoice(String id, IModel<? extends List<? extends T>> choices,
208                IChoiceRenderer<? super T> renderer)
209        {
210                super(id, choices, renderer);
211        }
212
213        /**
214         * Constructor
215         * 
216         * @param id
217         *            See Component
218         * @param model
219         *            The model that is updated with changes in this component. See Component
220         * @param choices
221         *            The collection of choices in the radio choice
222         * @param renderer
223         *            The rendering engine
224         * @see org.apache.wicket.Component#Component(String, org.apache.wicket.model.IModel)
225         * @see AbstractChoice#AbstractChoice(String, org.apache.wicket.model.IModel,
226         *      org.apache.wicket.model.IModel,org.apache.wicket.markup.html.form.IChoiceRenderer)
227         */
228        public CheckBoxMultipleChoice(String id, IModel<? extends Collection<T>> model,
229                IModel<? extends List<? extends T>> choices, IChoiceRenderer<? super T> renderer)
230        {
231                super(id, model, choices, renderer);
232        }
233
234        /**
235         * @return Prefix to use before choice
236         */
237        public String getPrefix()
238        {
239                return prefix;
240        }
241
242        /**
243         * @param index
244         *            index of the choice
245         * @param choice
246         *            the choice itself
247         * @return Prefix to use before choice. The default implementation just returns
248         *         {@link #getPrefix()}. Override to have a prefix dependent on the choice item.
249         */
250        protected String getPrefix(int index, T choice)
251        {
252                return getPrefix();
253        }
254
255        /**
256         * @param index
257         *            index of the choice
258         * @param choice
259         *            the choice itself
260         * @return Separator to use between radio options. The default implementation just returns
261         *         {@link #getSuffix()}. Override to have a prefix dependent on the choice item.
262         */
263        protected String getSuffix(int index, T choice)
264        {
265                return getSuffix();
266        }
267
268        /**
269         * @param prefix
270         *            Prefix to use before choice
271         * @return this
272         */
273        public final CheckBoxMultipleChoice<T> setPrefix(final String prefix)
274        {
275                // Tell the page that this component's prefix was changed
276                final Page page = findPage();
277                if (page != null)
278                {
279                        addStateChange();
280                }
281
282                this.prefix = prefix;
283                return this;
284        }
285
286        /**
287         * @return Separator to use between radio options
288         */
289        public String getSuffix()
290        {
291                return suffix;
292        }
293
294        /**
295         * @param suffix
296         *            Separator to use between radio options
297         * @return this
298         */
299        public final CheckBoxMultipleChoice<T> setSuffix(final String suffix)
300        {
301                // Tell the page that this component's suffix was changed
302                final Page page = findPage();
303                if (page != null)
304                {
305                        addStateChange();
306                }
307
308                this.suffix = suffix;
309                return this;
310        }
311
312        /**
313         * Sets the preferred position of the &lt;label&gt; for each choice
314         *
315         * @param labelPosition
316         *              The preferred position for the label
317         * @return {@code this} instance, for chaining
318         */
319        public CheckBoxMultipleChoice<T> setLabelPosition(LabelPosition labelPosition)
320        {
321                Args.notNull(labelPosition, "labelPosition");
322                this.labelPosition = labelPosition;
323                return this;
324        }
325
326        /**
327         * @see org.apache.wicket.markup.html.form.ListMultipleChoice#onComponentTag(org.apache.wicket.markup.ComponentTag)
328         */
329        @Override
330        protected void onComponentTag(ComponentTag tag)
331        {
332                super.onComponentTag(tag);
333                // No longer applicable, breaks XHTML validation.
334                tag.remove("multiple");
335                tag.remove("size");
336                tag.remove("disabled");
337                tag.remove("name");
338        }
339
340        /**
341         * Generates and appends html for a single choice into the provided buffer
342         * 
343         * @param buffer
344         *            Appending string buffer that will have the generated html appended
345         * @param choice
346         *            Choice object
347         * @param index
348         *            The index of this option
349         * @param selected
350         *            The currently selected string value
351         */
352        @Override
353        protected void appendOptionHtml(final AppendingStringBuffer buffer, final T choice, int index,
354                final String selected)
355        {
356                // Append option suffix
357                buffer.append(getPrefix(index, choice));
358
359                String id = getChoiceRenderer().getIdValue(choice, index);
360                final String idAttr = getCheckBoxMarkupId(id);
361
362                CharSequence renderValue = renderValue(choice);
363
364                // Allows user to add attributes to the <label..> tag
365                IValueMap labelAttrs = getAdditionalAttributesForLabel(index, choice);
366                StringBuilder extraLabelAttributes = new StringBuilder();
367                if (labelAttrs != null)
368                {
369                        for (Map.Entry<String, Object> attr : labelAttrs.entrySet())
370                        {
371                                extraLabelAttributes.append(' ')
372                                                .append(Strings.escapeMarkup(attr.getKey()))
373                                                .append("=\"")
374                                                .append(Strings.escapeMarkup(attr.getValue().toString()))
375                                                .append('"');
376                        }
377                }
378
379                labelPosition.before(buffer, idAttr, extraLabelAttributes, renderValue);
380
381                // Add checkbox element
382                buffer.append("<input name=\"");
383                buffer.append(getInputName());
384                buffer.append('"');
385                buffer.append(" type=\"checkbox\"");
386                if (isSelected(choice, index, selected))
387                {
388                        buffer.append(" checked=\"checked\"");
389                }
390                if (isDisabled(choice, index, selected) || !isEnabledInHierarchy())
391                {
392                        buffer.append(" disabled=\"disabled\"");
393                }
394                buffer.append(" value=\"");
395                buffer.append(Strings.escapeMarkup(id));
396                buffer.append("\" id=\"");
397                buffer.append(Strings.escapeMarkup(idAttr));
398                buffer.append('"');
399
400                // Allows user to add attributes to the <input..> tag
401                {
402                        IValueMap attrs = getAdditionalAttributes(index, choice);
403                        if (attrs != null)
404                        {
405                                for (Map.Entry<String, Object> attr : attrs.entrySet())
406                                {
407                                        buffer.append(' ')
408                                                .append(Strings.escapeMarkup(attr.getKey()))
409                                                .append("=\"")
410                                                .append(Strings.escapeMarkup(attr.getValue().toString()))
411                                                .append('"');
412                                }
413                        }
414                }
415
416                DebugSettings debugSettings = getApplication().getDebugSettings();
417                String componentPathAttributeName = debugSettings.getComponentPathAttributeName();
418                if (Strings.isEmpty(componentPathAttributeName) == false)
419                {
420                        CharSequence path = getPageRelativePath();
421                        path = Strings.replaceAll(path, "_", "__");
422                        path = Strings.replaceAll(path, ":", "_");
423                        buffer.append(' ').append(componentPathAttributeName).append("=\"")
424                                .append(path)
425                                .append("_input_")
426                                .append(index)
427                                .append('"');
428                }
429
430                buffer.append("/>");
431
432                labelPosition.after(buffer, idAttr, extraLabelAttributes, renderValue);
433
434                // Append option suffix
435                buffer.append(getSuffix(index, choice));
436        }
437
438        /**
439         * You may subclass this method to provide additional attributes to the &lt;label ..&gt; tag.
440         *
441         * @param index
442         *            index of the choice
443         * @param choice
444         *            the choice itself
445         * @return tag attribute name/value pairs.
446         */
447        protected IValueMap getAdditionalAttributesForLabel(int index, T choice)
448        {
449                return null;
450        }
451
452        /**
453         * You may subclass this method to provide additional attributes to the &lt;input ..&gt; tag.
454         * 
455         * @param index
456         * @param choice
457         * @return tag attribute name/value pairs.
458         */
459        protected IValueMap getAdditionalAttributes(final int index, final T choice)
460        {
461                return null;
462        }
463
464        /**
465         * Creates markup id for the input tag used to generate the checkbox for the element with the
466         * specified {@code id}.
467         * <p>
468         * NOTE It is useful to override this method if the contract for the generated ids should be
469         * fixed, for example in cases when the id generation pattern in this method is used to predict
470         * ids by some external javascript. If the contract is fixed in the user's code then upgrading
471         * wicket versions will guarantee not to break it should the default contract be changed at a
472         * later time.
473         * </p>
474         * 
475         * @param id
476         * @return markup id for the input tag
477         */
478        protected String getCheckBoxMarkupId(String id)
479        {
480                return getMarkupId() + '-' + getInputName() + '_' + id;
481        }
482}