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 com.sun.rave.web.ui.component.Hyperlink;
044: import com.sun.rave.web.ui.component.Form;
045: import com.sun.rave.web.ui.component.util.Util;
046: import com.sun.rave.web.ui.theme.Theme;
047: import com.sun.rave.web.ui.theme.ThemeImages;
048: import com.sun.rave.web.ui.theme.ThemeStyles;
049: import com.sun.rave.web.ui.util.ConversionUtilities;
050: import com.sun.rave.web.ui.util.RenderingUtilities;
051: import com.sun.rave.web.ui.util.ThemeUtilities;
052: import java.io.IOException;
053: import java.lang.NullPointerException;
054: import java.lang.StringBuffer;
055: import java.util.Iterator;
056: import java.util.HashMap;
057: import java.util.Map;
058:
059: import javax.faces.component.NamingContainer;
060: import javax.faces.component.UIComponent;
061: import javax.faces.component.UIParameter;
062: import javax.faces.component.UICommand;
063: import javax.faces.component.UIForm;
064:
065: import javax.faces.context.FacesContext;
066: import javax.faces.context.ResponseWriter;
067: import javax.faces.event.ActionEvent;
068:
069: /**
070: * <p>This class is responsible for rendering the {@link Hyperlink} component for the
071: * HTML Render Kit.</p> <p> The {@link Hyperlink} component can be used as an anchor, a
072: * plain hyperlink or a hyperlink that submits the form depending on how the
073: * properites are filled out for the component </p>
074: */
075: public class HyperlinkRenderer extends AbstractRenderer {
076:
077: // -------------------------------------------------------- Static Variables
078:
079: //TODO: figure out a way to do anchors better than specifying the entire context path
080: //TODO: use the style factories Rick is going to setup
081: //TODO: move the javascript created here to a function in the default javascript.
082:
083: /**
084: * <p>The set of boolean pass-through attributes to be rendered.
085: * <br />Note: if you add a boolean here and you want it rendered
086: * if the hyperlink is disabled then you must fix the renderer to
087: * work properly! </p>
088: */
089: private static final String booleanAttributes[] = { "disabled" }; //NOI18N
090:
091: /**
092: * <p>The set of integer pass-through attributes to be rendered.</p>
093: */
094: private static final String integerAttributes[] = { "tabIndex" }; //NOI18N
095:
096: /**
097: * <p>The set of String pass-through attributes to be rendered.</p>
098: */
099: private static final String stringAttributes[] = { "onBlur",
100: "onFocus", "onDblClick", "onKeyDown",
101: "onKeyPress",
102: "onMouseUp", //NOI18N
103: "onKeyUp", "onMouseDown", "onMouseMove", "onMouseOut",
104: "onMouseOver" }; //NOI18N
105:
106: // -------------------------------------------------------- Renderer Methods
107:
108: public boolean getRendersChildren() {
109: return true;
110: }
111:
112: /**
113: * <p>Decode will determine if this component was the one that submitted the form.
114: * It determines this by looking for the hidden field with the link's name
115: * appended with an "_submittedField"
116: * If this hidden field contains the id of the component then this component submitted
117: * the form.</p>
118: * @param context <code>FacesContext</code> for the current request
119: * @param component <code>UIComponent</code> to be decoded
120: * @exception NullPointerException if <code>context</code> or
121: * <code>component</code> is <code>null</code>
122: */
123: public void decode(FacesContext context, UIComponent component) {
124:
125: // Enforce NPE requirements in the Javadocs
126: if ((context == null) || (component == null)) {
127: throw new NullPointerException();
128: }
129: Hyperlink link = (Hyperlink) component;
130:
131: if (isSubmitLink(link)) {
132: String paramId = component.getClientId(context)
133: + "_submittedField";
134: String value = (String) context.getExternalContext()
135: .getRequestParameterMap().get(paramId);
136:
137: if ((value == null)
138: || !value.equals(component.getClientId(context))) {
139: return;
140: }
141:
142: //add the event to the queue so we know that a command happened.
143: //this should automatically take care of actionlisteners and actions
144: link.queueEvent(new ActionEvent(link));
145:
146: // since this component submitted the form, we need to make it have
147: // focus next time through. To do this, we will set an attribute
148: // in the request map.
149: RenderingUtilities.setLastClientID(context, link
150: .getPrimaryElementID(context));
151: }
152: }
153:
154: /**
155: * <p>Render the start of an anchor (hyperlink) tag.</p>
156: * @param context <code>FacesContext</code> for the current request
157: * @param component <code>UIComponent</code> to be rendered
158: * @param writer <code>ResponseWriter</code> to which the element
159: * start should be rendered
160: * @exception IOException if an input/output error occurs
161: */
162: protected void renderStart(FacesContext context,
163: UIComponent component, ResponseWriter writer)
164: throws IOException {
165: //intentionally left blank
166: }
167:
168: /**
169: * <p>Render the attributes for an anchor tag. The onclick attribute will contain
170: * extra javascript that will appropriately submit the form if the URL field is
171: * not set.</p>
172: * @param context <code>FacesContext</code> for the current request
173: * @param component <code>UIComponent</code> to be rendered
174: * @param writer <code>ResponseWriter</code> to which the element
175: * attributes should be rendered
176: * @exception IOException if an input/output error occurs
177: */
178: protected void renderAttributes(FacesContext context,
179: UIComponent component, ResponseWriter writer)
180: throws IOException {
181: //intentionally left blank.
182: }
183:
184: protected void finishRenderAttributes(FacesContext context,
185: UIComponent component, ResponseWriter writer)
186: throws IOException {
187:
188: Hyperlink link = (Hyperlink) component;
189:
190: // Set up local variables we will need
191: // <RAVE>
192: // String label = link.getText();
193: String label = ConversionUtilities.convertValueToString(
194: component, link.getText());
195: // </RAVE>
196: if (label != null) {
197: writer.writeText(label, null);
198: }
199:
200: }
201:
202: public void encodeChildren(FacesContext context,
203: UIComponent component) throws IOException {
204: //purposefully don't want to do anything here!
205:
206: }
207:
208: /**
209: * <p>Close off the anchor tag.</p>
210: * @param context <code>FacesContext</code> for the current request
211: * @param component <code>UIComponent</code> to be rendered
212: * @param writer <code>ResponseWriter</code> to which the element
213: * end should be rendered
214: * @exception IOException if an input/output error occurs
215: */
216: protected void renderEnd(FacesContext context,
217: UIComponent component, ResponseWriter writer)
218: throws IOException {
219: renderLink(context, component, writer);
220: }
221:
222: protected void renderLink(FacesContext context,
223: UIComponent component, ResponseWriter writer)
224: throws IOException {
225: Hyperlink link = (Hyperlink) component;
226:
227: if (!link.isDisabled()) {
228: // Start the appropriate element
229: writer.startElement("a", link); //NOI18N
230: } else {
231: writer.startElement("span", link); //NOI18N
232: }
233:
234: // Set up local variables we will need
235: // <RAVE>
236: // String label = link.getText();
237: String label = ConversionUtilities.convertValueToString(
238: component, link.getText());
239: // </RAVE>
240: String url = link.getUrl();
241: String target = link.getTarget();
242: String tooltip = link.getToolTip();
243: String onclick = link.getOnClick();
244: String urlLang = link.getUrlLang();
245:
246: // Render core and pass through attributes as necessary
247: String sb = getStyles(context, link);
248:
249: addCoreAttributes(context, component, writer, sb);
250: addIntegerAttributes(context, component, writer,
251: integerAttributes);
252: addStringAttributes(context, component, writer,
253: stringAttributes);
254:
255: if (!link.isDisabled()) {
256: // no such thing as disabling a span so we must do this here.
257: addBooleanAttributes(context, component, writer,
258: booleanAttributes);
259:
260: // writeout href for the a tag:
261: if (url != null) {
262: // URL is not empty, check and see if it's just an anchor:
263: if (url.startsWith("#") || url.startsWith("mailto:")
264: || url.startsWith("javascript:")) {
265: writer.writeURIAttribute("href", url, "url"); //NOI18N
266: } else {
267: url = context.getApplication().getViewHandler()
268: .getResourceURL(context, url);
269: RenderingUtilities.renderURLAttribute(context,
270: writer, component, "href", //NOI18N
271: url, "url"); //NOI18N
272: }
273: if (onclick != null) {
274: writer
275: .writeAttribute("onclick", onclick,
276: "onclick");
277: }
278: } else {
279: UIComponent form = Util.getForm(context, component);
280: if (form != null) {
281: String formClientId = form.getClientId(context);
282:
283: StringBuffer buff = new StringBuffer(200);
284: if (onclick != null) {
285: buff.append(onclick);
286: if (!onclick.endsWith(";")) { //NOI18N
287: buff.append(";"); //NOI18N
288: }
289: }
290: buff.append("return hyperlink_submit(this, "); //NOI18N
291: buff.append("'");
292: buff.append(formClientId);
293: buff.append("'");
294: buff.append(", ");
295:
296: boolean didOnce = false;
297: Iterator kids = component.getChildren().iterator();
298: while (kids.hasNext()) {
299: UIComponent kid = (UIComponent) kids.next();
300: if (!(kid instanceof UIParameter)) {
301: continue;
302: }
303:
304: if (!didOnce) {
305: buff.append("new Array(");
306: }
307: String name = (String) kid.getAttributes().get(
308: "name"); //NOI18N
309: String value = (String) kid.getAttributes()
310: .get("value"); //NOI18N
311: //add to map for later use.
312: if (!didOnce) {
313: buff.append("'");
314: } else {
315: buff.append(",'");
316: }
317: buff.append(name);
318: buff.append("','");
319: buff.append(value);
320: buff.append("'"); //NOI18N
321: didOnce = true;
322: }
323:
324: if (!didOnce) {
325: buff.append("null");
326: } else {
327: buff.append(")");
328: }
329:
330: buff.append(");");
331: writer.writeAttribute("onclick", buff.toString(),
332: null);
333: writer.writeAttribute("href", "#", null); //NOI18N
334: }
335: }
336:
337: if (null != target) {
338: writer.writeAttribute("target", target, null); //NOI18N
339: }
340:
341: if (null != tooltip) {
342: writer.writeAttribute("title", tooltip, null); //NOI18N
343: }
344:
345: if (null != urlLang) {
346: writer.writeAttribute("hreflang", urlLang, "urlLang"); //NOI18N
347: }
348:
349: }
350: //for hyperlink, this will encodeChildren as well, but not for subclasses
351: //unless they explicitly do it!
352: finishRenderAttributes(context, component, writer);
353:
354: renderChildren(context, component);
355:
356: // End the appropriate element
357: if (!link.isDisabled()) {
358: writer.endElement("a"); //NOI18N
359: } else {
360: // no need to render params for disabled link
361: writer.endElement("span"); //NOI18N
362: }
363: }
364:
365: /**
366: * This method is called by renderEnd. It is provided so renderers that
367: * extend HyperlinkRenderer (such as TabRenderer) may override it in order
368: * to prevent children from always being rendered.
369: *
370: * @param context The current FacesContext.
371: * @param component The current component.
372: */
373: protected void renderChildren(FacesContext context,
374: UIComponent component) throws IOException {
375: super .encodeChildren(context, component);
376: }
377:
378: /**
379: * This function returns the style classes necessary to display the {@link Hyperlink} component as it's state indicates
380: * @return the style classes needed to display the current state of the component
381: */
382: protected String getStyles(FacesContext context,
383: UIComponent component) {
384: Hyperlink link = (Hyperlink) component;
385:
386: StringBuffer sb = new StringBuffer();
387: Theme theme = ThemeUtilities.getTheme(context);
388: if (link.isDisabled()) {
389: sb.append(" "); //NOI18N
390: sb.append(theme.getStyleClass(ThemeStyles.LINK_DISABLED));
391: }
392: return (sb.length() > 0) ? sb.toString() : null;
393: }
394:
395: // --------------------------------------------------------- Private Methods
396:
397: private boolean isSubmitLink(Hyperlink h) {
398: return (h.getUrl() == null);
399: }
400:
401: }
|