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:
042: package com.sun.rave.web.ui.appbase.renderer;
043:
044: import java.beans.Beans;
045: import java.io.IOException;
046: import java.util.Iterator;
047: import javax.faces.component.NamingContainer;
048: import javax.faces.component.UIComponent;
049: import javax.faces.component.UIForm;
050: import javax.faces.component.UIParameter;
051: import javax.faces.context.FacesContext;
052: import javax.faces.context.ResponseWriter;
053: import javax.faces.event.ActionEvent;
054: import javax.faces.render.Renderer;
055:
056: /**
057: * <p>Replacement renderer for the <code><h:commandLink></code>
058: * component, which is not tied to the JSF standard form renderer (and
059: * will therefore work inside a Braveheart form component).</p>
060: */
061:
062: public class CommandLinkRenderer extends Renderer {
063:
064: // -------------------------------------------------------- Static Variables
065:
066: /**
067: * <p>Token for private names.</p>
068: */
069: private static final String TOKEN = "com_sun_rave_web_ui_appbase_renderer_CommandLinkRendererer";
070:
071: /**
072: * <p>Pass through attribute names.</p>
073: */
074: private static String passThrough[] = { "accesskey", "charset",
075: "dir", "hreflang", "lang", "onblur",
076: /* "onclick", */"ondblclick", "onfocus", "onkeydown",
077: "onkeypress", "onkeyup", "onmousedown", "onmousemove",
078: "onmouseout", "onmouseover", "onmouseup", "rel", "rev",
079: "style", "tabindex", "target", "title", "type" };
080:
081: // -------------------------------------------------------- Renderer Methods
082:
083: /**
084: * <p>Perform setup processing that will be required for decoding
085: * the incoming request.</p>
086: *
087: * @param context <code>FacesContext</code> for the current request
088: * @param component <code>UIComponent</code> to be processed
089: *
090: * @exception NullPointerException if <code>contxt</code>
091: * or <code>component</code> is <code>null</code>.
092: */
093: public void decode(FacesContext context, UIComponent component) {
094:
095: // Enforce spec NPE behaviors
096: if ((context == null) || (component == null)) {
097: throw new NullPointerException();
098: }
099:
100: // Skip this component if it is not relevant
101: if (!component.isRendered() || isDisabled(component)
102: || isReadOnly(component)) {
103: return;
104: }
105:
106: // Set up the variables we will need
107: UIForm form = null;
108: UIComponent parent = component.getParent();
109: while (parent != null) {
110: if (parent instanceof UIForm) {
111: form = (UIForm) parent;
112: break;
113: }
114: parent = parent.getParent();
115: }
116: if (form == null) {
117: return;
118: }
119:
120: // Was this the component that submitted the form?
121: String value = (String) context.getExternalContext()
122: .getRequestParameterMap().get(TOKEN);
123: if ((value == null)
124: || !value.equals(component.getClientId(context))) {
125: return;
126: }
127:
128: // Queue an ActionEvent from this component
129: component.queueEvent(new ActionEvent(component));
130:
131: }
132:
133: /**
134: * <p>Render the beginning of a hyperlink to submit this form.</p>
135: *
136: * @param context <code>FacesContext</code> for the current request
137: * @param component <code>UIComponent</code> to be processed
138: *
139: * @exception IOException if an input/output error occurs
140: * @exception NullPointerException if <code>contxt</code>
141: * or <code>component</code> is <code>null</code>.
142: */
143: public void encodeBegin(FacesContext context, UIComponent component)
144: throws IOException {
145:
146: // Enforce spec NPE behaviors
147: if ((context == null) || (component == null)) {
148: throw new NullPointerException();
149: }
150:
151: // Skip this component if it is not relevant
152: if (!component.isRendered() || isDisabled(component)
153: || isReadOnly(component)) {
154: return;
155: }
156:
157: // At designtime we don't require a form. For example,
158: // without this you cannot render CommandLinks in page fragments
159: // since these typically don't include forms
160: String formClientId = "";
161: if (!Beans.isDesignTime()) {
162: UIForm form = null;
163: UIComponent parent = component.getParent();
164: while (parent != null) {
165: if (parent instanceof UIForm) {
166: form = (UIForm) parent;
167: break;
168: }
169: parent = parent.getParent();
170: }
171: if (form == null) {
172: return;
173: }
174: formClientId = form.getClientId(context);
175: }
176:
177: // If this is the first nested command link inside this form,
178: // render a hidden variable to identify which link submitted.
179: String key = formClientId + NamingContainer.SEPARATOR_CHAR
180: + TOKEN;
181: ResponseWriter writer = context.getResponseWriter();
182: // FIXME - single outer span?
183: if (context.getExternalContext().getRequestMap().get(key) == null) {
184: writer.startElement("input", component); // NOI18N
185: writer.writeAttribute("name", TOKEN, null); // NOI18N
186: writer.writeAttribute("type", "hidden", null); // NOI18N
187: writer.writeAttribute("value", "", null); // NOI18N
188: writer.endElement("input"); // NOI18N
189: context.getExternalContext().getRequestMap().put(key,
190: Boolean.TRUE);
191: }
192:
193: // Render the beginning of this hyperlink
194: writer.startElement("a", component);
195: if (component.getId() != null) {
196: writer.writeAttribute("id", component.getClientId(context),
197: "id"); // NOI18N
198: }
199: writer.writeAttribute("href", "#", null); // NOI18N
200: String styleClass = (String) component.getAttributes().get(
201: "styleClass"); // NOI18N
202: if (styleClass != null) {
203: writer.writeAttribute("class", styleClass, "styleClass"); // NOI18N
204: }
205: for (int i = 0; i < passThrough.length; i++) {
206: Object value = component.getAttributes()
207: .get(passThrough[i]);
208: if (value != null) {
209: writer.writeAttribute(passThrough[i], value.toString(),
210: passThrough[i]);
211: }
212: }
213:
214: // Render the JavaScript content of the "onclick" element
215: StringBuffer sb = new StringBuffer();
216: sb.append("document.forms['"); // NOI18N
217: sb.append(formClientId);
218: sb.append("']['"); // NOI18N
219: sb.append(TOKEN);
220: sb.append("'].value='"); // NOI18N
221: sb.append(component.getClientId(context));
222: sb.append("';"); // NOI18N
223: Iterator kids = component.getChildren().iterator();
224: while (kids.hasNext()) {
225: UIComponent kid = (UIComponent) kids.next();
226: if (!(kid instanceof UIParameter)) {
227: continue;
228: }
229: sb.append("document.forms['"); // NOI18N
230: sb.append(formClientId);
231: sb.append("']['"); // NOI18N
232: sb.append((String) kid.getAttributes().get("name")); // NOI18N
233: sb.append("'].value='"); // NOI18N
234: sb.append((String) kid.getAttributes().get("value")); // NOI18N
235: sb.append("';"); // NOI18N
236: }
237: sb.append("document.forms['"); // NOI18N
238: sb.append(formClientId);
239: sb.append("'].submit(); return false;"); // NOI18N
240: writer.writeAttribute("onclick", sb.toString(), null); // NOI18N
241:
242: // Render the value (if any) as part of the hyperlink text
243: Object value = component.getAttributes().get("value"); // NOI18N
244: if (value != null) {
245: if (value instanceof String) {
246: writer.write((String) value);
247: } else {
248: writer.write(value.toString());
249: }
250: }
251:
252: }
253:
254: /**
255: * <p>Render the ending of a hyperlink to submit this form.</p>
256: *
257: * @param context <code>FacesContext</code> for the current request
258: * @param component <code>UIComponent</code> to be processed
259: *
260: * @exception IOException if an input/output error occurs
261: * @exception NullPointerException if <code>contxt</code>
262: * or <code>component</code> is <code>null</code>.
263: */
264: public void encodeEnd(FacesContext context, UIComponent component)
265: throws IOException {
266:
267: // Enforce spec NPE behaviors
268: if ((context == null) || (component == null)) {
269: throw new NullPointerException();
270: }
271:
272: // Skip this component if it is not relevant
273: if (!component.isRendered() || isDisabled(component)
274: || isReadOnly(component)) {
275: return;
276: }
277:
278: // Render the ending of this hyperlink
279: ResponseWriter writer = context.getResponseWriter();
280: writer.endElement("a"); // NOI18N
281:
282: }
283:
284: // -------------------------------------------------- Private Methods
285:
286: /**
287: * <p>Return <code>true</code> if the specified component is disabled.</p>
288: *
289: * @param component <code>UIComponent</code> to be tested
290: */
291: private boolean isDisabled(UIComponent component) {
292:
293: Object value = component.getAttributes().get("disabled"); // NOI18N
294: if (value != null) {
295: if (value instanceof String) {
296: return (Boolean.valueOf((String) value).booleanValue());
297: } else {
298: return (value.equals(Boolean.TRUE));
299: }
300: } else {
301: return false;
302: }
303:
304: }
305:
306: /**
307: * <p>Return <code>true</code> if the specified component is read only.</p>
308: *
309: * @param component <code>UIComponent</code> to be tested
310: */
311: private boolean isReadOnly(UIComponent component) {
312:
313: Object value = component.getAttributes().get("readonly"); // NOI18N
314: if (value != null) {
315: if (value instanceof String) {
316: return (Boolean.valueOf((String) value).booleanValue());
317: } else {
318: return (value.equals(Boolean.TRUE));
319: }
320: } else {
321: return false;
322: }
323:
324: }
325:
326: }
|