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:
045: import javax.faces.component.UIComponent;
046: import javax.faces.context.FacesContext;
047: import javax.faces.context.ResponseWriter;
048: import javax.faces.convert.ConverterException;
049:
050: import com.sun.rave.web.ui.component.ImageComponent;
051: import com.sun.rave.web.ui.component.Label;
052: import com.sun.rave.web.ui.component.RadioButton;
053: import com.sun.rave.web.ui.component.RbCbSelector;
054: import com.sun.rave.web.ui.component.util.Util;
055: import com.sun.rave.web.ui.theme.Theme;
056: import com.sun.rave.web.ui.util.ConversionUtilities;
057: import com.sun.rave.web.ui.util.RenderingUtilities;
058:
059: /**
060: * <p>
061: * The <code>RbCbRendererBase</code> class is the abstract base class for
062: * {@link com.sun.rave.web.ui.renderer.RadioButtonRenderer} and
063: * {@link com.sun.rave.web.ui.renderer.CheckboxRenderer}.
064: * </p>
065: * <p>
066: * <code>RbCbRendererBase</code> provides encoding functionality for
067: * the <code>RadioButtonRenderer</code> and <code>CheckboxRenderer</code>.
068: * This includes an implementation of <code>getConvertedValue</code>, and
069: * a method called <code>renderSelection</code> which a subclass calls to render
070: * either a <code>Checkbox</code> or a <code>RadioButton</code> component
071: * at the appropriate time.<br/>
072: * The renderer subclass must implement
073: * <p>
074: * <ul>
075: * <li><code>isSelected</code> in order for this class to generically render
076: * either component</li>
077: * <li><code>getStyle</code> so the specific subclass can specify the
078: * appropriate <code>ThemeStyle</code> constants.</li>
079: * </ul>
080: * </p>
081: * <h3>Decoding</h3>
082: * <p>
083: * See {@link com.sun.rave.web.ui.renderer.RadioButtonRenderer} and
084: * {@link com.sun.rave.web.ui.renderer.CheckboxRenderer} for details on
085: * decoding requests.
086: * </p>
087: * <h3>Encoding</h3>
088: * <p>
089: * The renderer emits the following HTML elements.
090: * <ul>
091: * <li> An INPUT element of type specified by the subclass in
092: * <code>renderSelection</code>
093: * </li>
094: * <li> An optional {@link com.sun.rave.web.ui.component.ImageComponent}
095: * component is rendered for each INPUT element
096: * </li>
097: * <li> An optional {@link com.sun.rave.web.ui.component.Label} component
098: * is rendered for each INPUT element
099: * </li>
100: * </ul>
101: * </p>
102: * <p>
103: * The ID attributes for HTML elements are constructed as follows,
104: * where <cid> is the <code>clientId</code> of the
105: * component being rendered.
106: * <p>
107: * <lo>
108: * <li> <cid> for the INPUT element
109: * </li>
110: * <li> <cid>_image for the image component
111: * </li>
112: * <li> <cid>_label for the label component
113: * </li>
114: * </lo>
115: * <h1>Encoding the INPUT element</h1>
116: * <p>
117: * If the <code>name</code> property of the component is <code>null</code>
118: * the name attribute of the INPUT element will be set to the
119: * value of the component's <code>clientId</code> and the control will
120: * not be part of a group, and behave as an individual control.<br/>
121: * If the <code>name</code> property is not <code>null</code> then its
122: * value is used as the value of the name attribute of the HTML INPUT
123: * element and the control will behave as part of a group.
124: * </p>
125: * <p>
126: * The <code>ConversionUtilities.getValueAsString</code> method is called with
127: * the value of the component's <code>selectedValue</code> property
128: * and the result is used as the value of the HTML INPUT element's
129: * value attribute. The <code>String</code> value that is returned may be
130: * the actual value of the <code>selectedValue</code> property or
131: * the result of a conversion of a developer defined object value
132: * to a <code>String</code> or "true" if the <code>selectedValue</code>
133: * property was null or never assigned a value.<em>The components
134: * {@link com.sun.rave.web.ui.component.RadioButton} and
135: * {@link com.sun.rave.web.ui.component.Checkbox} implement the behavior
136: * of returning "true" when <code>selectedValue</code> is null. Therefore
137: * if the component parameter is not one of these classes then this behavior
138: * may vary.</em>
139: * </p>
140: * <p>
141: * If <code>isSelected</code> returns <code>true</code> the
142: * the value of the HTML INPUT element's checked attribute is set to "checked",
143: * otherwise the checked attribute is not rendered.
144: * </p>
145: * <p>
146: * The following component properties are obtained and rendered in turn and
147: * equivalent to the HTML INPUT element's attributes of the same name, but
148: * rendered in all lowercase.
149: * <ul>
150: * <li>disabled</li>
151: * <li>readOnly</li>
152: * <li>tabIndex</li>
153: * <li>style</li>
154: * </lu>
155: * The component's <code>toolTip</code> property if not null is rendered as the
156: * value of the HTML INPUT element's title attribute.<br/>
157: * The HTML INPUT element's class attribute is set to the
158: * component's <code>styleClass</code> property appended with
159: * the value returned from a call to the <code>getStyle</code> method.
160: * </p>
161: * <h1>Rendering the image component</h1>
162: * <p>
163: * The renderer calls the component's <code>getImageComponent</code>
164: * method to obtain an instance of a
165: * {@link com.sun.rave.web.ui.component.ImageComponent} component. If
166: * null is returned, no image will appear with the control.
167: * If a non null instance is returned, the appropriate disabled or
168: * enabled style class returned by <code>getStyle</code>
169: * is appended to the image's <code>styleClass</code> property.
170: * <code>RenderingUtilities.renderComponent</code> is called to render
171: * the component.<br/>
172: * </p>
173: * <p>
174: * If an image is rendered it appears to the immediate left of the
175: * control.
176: * </p>
177: * <h1>Encoding the label component</h1>
178: * <p>
179: * The renderer calls the component's <code>getLabelComponent</code>
180: * method to obtain an instance of a
181: * {@link com.sun.rave.web.ui.component.Label} component. If
182: * null is returned, no label will appear with the control.
183: * If a non null instance is returned, the appropriate disabled or
184: * enabled style class returned by <code>getStyle</code>
185: * is appended to the label's <code>styleClass</code> property.
186: * <code>RenderingUtilities.renderComponent</code> is called to render
187: * the component.<br/>
188: * </p>
189: */
190: abstract class RbCbRendererBase extends AbstractRenderer {
191:
192: /**
193: * The define constant indicating the style class
194: * for an INPUT element.
195: */
196: protected final static int INPUT = 0;
197: /**
198: * The define constant indicating the style class
199: * for a disabled INPUT element.
200: */
201: protected final static int INPUT_DIS = 1;
202: /**
203: * The define constant indicating the style class
204: * for the LABEL element.
205: */
206: protected final static int LABEL = 2;
207: /**
208: * The define constant indicating the style class
209: * for a disabled LABEL element.
210: */
211: protected final static int LABEL_DIS = 3;
212: /**
213: * The define constant indicating the style class
214: * for the IMG element.
215: */
216: protected final static int IMAGE = 4;
217: /**
218: * The define constant indicating the style class
219: * for a disabled IMG element.
220: */
221: protected final static int IMAGE_DIS = 5;
222: /**
223: * The define constant indicating the style class for
224: * for the containing span element
225: */
226: protected final static int SPAN = 6;
227: /**
228: * The define constant indicating the style class for
229: * for the containing span element, when disabled.
230: */
231: protected final static int SPAN_DIS = 7;
232:
233: // Collect most NOI18N in one place
234: //
235: private static final String INPUT_ELEM = "input"; //NOI18N
236: private static final String SPAN_ELEM = "span"; //NOI18N
237:
238: private static final String CHECKED_ATTR = "checked"; //NOI18N
239: private static final String DISABLED_ATTR = "disabled"; //NOI18N
240: private static final String CLASS_ATTR = "class"; //NOI18N
241: private static final String ID_ATTR = "id"; //NOI18N
242: private static final String NAME_ATTR = "name"; //NOI18N
243: private static final String READONLY_ATTR = "readonly"; //NOI18N
244: private static final String READONLY_CC_ATTR = "readOnly"; //NOI18N
245: private static final String STYLE_ATTR = "style"; //NOI18N
246: private static final String STYLECLASS_ATTR = "styleClass"; //NOI18N
247: private static final String TABINDEX_ATTR = "tabindex"; //NOI18N
248: private static final String TABINDEX_CC_ATTR = "tabIndex"; //NOI18N
249: private static final String TITLE_ATTR = "title"; //NOI18N
250: private static final String TOOLTIP_ATTR = "toolTip"; //NOI18N
251: private static final String TYPE_ATTR = "type"; //NOI18N
252: private static final String VALUE_ATTR = "value"; //NOI18N
253:
254: private static final String SPAN_SUFFIX = "_span"; //NOI18N
255:
256: /**
257: * <p>The list of attribute names for Rb and Cb
258: *
259: **/
260: public static final String RBCB_EVENTS_ATTRIBUTES[] = { "onFocus",
261: "onBlur", "onClick", "onDblClick",
262: "onChange", // NOI18N
263: "onMouseDown", "onMouseUp", "onMouseOver", "onMouseMove",
264: "onMouseOut", // NOI18N
265: "onKeyPress", "onKeyDown", "onKeyUp", // NOI18N
266: };
267:
268: /**
269: * Creates a new instance of RbCbRendererBase
270: */
271: public RbCbRendererBase() {
272: super ();
273: }
274:
275: /**
276: * The getStlye method is implemented by subclasses
277: * to return the actual CSS style class name for
278: * the given structural element of the rendered component.
279: *
280: * @param theme Theme for the request we are processing.
281: * @param styleCode one of the previously defined constants.
282: */
283: protected abstract String getStyle(Theme theme, int styleCode);
284:
285: /**
286: * Implemented in the subclass to determine if the <code>item</code>
287: * is the currently selected control.
288: *
289: * @param Object selectedValue contol value.
290: * @param currentValue the value of the currently selected control.
291: */
292: protected abstract boolean isSelected(FacesContext context,
293: UIComponent component);
294:
295: /*
296: * <p>
297: * Decode the <code>RadioButton</code> or <code>Checkbox</code> selection.
298: * If the value of the component's <code>name</code> property
299: * has been set, the value is used to match a request parameter.
300: * If it has not been set the component clientId is used to match
301: * a request parameter. If a match is found, and the value of the
302: * of the request parameter matches the value of the
303: * <code>selectedValue</code> component property, the
304: * value of the <code>selectedValue</code> property is set
305: * as the submitted value, as a one element array containing this value.
306: * </p>
307: * <p>
308: * In the case of a <code>Checkbox</code> component where the
309: * check box is part of a group, the value of the request parameter
310: * may contain more than one value. If the value of the component's
311: * <code>selectedValue</code> property is among the returned values
312: * then the value of the <code>selectedValue</code> property is
313: * set as the submitted value in a one element array.
314: * In the case of a <code>RadioButton</code>
315: * there is always only one element selected when part of a group.
316: * </p>
317: * <p>
318: * If no matching request parameter is found, an instance of
319: * <code>String[0]</code> is assigned as the submitted value,
320: * meaning that this is a component was not selected.
321: * </p>
322: *
323: * @param context FacesContext for the request we are processing.
324: * @param component The <code>RadioButton</code> or <code>Checkbox</code>
325: * component to be decoded.
326: */
327:
328: // This should probably be in RadioButtonRenderer.
329: //
330: /**
331: * Render the child components of this UIComponent, following the rules
332: * described for encodeBegin() to acquire the appropriate value to be
333: * rendered. This method will only be called if the rendersChildren property
334: * of this component is true.
335: *
336: * @param context FacesContext for the request we are processing.
337: * @param component UIComponent to be decoded.
338: */
339: public void encodeChildren(FacesContext context,
340: UIComponent component) throws IOException {
341: }
342:
343: /**
344: * Render a radio button or a checkbox.
345: *
346: * @param context FacesContext for the request we are processing.
347: * @param component UIComponent to be decoded.
348: * @param writer <code>ResponseWriter</code> to which the HTML will
349: * be output
350: * @param type the INPUT element type attribute value.
351: */
352: protected void renderSelection(FacesContext context,
353: UIComponent component, Theme theme, ResponseWriter writer,
354: String type) throws IOException {
355:
356: // Contain the radio button components within a span element
357: // assigning the style and styleClass attribute to its
358: // style and class attributes.
359: //
360: writer.startElement(SPAN_ELEM, component);
361: writer.writeAttribute(ID_ATTR, component.getClientId(context)
362: .concat(SPAN_SUFFIX), null);
363:
364: // Transfer explicit style attribute value to the span's style
365: //
366: String prop = (String) ((RbCbSelector) component).getStyle();
367: if (prop != null) {
368: writer.writeAttribute(STYLE_ATTR, prop, STYLE_ATTR);
369: }
370:
371: // Merge the standard style class with the styleClass
372: // attribute
373: //
374: String styleClass = getStyle(theme, ((RbCbSelector) component)
375: .isDisabled() ? SPAN_DIS : SPAN);
376: styleClass = RenderingUtilities.getStyleClasses(context,
377: component, styleClass);
378: if (styleClass != null) {
379: writer.writeAttribute(CLASS_ATTR, styleClass, null);
380: }
381:
382: renderInput(context, component, theme, writer, type);
383: renderImage(context, component, theme, writer);
384: renderLabel(context, component, theme, writer);
385:
386: writer.endElement(SPAN_ELEM);
387: }
388:
389: /**
390: * Called from renderSelection to render an INPUT element of type
391: * <code>type</code> for the specified <code>component</code>.
392: *
393: * @param context FacesContext for the request we are processing.
394: * @param component UIComponent to be rendered.
395: * @param writer <code>ResponseWriter</code> to which the HTML will
396: * be output
397: * @param type the INPUT element type attribute value.
398: */
399: protected void renderInput(FacesContext context,
400: UIComponent component, Theme theme, ResponseWriter writer,
401: String type) throws IOException {
402:
403: RbCbSelector rbcbSelector = (RbCbSelector) component;
404:
405: String componentId = component.getClientId(context);
406:
407: writer.startElement(INPUT_ELEM, component);
408: writer.writeAttribute(TYPE_ATTR, type, null);
409:
410: // Set the control name to the radiobutton group id
411: // and create a unique id from the radiobutton group id.
412: //
413: writer.writeAttribute(ID_ATTR, componentId, ID_ATTR);
414:
415: // If name is not set use the component's clientId
416: //
417: boolean inGroup = true;
418: String prop = rbcbSelector.getName();
419: if (prop == null) {
420: prop = componentId;
421: inGroup = false;
422: }
423: writer.writeAttribute(NAME_ATTR, prop, NAME_ATTR);
424:
425: // If the selectedValue is Boolean and the component is part
426: // of a group, "name != null", then set the value of the value
427: // attribute to "component.getClientId()".
428: //
429: Object selectedValue = rbcbSelector.getSelectedValue();
430: prop = ConversionUtilities.convertValueToString(component,
431: selectedValue);
432:
433: // Need to check immediate conditions
434: // submittedValue will be non null if immediate is true on
435: // some action component or a component on the page was invalid
436: //
437: String[] subValue = (String[]) rbcbSelector.getSubmittedValue();
438: if (subValue == null) {
439: Object selected = rbcbSelector.getSelected();
440: if (isSelected(context, component)) {
441: writer.writeAttribute(CHECKED_ATTR, CHECKED_ATTR, null);
442: }
443: // A component can't be selected if "getSelected" returns null
444: //
445: // Remember that the rendered value was null.
446: //
447: ConversionUtilities.setRenderedValue(component, selected);
448: } else
449: //
450: // if the submittedValue is a 0 length array or the
451: // first element is "" then the control is unchecked.
452: //
453: if (subValue.length != 0 && subValue[0].length() != 0) {
454: // The submitted value has the String value of the
455: // selectedValue property. Just compare the submittedValue
456: // to it to determine if it is checked.
457: //
458: // Assume that the RENDERED_VALUE_STATE is the same
459: // as the last rendering.
460: //
461: if (prop != null && prop.equals(subValue[0])) {
462: writer.writeAttribute(CHECKED_ATTR, CHECKED_ATTR, null);
463: }
464: }
465:
466: // If not ingroup prop has String version of selectedValue
467: //
468: boolean booleanControl = selectedValue instanceof Boolean;
469: if (inGroup && booleanControl) {
470: prop = componentId;
471: }
472: writer.writeAttribute(VALUE_ATTR, prop, null);
473:
474: boolean readonly = rbcbSelector.isReadOnly();
475: if (readonly) {
476: writer.writeAttribute(READONLY_ATTR, READONLY_ATTR,
477: READONLY_CC_ATTR);
478: }
479:
480: String styleClass = null;
481: boolean disabled = rbcbSelector.isDisabled();
482: if (disabled) {
483: writer.writeAttribute(DISABLED_ATTR, DISABLED_ATTR,
484: DISABLED_ATTR);
485: styleClass = getStyle(theme, INPUT_DIS);
486: } else {
487: styleClass = getStyle(theme, INPUT);
488: }
489:
490: prop = rbcbSelector.getToolTip();
491: if (prop != null) {
492: writer.writeAttribute(TITLE_ATTR, prop, TOOLTIP_ATTR);
493: }
494:
495: // Output the component's event attributes
496: // Probably want the 'no auto submit javascript at some point'
497: //
498: addStringAttributes(context, component, writer,
499: RBCB_EVENTS_ATTRIBUTES);
500:
501: int tabIndex = rbcbSelector.getTabIndex();
502: if (tabIndex > 0 && tabIndex < 32767) {
503: writer.writeAttribute(TABINDEX_ATTR, String
504: .valueOf(tabIndex), TABINDEX_CC_ATTR);
505: }
506:
507: writer.endElement(INPUT_ELEM);
508: }
509:
510: /**
511: * Called from renderSelection to render an IMG element for the
512: * specified <code>item</code>control.
513: *
514: * @param context FacesContext for the request we are processing.
515: * @param component UIComponent to be decoded.
516: * @param writer <code>ResponseWriter</code> to which the HTML will
517: * be output
518: */
519: protected void renderImage(FacesContext context,
520: UIComponent component, Theme theme, ResponseWriter writer)
521: throws IOException {
522:
523: UIComponent imageComponent = getImageComponent(context,
524: component, theme);
525: if (imageComponent != null) {
526: RenderingUtilities.renderComponent(imageComponent, context);
527: }
528: }
529:
530: // There is a serious issue creating child components for
531: // renderering purposes. They must be updated to reflect
532: // the application state. This can happen in two ways.
533: // Literal property values in the current component being rendered
534: // that are intended for the child component may have been
535: // changed by the application.
536: // Properties intended for the child component may be binding
537: // expressions in which case the value binding must be
538: // assigned to the property in the child component.
539: // Since both these values must be updated in the child component
540: // since the application can change them at any time, it makes
541: // sense to just always update the child with the value obtained
542: // from the accessor for the property vs. obtaining and assigning
543: // the ValueBinding.
544: //
545: // Also child creation should occur in the component and
546: // the process of this creation should produce a facet
547: // so that the renderer just asks for the facet.
548: // It may orginate from the component or the developer.
549: //
550:
551: private UIComponent getImageComponent(FacesContext context,
552: UIComponent component, Theme theme) throws IOException {
553:
554: RbCbSelector rbcbComponent = (RbCbSelector) component;
555: ImageComponent imageComponent = (ImageComponent) rbcbComponent
556: .getImageComponent();
557: if (imageComponent == null) {
558: return null;
559: }
560:
561: // Need to apply disabled class
562: //
563: String styleClass = getStyle(theme,
564: rbcbComponent.isDisabled() ? IMAGE_DIS : IMAGE);
565: styleClass = RenderingUtilities.getStyleClasses(context,
566: imageComponent, styleClass);
567: if (styleClass != null) {
568: imageComponent.setStyleClass(styleClass);
569: }
570:
571: return imageComponent;
572: }
573:
574: /**
575: * Called from <code>renderSelection</code> to render a LABEL.
576: *
577: * @param context FacesContext for the request we are processing.
578: * @param component UIComponent to be decoded.
579: * @param writer <code>ResponseWriter</code> to which the HTML will
580: * be output
581: */
582: protected void renderLabel(FacesContext context,
583: UIComponent component, Theme theme, ResponseWriter writer)
584: throws IOException {
585:
586: UIComponent labelComponent = getLabelComponent(context,
587: component, theme);
588: if (labelComponent != null) {
589: RenderingUtilities.renderComponent(labelComponent, context);
590: }
591: }
592:
593: private UIComponent getLabelComponent(FacesContext context,
594: UIComponent component, Theme theme) throws IOException {
595:
596: RbCbSelector rbcbComponent = (RbCbSelector) component;
597: Label labelComponent = (Label) rbcbComponent
598: .getLabelComponent();
599: if (labelComponent == null) {
600: return null;
601: }
602:
603: // Need to apply disabled class
604: //
605: String styleClass = getStyle(theme,
606: rbcbComponent.isDisabled() ? LABEL_DIS : LABEL);
607: styleClass = RenderingUtilities.getStyleClasses(context,
608: labelComponent, styleClass);
609: if (styleClass != null) {
610: labelComponent.setStyleClass(styleClass);
611: }
612: return labelComponent;
613: }
614:
615: /**
616: * <p>
617: * Attempt to convert previously stored state information into an
618: * object of the type required for this component (optionally using the
619: * registered {@link javax.faces.convert.Converter} for this component,
620: * if there is one). If conversion is successful, the new value
621: * is returned and if not, a
622: * {@link javax.faces.convert.ConverterException} is thrown.
623: * </p>
624: *
625: * @param context {@link FacesContext} for the request we are processing
626: * @param component component being renderer.
627: * @param submittedValue a value stored on the component during
628: * <code>decode</code>.
629: *
630: * @exception ConverterException if the submitted value
631: * cannot be converted successfully.
632: * @exception NullPointerException if <code>context</code>
633: * or <code>component</code> is <code>null</code>
634: */
635: public Object getConvertedValue(FacesContext context,
636: UIComponent component, Object submittedValue)
637: throws ConverterException {
638:
639: // I know this looks odd but it gives an opportunity
640: // for an alternative renderer for Checkbox and RadioButton
641: // to provide a converter.
642: //
643: return ((RbCbSelector) component).getConvertedValue(context,
644: (RbCbSelector) component, submittedValue);
645: }
646: }
|