001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package com.sun.rave.web.ui.renderer;
042:
043: import java.io.IOException;
044: import java.util.Map;
045:
046: import javax.faces.component.UIComponent;
047: import javax.faces.component.UIInput;
048: import javax.faces.context.FacesContext;
049: import javax.faces.context.ResponseWriter;
050:
051: import com.sun.rave.web.ui.component.RadioButton;
052: import com.sun.rave.web.ui.theme.Theme;
053: import com.sun.rave.web.ui.theme.ThemeStyles;
054: import com.sun.rave.web.ui.util.ConversionUtilities;
055: import com.sun.rave.web.ui.util.MessageUtil;
056: import com.sun.rave.web.ui.util.ThemeUtilities;
057:
058: /**
059: * <p>
060: * The <code>RadioButtonRenderer</code> renders a
061: * {@link com.sun.rave.web.ui.component.RadioButton} component.
062: * </p>
063: * <h3>Encoding</h3>
064: * <p>
065: * The <code>RadioButtonRenderer</code> renders a <code>RadioButton</code> as:
066: * <ul>
067: * <li> An INPUT element of type radio for each radio button.
068: * </li>
069: * <li> An optional image. The component rendered for this feature is obtained
070: * from a call to <code>getImageComponent()</code> on the component being
071: * rendered. </li>
072: * <li> An optional label. The component rendered for this feature is obtained
073: * from a call to <code>getLabelComponent()</code> on the component being
074: * rendered. </li>
075: * </li>
076: * </ul>
077: * </p>
078: * <p>
079: * The CSS selectors for the elements and components that comprise a
080: * radio button are identified by java
081: * constants defined in the {@link ThemeStyles} class.
082: * </p>
083: * <ul>
084: * <li>RADIOBUTTON for the INPUT element</li>
085: * <li>RADIOBUTTON_DISABLED for the INPUT element of a disabled radio
086: * button</li>
087: * <li>RADIOBUTTON_LABEL for label component if a label is rendered</li>
088: * <li>RADIOBUTTON_LABEL_DISABLED for a label component of a disabled
089: * radio button, if a label is rendered</li>
090: * <li>RADIOBUTTON_IMAGE for an image component if an image is rendered</li>
091: * <li>RADIOBUTTON_IMAGE_DISABLED for an image component of a disabled
092: * radio button if an image is rendered.</li>
093: * </ul>
094: * <em>Note that these selectors are appended to any existing selectors
095: * that may already exist on the <code>styleClass</code> property of the
096: * <code>RadioButton</code> component and the optional image and label
097: * components.</em>
098: * <p>
099: * For more details on the encoding the
100: * <code>RadioButton</code> component see the super class
101: * {@link com.sun.rave.web.ui.renderer.RbCbRendererBase}
102: * </p>
103: * <p>
104: * <h3>Decoding</h3>
105: * <p>
106: * If the INPUT element representing a radio button is selected on the
107: * the client, the submitted request will contain a request parameter
108: * whose name is the value of the name attribute of the selected
109: * HTML INPUT element. The value of the request parameter will be the
110: * value of the value attribute of the selected HTML INPUT element.
111: * </p>
112: * <p>
113: * The component being decoded is selected if the component's
114: * <code>isDisabled</code> and <code>isReadOnly</code> methods
115: * return false and:
116: * </p>
117: * <ul>
118: * <li>a request parameter exists that is equal to its <code>name</code>
119: * property. If the <code>name</code> property is null, then a
120: * request parameter exists that is equal to its <code>clientId</code>
121: * property.
122: * <li/>
123: * </ul>
124: * <p>
125: * And
126: * </p>
127: * <ul>
128: * <li>the request parameter's value is <code>String.equal</code> to the
129: * the component's <code>selectedValue</code> property, after conversion
130: * to a <code>String</code>, by calling
131: * <code>ConversionUtilities.convertValueToString</code>. If the component
132: * was encoded as a boolean control, then the request parameter's value
133: * must be equal to the component's <code>clientId</code> property.
134: * </li>
135: * </ul>
136: * <p>
137: * If selected, a <code>String[1]</code> array is assigned as the component's
138: * submitted value where the single array element is the <code>String</code>
139: * version of the <code>selectedValue</code> property or "true" if the
140: * component was encoded as a boolean control.<br/>
141: * If not selected, a <code>String[0]</code> array is assigned as the
142: * component's submitted value or a <code>String[1]</code> array where the
143: * single array element is "false" if the component was encoded as a
144: * boolean control.
145: * </p>
146: * <p>
147: * If the component's <code>isDisabled</code> or <code>isReadOnly</code>
148: * methods return true no submitted value is assigned to the component,
149: * and results in a null submitted value implying the component
150: * was not submitted, and the state of the component is unchanged.
151: * <p>
152: * Since the <code>RadioButtonRenderer</code> only renders a single
153: * <code>RadioButton</code> component it cannot enforce that at least
154: * one radio button should be selected among a group of <code>RadioButton</code>
155: * components with the same <code>name</code> property.
156: * </p>
157: * <p>
158: * If the <code>RadioButton</code> is selected, the <code>selected<code>
159: * property will be the same value as the <code>selectedValue</code>
160: * property. If more than one <code>RadioButton</code> component is
161: * encoded with the same <code>name</code> property and more than one
162: * <code>RadioButton</code>'s is selectred,
163: * the last selected <code>RadioButton</code> component that is encoded
164: * will be appear as checked in the HTML page. Subsequently during the
165: * next submit, only the checked <code>RadioButton</code> component
166: * will be selected.
167: * </p>
168: */
169: public class RadioButtonRenderer extends RbCbRendererBase {
170:
171: private final String MSG_COMPONENT_NOT_RADIOBUTTON = "RadioButtonRenderer only renders RadioButton components.";
172:
173: /**
174: * Creates a new instance of RadioButtonRenderer
175: */
176: public RadioButtonRenderer() {
177: super ();
178: }
179:
180: /**
181: * <p>Decode the <code>RadioButton</code> selection.</p>
182: * <p>
183: * If the value of the component's <code>name</code> property
184: * has been set, the value is used to match a request parameter.
185: * If it has not been set the component clientId is used to match
186: * a request parameter. If a match is found, and the value of the
187: * of the request parameter matches the <code>String</code> value of the
188: * component's <code>selectedValue</code> property, the
189: * radio button is selected. The component's submitted value is
190: * assigned a <code>String[1]</code> array where the single array
191: * element is the matching parameter value.
192: * </p>
193: * <p>
194: * If no matching request parameter or value is found, an instance of
195: * <code>String[0]</code> is assigned as the submitted value,
196: * meaning that this is a component was not selected.
197: * </p>
198: * <p>
199: * If the component was encoded as a boolean control the
200: * value of the matching request attribute will be the component's
201: * <code>clientId</code> property if selected. If selected the
202: * submitted value is <code>new String[] { "true" }</code>
203: * and <code>new String[] { "false" }</code> if not selected.
204: * </p>
205: * <p>
206: * It is the developer's responsibility to ensure the validity of the
207: * <code>name</code> property (the name attribute on the
208: * INPUT element) by ensuring that it is unique to the radio buttons
209: * for a given group within a form.
210: * </p>
211: *
212: * @param context FacesContext for the request we are processing.
213: * @param component The <code>RadioButton</code>
214: * component to be decoded.
215: */
216: public void decode(FacesContext context, UIComponent component) {
217:
218: // We need to know the last state of the component before decoding
219: // this radio button. This disabled check is not to determine
220: // if the radio button was disabled on the client.
221: // We assume that the disabled state is in the same state as it was
222: // when this radio button was last rendered.
223: // If the radio button was disabled then it can not have changed on
224: // the client. We ignore the case that it might have been
225: // enabled in javascript on the client.
226: // This allows us to distinguish that no radio button was selected.
227: // No radio buttons are selected when "isDisabled || isReadOnly -> false
228: // and no request parameters match the name attribute if part of a
229: // group or the clientId if a single radio button.
230: //
231: if (isDisabled(component) || isReadOnly(component)) {
232: return;
233: }
234: // If there is a request parameter that that matches the
235: // name property, this component is one of the possible
236: // selections. We need to match the value of the parameter to the
237: // the component's value to see if this is the selected component.
238: //
239: RadioButton radioButton = (RadioButton) component;
240: String name = radioButton.getName();
241: boolean inGroup = name != null;
242:
243: // If name is null use the clientId.
244: //
245: if (name == null) {
246: name = component.getClientId(context);
247: }
248:
249: Map requestParameterMap = context.getExternalContext()
250: .getRequestParameterMap();
251:
252: // The request parameter map contains the INPUT element
253: // name attribute value as a parameter. The value is the
254: // the "selectedValue" value of the RadioButton component.
255: //
256: if (requestParameterMap.containsKey(name)) {
257:
258: String newValue = (String) requestParameterMap.get(name);
259:
260: // We need to discern the case where the radio button
261: // is part of a group and it is a boolean radio button.
262: // If the radio button is part of a group and it is a
263: // boolean radio button then the submitted value contains the
264: // value of "component.getClientId()". If
265: // the value was not a unique value within the group
266: // of boolean radio buttons, then all will appear selected,
267: // since name will be the same for all the radio buttons
268: // and the submitted value would always be "true" and then
269: // every radio button component in the group would decode
270: // as selected. Due to the HTML implementation of radio
271: // buttons, only the last radio button will appear selected.
272: //
273: Object selectedValue = radioButton.getSelectedValue();
274: String selectedValueAsString = null;
275:
276: if (inGroup && selectedValue instanceof Boolean) {
277: selectedValueAsString = component.getClientId(context);
278: // Use the toString value of selectedValue even if
279: // it is a Boolean control, in case the application
280: // wants "FALSE == FALSE" to mean checked.
281: //
282: if (selectedValueAsString.equals(newValue)) {
283: ((UIInput) component)
284: .setSubmittedValue(new String[] { selectedValue
285: .toString() });
286: return;
287: }
288: } else {
289: selectedValueAsString = ConversionUtilities
290: .convertValueToString(component, selectedValue);
291: if (selectedValueAsString.equals(newValue)) {
292: ((UIInput) component)
293: .setSubmittedValue(new String[] { newValue });
294: return;
295: }
296: }
297: // Not selected possibly deselected.
298: //
299: ((UIInput) component).setSubmittedValue(new String[0]);
300: }
301: return;
302: }
303:
304: /**
305: * Ensure that the component to be rendered is a RadioButton instance.
306: * Actual rendering occurs during <code>renderEnd</code>
307: *
308: * @param context FacesContext for the request we are processing.
309: * @param component UIComponent to be decoded.
310: */
311: public void renderStart(FacesContext context,
312: UIComponent component, ResponseWriter writer)
313: throws IOException {
314:
315: // Bail out if the component is not a RadioButton component.
316: // This message should be logged.
317: //
318: if (!(component instanceof RadioButton)) {
319: throw new IllegalArgumentException(
320: MSG_COMPONENT_NOT_RADIOBUTTON);
321: }
322: }
323:
324: /**
325: * RadioButtonRenderer renders the entire RadioButton
326: * component within the renderEnd method.
327: *
328: * @param context FacesContext for the request we are processing.
329: * @param component UIComponent to be decoded.
330: */
331: public void renderEnd(FacesContext context, UIComponent component,
332: ResponseWriter writer) throws IOException {
333:
334: Theme theme = ThemeUtilities.getTheme(context);
335: renderSelection(context, component, theme, writer, "radio");
336:
337: }
338:
339: /**
340: * Return true if the <code>component</code> is selected, false
341: * otherwise.
342: *
343: * @param context FacesContext for the request we are processing.
344: * @param component UIComponent to test for selected.
345: */
346: protected boolean isSelected(FacesContext context,
347: UIComponent component) {
348: return ((RadioButton) component).isChecked();
349: }
350:
351: protected String[] styles = { ThemeStyles.RADIOBUTTON, /* INPUT */
352: ThemeStyles.RADIOBUTTON_DISABLED, /* INPUT_DIS */
353: ThemeStyles.RADIOBUTTON_LABEL, /* LABEL */
354: ThemeStyles.RADIOBUTTON_LABEL_DISABLED, /* LABEL_DIS */
355: ThemeStyles.RADIOBUTTON_IMAGE, /* IMAGE */
356: ThemeStyles.RADIOBUTTON_IMAGE_DISABLED, /* IMAGE_DIS */
357: ThemeStyles.RADIOBUTTON_SPAN, /* SPAN */
358: ThemeStyles.RADIOBUTTON_SPAN_DISABLED /* SPAN_DIS */
359: };
360:
361: /**
362: * Return the style class name for the structural element indicated
363: * by <code>styleCode</code>
364: *
365: * @param theme The Theme for this request.
366: * @param styleCode identifies the style class for the element about
367: * to be rendered.
368: */
369: protected String getStyle(Theme theme, int styleCode) {
370: String style = null;
371: try {
372: style = theme.getStyleClass(styles[styleCode]);
373: } catch (Exception e) {
374: // Don't care
375: }
376: return style;
377: }
378: }
|