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}