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;
020
021import org.apache.wicket.Component;
022import org.apache.wicket.IGenericComponent;
023import org.apache.wicket.WicketRuntimeException;
024import org.apache.wicket.markup.ComponentTag;
025import org.apache.wicket.markup.html.WebMarkupContainer;
026import org.apache.wicket.model.IModel;
027import org.apache.wicket.util.string.Strings;
028
029
030/**
031 * Component representing a single checkbox choice in a
032 * org.apache.wicket.markup.html.form.CheckGroup.
033 * 
034 * Must be attached to an <input type="checkbox" ... > markup.
035 * <p>
036 * STATELESS NOTES: By default this component cannot be used inside a stateless form. If it is
037 * desirable to use this inside a stateless form then
038 * <ul>
039 * <li>
040 * override #getValue() and return some stateless value to uniquely identify this radio (eg relative
041 * component path from group to this radio)</li>
042 * <li>
043 * override {@link #getStatelessHint()} and return <code>true</code></li>
044 * </ul>
045 * </p>
046 * 
047 * @see org.apache.wicket.markup.html.form.CheckGroup
048 * 
049 * @author Igor Vaynberg
050 * 
051 * @param <T>
052 *            The model object type
053 */
054public class Check<T> extends LabeledWebMarkupContainer implements IGenericComponent<T, Check<T>>
055{
056        private static final long serialVersionUID = 1L;
057
058        private static final String ATTR_DISABLED = "disabled";
059
060        /**
061         * page-scoped uuid of this check. this property must not be accessed directly, instead
062         * {@link #getValue()} must be used
063         */
064        private int uuid = -1;
065
066        private final CheckGroup<T> group;
067
068        /**
069         * @see WebMarkupContainer#WebMarkupContainer(String)
070         */
071        public Check(String id)
072        {
073                this(id, null, null);
074        }
075
076        /**
077         * @param id
078         * @param model
079         * @see WebMarkupContainer#WebMarkupContainer(String, IModel)
080         */
081        public Check(String id, IModel<T> model)
082        {
083                this(id, model, null);
084        }
085
086        /**
087         * @param id
088         * @param group
089         *            parent {@link CheckGroup} of this check
090         * @see WebMarkupContainer#WebMarkupContainer(String)
091         */
092        public Check(String id, CheckGroup<T> group)
093        {
094                this(id, null, group);
095        }
096
097        /**
098         * @param id
099         * @param model
100         * @param group
101         *            parent {@link CheckGroup} of this check
102         * @see WebMarkupContainer#WebMarkupContainer(String, IModel)
103         */
104        public Check(String id, IModel<T> model, CheckGroup<T> group)
105        {
106                super(id, model);
107                this.group = group;
108                setOutputMarkupId(true);
109        }
110
111
112        /**
113         * Form submission value used for the Html <code>value</code> attribute of the <code>input</code> tag.
114         * <p>
115         * If {@link Check}s are recreated on each render of their {@link CheckGroup}, this method should
116         * be overridden to return a 'stable' value, otherwise its selection will be lost after a {@link Form}
117         * was submitted and resulted in {@link Form#hasError()}.
118         * 
119         * @return input value
120         */
121        public String getValue()
122        {
123                if (uuid < 0)
124                {
125                        uuid = getPage().getAutoIndex();
126                }
127                return "check" + uuid;
128        }
129
130        @SuppressWarnings("unchecked")
131        protected CheckGroup<T> getGroup()
132        {
133                CheckGroup<T> group = this.group;
134                if (group == null)
135                {
136                        group = findParent(CheckGroup.class);
137                        if (group == null)
138                        {
139                                throw new WicketRuntimeException("Check component [" + getPath() +
140                                        "] cannot find its parent CheckGroup");
141                        }
142                }
143                return group;
144        }
145
146        /**
147         * @see Component#onComponentTag(ComponentTag)
148         * @param tag
149         *            the abstraction representing html tag of this component
150         */
151        @Override
152        protected void onComponentTag(final ComponentTag tag)
153        {
154                // Default handling for component tag
155                super.onComponentTag(tag);
156
157                // must be attached to <input type="checkbox" .../> tag
158                checkComponentTag(tag, "input");
159                checkComponentTagAttribute(tag, "type", "checkbox");
160
161                CheckGroup<?> group = getGroup();
162
163                final String uuid = getValue();
164
165                // assign name and value
166                tag.put("name", group.getInputName());
167                tag.put("value", uuid);
168
169                // check if the model collection of the group contains the model object.
170                // if it does check the check box.
171                Collection<?> collection = (Collection<?>)group.getDefaultModelObject();
172
173                // check for npe in group's model object
174                if (collection == null)
175                {
176                        throw new WicketRuntimeException("CheckGroup [" + group.getPath() +
177                                "] contains a null model object, must be an object of type java.util.Collection");
178                }
179
180                if (group.hasRawInput())
181                {
182                        final String raw = group.getRawInput();
183                        if (!Strings.isEmpty(raw))
184                        {
185                                final String[] values = raw.split(FormComponent.VALUE_SEPARATOR);
186                                for (String value : values)
187                                {
188                                        if (uuid.equals(value))
189                                        {
190                                                tag.put("checked", "checked");
191                                        }
192                                }
193                        }
194                }
195                else if (collection.contains(getDefaultModelObject()))
196                {
197                        tag.put("checked", "checked");
198                }
199
200                if (!isActionAuthorized(ENABLE) || !isEnabledInHierarchy() || !group.isEnabledInHierarchy())
201                {
202                        tag.put(ATTR_DISABLED, ATTR_DISABLED);
203                }
204
205        }
206
207        /**
208         * The value will be made available to the validator property by means of ${label}. It does not
209         * have any specific meaning to Check itself.
210         * 
211         * @param labelModel
212         * @return this for chaining
213         */
214        @Override
215        public Check<T> setLabel(IModel<String> labelModel)
216        {
217                super.setLabel(labelModel);
218                return this;
219        }
220
221        @Override
222        protected boolean getStatelessHint()
223        {
224                // because this component uses uuid field it cannot be stateless
225                return false;
226        }
227}