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.Checkbox;
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>CheckboxRenderer</code> renders a
061: * {@link com.sun.rave.web.ui.component.Checkbox} component.
062: * </p>
063: * <h3>Encoding</h3>
064: * <p>
065: * The <code>CheckboxRenderer</code> renders a <code>Checkbox</code> as:
066: * <ul>
067: * <li> An INPUT element of type checkbox for each checkbox.
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: * checkbox are identified by java
081: * constants defined in the {@link ThemeStyles} class.
082: * </p>
083: * <ul>
084: * <li>CHECKBOX for the INPUT element</li>
085: * <li>CHECKBOX_DISABLED for the INPUT element of a disabled radio
086: * button</li>
087: * <li>CHECKBOX_LABEL for label component if a label is rendered</li>
088: * <li>CHECKBOX_LABEL_DISABLED for a label component of a disabled
089: * radio button, if a label is rendered</li>
090: * <li>CHECKBOX_IMAGE for an image component if an image is rendered</li>
091: * <li>CHECKBOX_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>Checkbox</code> component and the optional image and label
097: * components.</em>
098: * <p>
099: * For more details on the logic for actual renderering of HTML for
100: * a <code>Checkbox</code> component see the super class
101: * {@link com.sun.rave.web.ui.renderer.RbCbRendererBase}
102: * </p>
103: * <p>
104:
105:
106: * <h3>Decoding</h3>
107: * <p>
108: * If the INPUT element representing a checkbox is selected on the
109: * the client, the submitted request will contain a request parameter
110: * whose name is the value of the name attribute of the selected
111: * HTML INPUT element. The value of the request parameter will be the
112: * value of the value attribute of the selected HTML INPUT element.
113: * If more than one checkbox INPUT element has the same value for
114: * the name attribute, then the value of the request parameter will
115: * be an array of those INPUT element values.
116: * </p>
117: * <p>
118: * The component being decoded is selected if the component's
119: * <code>isDisabled</code> and <code>isReadOnly</code> methods
120: * return false and:
121: * </p>
122: * <ul>
123: * <li>a request parameter exists that is equal to its <code>name</code>
124: * property. If the <code>name</code> property is null, then a
125: * request parameter exists that is equal to its <code>clientId</code>
126: * property.
127: * <li/>
128: * </ul>
129: * <p>
130: * And
131: * </p>
132: * <ul>
133: * <li> the request parameter's value is an array that contains an element
134: * that is <code>String.equal</code> to the
135: * the component's <code>selectedValue</code> property, after conversion
136: * to a <code>String</code>, by calling
137: * <code>ConversionUtilities.convertValueToString</code>. If the component
138: * was encoded as a boolean control, then an element the request parameter's
139: * array value must be equal to the component's <code>clientId</code> property.
140: * </li>
141: * </ul>
142: * <p>
143: * If selected, a <code>String[1]</code> array is assigned as the component's
144: * submitted value where the single array element is the <code>String</code>
145: * version of the <code>selectedValue</code> property or "true" if the
146: * component was encoded as a boolean control.<br/>
147: * If not selected, a <code>String[0]</code> array is assigned as the
148: * component's submitted value or a <code>String[1]</code> array where the
149: * single array element is "false" if the component was encoded as a
150: * boolean control.
151: * </p>
152: * <p>
153: * If the component's <code>isDisabled</code> or <code>isReadOnly</code>
154: * methods return true no submitted value is assigned to the component,
155: * and results in a null submitted value implying the component
156: * was not submitted, and the state of the component is unchanged.
157: * </p>
158: */
159: public class CheckboxRenderer extends RbCbRendererBase {
160:
161: private final String MSG_COMPONENT_NOT_CHECKBOX = "CheckboxRenderer only renders Checkbox components.";
162:
163: /**
164: * Creates a new instance of CheckboxRenderer
165: */
166: public CheckboxRenderer() {
167: super ();
168: }
169:
170: /**
171: * <p>Decode the <code>Checkbox</code> selection.</p>
172: * <p>
173: * If the component's <code>isDisabled</code> and <code>isReadOnly</code>
174: * methods return false,
175: * If the value of the component's <code>name</code> property
176: * has been set, the value is used to match a request parameter.
177: * If it has not been set the component clientId is used to match
178: * a request parameter. If a match is found, and the value of the
179: * of the request parameter matches the <code>String</code> value of the
180: * component's <code>selectedValue</code> property, the
181: * radio button is selected. The component's submitted value is
182: * assigned a <code>String[1]</code> array where the single array
183: * element is the matching parameter value.
184: * </p>
185: * <p>
186: * If no matching request parameter or value is found, an instance of
187: * <code>String[0]</code> is assigned as the submitted value,
188: * meaning that this is a component was not selected.
189: * </p>
190: * <p>
191: * If the component was encoded as a boolean control the
192: * value of the matching request attribute will be the component's
193: * <code>clientId</code> property if selected. If selected the
194: * submitted value is <code>new String[] { "true" }</code>
195: * and <code>new String[] { "false" }</code> if not selected.
196: * </p>
197: * <p>
198: * It is the developer's responsibility to ensure the validity of the
199: * <code>name</code> property (the name attribute on the
200: * INPUT element) by ensuring that it is unique to the radio buttons
201: * for a given group within a form.
202: * </p>
203: *
204: * @param context FacesContext for the request we are processing.
205: * @param component The <code>Checkbox</code>
206: * component to be decoded.
207: */
208: public void decode(FacesContext context, UIComponent component) {
209:
210: // We need to know if the last state of the component before decoding
211: // this checkbox. This disabled check is not to determine
212: // if the checkbox was disabled on the client.
213: // We assume that the disabled state is in the same state as it was
214: // when this checkbox was last rendered.
215: // If the checkbox was disabled then it can not have changed on
216: // the client. We ignore the case that it might have been
217: // enabled in javascript on the client.
218: // This allows us to distinguish that no checkbox was selected.
219: // No checkboxes are selected when "isDisabled || isReadOnly -> false
220: // and no request parameters match the name attribute if part of a
221: // group or the clientId, if a single checkbox.
222: //
223: if (isDisabled(component) || isReadOnly(component)) {
224: return;
225: }
226:
227: // If there is a request parameter that that matches the
228: // name property, this component is one of the possible
229: // selections. We need to match the value of the parameter to the
230: // the component's value to see if this is the selected component,
231: // unless it is a group of Boolean checkboxes.
232: //
233: Checkbox checkbox = (Checkbox) component;
234: String name = checkbox.getName();
235: boolean inGroup = name != null;
236:
237: // If name not set look for clientId.
238: // Boolean checkboxes decode correctly when they are not
239: // in a group, since the submitted attribute
240: // value in the clientId and is unique for each check box.
241: //
242: if (name == null) {
243: name = component.getClientId(context);
244: }
245:
246: Map requestParameterValuesMap = context.getExternalContext()
247: .getRequestParameterValuesMap();
248:
249: // If a parameter with key == name does not exist, the component
250: // was not submitted. This only means that it is
251: // unchecked, since we already know that is it not in the
252: // map because is was readonly or disabled. (based on the
253: // server side state)
254: //
255: if (requestParameterValuesMap.containsKey(name)) {
256:
257: String[] newValues = (String[]) requestParameterValuesMap
258: .get(name);
259:
260: if (newValues != null || newValues.length != 0) {
261:
262: String selectedValueAsString = null;
263: Object selectedValue = checkbox.getSelectedValue();
264:
265: // We need to discern the case where the checkbox
266: // is part of a group and it is a boolean checkbox.
267: // If the checkbox is part of a group and it is a
268: // boolean checkbox then the submitted value contains the
269: // value of "component.getClientId()". If
270: // the value was not a unique value within the group
271: // of boolean checkboxes, then all will appear selected,
272: // since name will be the same for all the checkboxes
273: // and the submitted value would always be "true" and then
274: // every checkbox component in the group would decode
275: // as selected.
276: //
277: if (inGroup && selectedValue instanceof Boolean) {
278: selectedValueAsString = component
279: .getClientId(context);
280: // See if one of the values of the attribute
281: // is equal to the component id.
282: //
283: // Use the toString value of selectedValue even if
284: // Boolean in case it is FALSE and the application
285: // wants checked to be "FALSE == FALSE"
286: //
287: for (int i = 0; i < newValues.length; ++i) {
288: if (selectedValueAsString.equals(newValues[i])) {
289: ((UIInput) component)
290: .setSubmittedValue(new String[] { selectedValue
291: .toString() });
292:
293: return;
294: }
295: }
296: } else {
297: selectedValueAsString = ConversionUtilities
298: .convertValueToString(component,
299: selectedValue);
300:
301: for (int i = 0; i < newValues.length; ++i) {
302: if (selectedValueAsString.equals(newValues[i])) {
303: ((UIInput) component)
304: .setSubmittedValue(new String[] { newValues[i] });
305:
306: return;
307: }
308: }
309: }
310: // Not selected.
311: // But this results in an update to the model object
312: // of every checkbox, even if the value is the same.
313: // However only those that experience a state change issue
314: // a ValueChangeEvent.
315: //
316: ((UIInput) component).setSubmittedValue(new String[0]);
317: return;
318: }
319: }
320: // Not disabled and this checkbox is not selected.
321: //
322: ((UIInput) component).setSubmittedValue(new String[0]);
323:
324: return;
325: }
326:
327: /**
328: * Ensure that the component to be rendered is a Checkbox instance.
329: * Actual rendering occurs during <code>renderEnd</code>
330: *
331: * @param context FacesContext for the request we are processing.
332: * @param component UIComponent to be decoded.
333: */
334: public void renderStart(FacesContext context,
335: UIComponent component, ResponseWriter writer)
336: throws IOException {
337:
338: // Bail out if the component is not a Checkbox component.
339: if (!(component instanceof Checkbox)) {
340: throw new IllegalArgumentException(MessageUtil.getMessage(
341: context, BUNDLE, MSG_COMPONENT_NOT_CHECKBOX));
342: }
343: }
344:
345: /**
346: * CheckboxRenderer renders the entire Checkbox
347: * component within the renderEnd method.
348: * See {@link com.sun.rave.web.ui.renderer.RbCbRendererBase} for
349: * details on encoding a <code>Checkbox</code> component.
350: *
351: * @param context FacesContext for the request we are processing.
352: * @param component UIComponent to be decoded.
353: */
354: public void renderEnd(FacesContext context, UIComponent component,
355: ResponseWriter writer) throws IOException {
356:
357: Theme theme = ThemeUtilities.getTheme(context);
358: renderSelection(context, component, theme, writer, "checkbox");
359:
360: }
361:
362: /**
363: * Return true if the <code>component</code> is selected, false
364: * otherwise.
365: *
366: * @param context FacesContext for the request we are processing.
367: * @param component UIComponent to test for selected
368: */
369: protected boolean isSelected(FacesContext context,
370: UIComponent component) {
371: return ((Checkbox) component).isChecked();
372: }
373:
374: protected String[] styles = { ThemeStyles.CHECKBOX, /* INPUT */
375: ThemeStyles.CHECKBOX_DISABLED, /* INPUT_DIS */
376: ThemeStyles.CHECKBOX_LABEL, /* LABEL */
377: ThemeStyles.CHECKBOX_LABEL_DISABLED, /* LABEL_DIS */
378: ThemeStyles.CHECKBOX_IMAGE, /* IMAGE */
379: ThemeStyles.CHECKBOX_IMAGE_DISABLED, /* IMAGE_DIS */
380: ThemeStyles.CHECKBOX_SPAN, /* SPAN */
381: ThemeStyles.CHECKBOX_SPAN_DISABLED, /* SPAN_DIS */
382: };
383:
384: /**
385: * Return the style class name for the structural element indicated
386: * by <code>styleCode</code>
387: *
388: * @param theme The Theme for this request.
389: * @param styleCode identifies the style class for the element about
390: * to be rendered.
391: */
392: protected String getStyle(Theme theme, int styleCode) {
393: String style = null;
394: try {
395: style = theme.getStyleClass(styles[styleCode]);
396: } catch (Exception e) {
397: // Don't care
398: }
399: return style;
400: }
401: }
|