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