001: /*
002: * Copyright 2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.myfaces.shared_impl.renderkit.html;
017:
018: import org.apache.myfaces.shared_impl.renderkit.JSFAttr;
019: import org.apache.myfaces.shared_impl.renderkit.RendererUtils;
020: import org.apache.myfaces.shared_impl.renderkit.html.util.FormInfo;
021: import org.apache.myfaces.shared_impl.renderkit.html.util.JavascriptUtils;
022: import org.apache.myfaces.shared_impl.util._ComponentUtils;
023:
024: import javax.faces.application.StateManager;
025: import javax.faces.application.ViewHandler;
026: import javax.faces.component.UICommand;
027: import javax.faces.component.UIComponent;
028: import javax.faces.component.UIOutput;
029: import javax.faces.component.UIParameter;
030: import javax.faces.component.html.HtmlCommandLink;
031: import javax.faces.context.FacesContext;
032: import javax.faces.context.ResponseWriter;
033: import javax.faces.event.ActionEvent;
034: import java.io.IOException;
035: import java.io.UnsupportedEncodingException;
036: import java.net.URLEncoder;
037: import java.util.Iterator;
038:
039: /**
040: * @author Manfred Geiler
041: * @version $Revision: 544646 $ $Date: 2007-06-05 23:51:27 +0200 (Di, 05 Jun 2007) $
042: */
043: public abstract class HtmlLinkRendererBase extends HtmlRenderer {
044: public static final String URL_STATE_MARKER = "JSF_URL_STATE_MARKER=DUMMY";
045: public static final int URL_STATE_MARKER_LEN = URL_STATE_MARKER
046: .length();
047:
048: //private static final Log log = LogFactory.getLog(HtmlLinkRenderer.class);
049:
050: public boolean getRendersChildren() {
051: // We must be able to render the children without a surrounding anchor
052: // if the Link is disabled
053: return true;
054: }
055:
056: public void decode(FacesContext facesContext, UIComponent component) {
057: super .decode(facesContext, component); //check for NP
058:
059: if (component instanceof UICommand) {
060: String clientId = component.getClientId(facesContext);
061: FormInfo formInfo = findNestingForm(component, facesContext);
062: String reqValue = (String) facesContext
063: .getExternalContext()
064: .getRequestParameterMap()
065: .get(
066: HtmlRendererUtils
067: .getHiddenCommandLinkFieldName(formInfo));
068: if (reqValue != null && reqValue.equals(clientId)) {
069: component.queueEvent(new ActionEvent(component));
070:
071: RendererUtils.initPartialValidationAndModelUpdate(
072: component, facesContext);
073: }
074: } else if (component instanceof UIOutput) {
075: //do nothing
076: } else {
077: throw new IllegalArgumentException(
078: "Unsupported component class "
079: + component.getClass().getName());
080: }
081: }
082:
083: public void encodeBegin(FacesContext facesContext,
084: UIComponent component) throws IOException {
085: super .encodeBegin(facesContext, component); //check for NP
086:
087: if (component instanceof UICommand) {
088: renderCommandLinkStart(facesContext, component, component
089: .getClientId(facesContext), ((UICommand) component)
090: .getValue(), getStyle(facesContext, component),
091: getStyleClass(facesContext, component));
092: } else if (component instanceof UIOutput) {
093: renderOutputLinkStart(facesContext, (UIOutput) component);
094: } else {
095: throw new IllegalArgumentException(
096: "Unsupported component class "
097: + component.getClass().getName());
098: }
099: }
100:
101: /**
102: * Can be overwritten by derived classes to overrule the style to be used.
103: */
104: protected String getStyle(FacesContext facesContext,
105: UIComponent link) {
106: if (link instanceof HtmlCommandLink) {
107: return ((HtmlCommandLink) link).getStyle();
108: }
109:
110: return (String) link.getAttributes().get(HTML.STYLE_ATTR);
111:
112: }
113:
114: /**
115: * Can be overwritten by derived classes to overrule the style class to be used.
116: */
117: protected String getStyleClass(FacesContext facesContext,
118: UIComponent link) {
119: if (link instanceof HtmlCommandLink) {
120: return ((HtmlCommandLink) link).getStyleClass();
121: }
122:
123: return (String) link.getAttributes().get(HTML.STYLE_CLASS_ATTR);
124:
125: }
126:
127: public void encodeChildren(FacesContext facesContext,
128: UIComponent component) throws IOException {
129: RendererUtils.renderChildren(facesContext, component);
130: }
131:
132: public void encodeEnd(FacesContext facesContext,
133: UIComponent component) throws IOException {
134: super .encodeEnd(facesContext, component); //check for NP
135:
136: if (component instanceof UICommand) {
137: renderCommandLinkEnd(facesContext, component);
138:
139: HtmlFormRendererBase.renderScrollHiddenInputIfNecessary(
140: findNestingForm(component, facesContext).getForm(),
141: facesContext, facesContext.getResponseWriter());
142: } else if (component instanceof UIOutput) {
143: renderOutputLinkEnd(facesContext, component);
144: } else {
145: throw new IllegalArgumentException(
146: "Unsupported component class "
147: + component.getClass().getName());
148: }
149: }
150:
151: protected void renderCommandLinkStart(FacesContext facesContext,
152: UIComponent component, String clientId, Object value,
153: String style, String styleClass) throws IOException {
154: ResponseWriter writer = facesContext.getResponseWriter();
155:
156: if (HtmlRendererUtils.isDisabled(component)) {
157: writer.startElement(HTML.SPAN_ELEM, component);
158: HtmlRendererUtils.writeIdIfNecessary(writer, component,
159: facesContext);
160: HtmlRendererUtils.renderHTMLAttributes(writer, component,
161: HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES);
162: } else {
163: String[] anchorAttrsToRender;
164: if (JavascriptUtils.isJavascriptAllowed(facesContext
165: .getExternalContext())) {
166: renderJavaScriptAnchorStart(facesContext, writer,
167: component, clientId);
168: anchorAttrsToRender = HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES_WITHOUT_ONCLICK_WITHOUT_STYLE;
169: } else {
170: renderNonJavaScriptAnchorStart(facesContext, writer,
171: component, clientId);
172: anchorAttrsToRender = HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES_WITHOUT_STYLE;
173: }
174:
175: HtmlRendererUtils.writeIdIfNecessary(writer, component,
176: facesContext);
177: HtmlRendererUtils.renderHTMLAttributes(writer, component,
178: anchorAttrsToRender);
179: HtmlRendererUtils.renderHTMLAttribute(writer,
180: HTML.STYLE_ATTR, HTML.STYLE_ATTR, style);
181: HtmlRendererUtils.renderHTMLAttribute(writer,
182: HTML.STYLE_CLASS_ATTR, HTML.STYLE_CLASS_ATTR,
183: styleClass);
184: }
185:
186: // render value as required by JSF 1.1 renderkitdocs
187: if (value != null) {
188: writer.writeText(value.toString(), JSFAttr.VALUE_ATTR);
189: }
190: }
191:
192: protected void renderJavaScriptAnchorStart(
193: FacesContext facesContext, ResponseWriter writer,
194: UIComponent component, String clientId) throws IOException {
195: //Find form
196: FormInfo formInfo = findNestingForm(component, facesContext);
197: if (formInfo == null) {
198: String path = RendererUtils.getPathToComponent(component);
199: String msg = "Link is not embedded in a form. Change component/tag '"
200: + clientId
201: + "' from javax.faces.*/<h:tagName /> "
202: + "to org.apache.myfaces.*/<t:tagName />, or embed it in a form. This is not a bug. "
203: + "Please see: http://wiki.apache.org/myfaces/Upgrading_to_Tomahawk_1.1.3 "
204: + "The path to this component is "
205: + path
206: + ". If you need to render a special form and a JSF-form's attributes are not enough,"
207: + "consider using the s:form tag of the MyFaces sandbox.";
208: throw new IllegalArgumentException(msg);
209: }
210: UIComponent nestingForm = formInfo.getForm();
211: String formName = formInfo.getFormName();
212:
213: StringBuffer onClick = new StringBuffer();
214:
215: String commandOnclick;
216: if (component instanceof HtmlCommandLink) {
217: commandOnclick = ((HtmlCommandLink) component).getOnclick();
218: } else {
219: commandOnclick = (String) component.getAttributes().get(
220: HTML.ONCLICK_ATTR);
221: }
222: if (commandOnclick != null) {
223: onClick.append(commandOnclick);
224: onClick.append(';');
225: }
226:
227: if (RendererUtils.isAdfOrTrinidadForm(formInfo.getForm())) {
228: onClick.append("submitForm('");
229: onClick
230: .append(formInfo.getForm()
231: .getClientId(facesContext));
232: onClick.append("',1,{source:'");
233: onClick.append(component.getClientId(facesContext));
234: onClick.append("'});return false;");
235: } else {
236: HtmlRendererUtils.renderFormSubmitScript(facesContext);
237:
238: StringBuffer params = addChildParameters(component,
239: nestingForm);
240:
241: String target = getTarget(component);
242:
243: onClick.append("return ").append(
244: HtmlRendererUtils.SUBMIT_FORM_FN_NAME).append("('")
245: .append(formName).append("','").append(clientId)
246: .append("'");
247:
248: if (params.length() > 2 || target != null) {
249: onClick.append(",").append(
250: target == null ? "null" : ("'" + target + "'"))
251: .append(",").append(params);
252: }
253: onClick.append(");");
254:
255: //render hidden field - todo: in here for backwards compatibility
256: String hiddenFieldName = HtmlRendererUtils
257: .getHiddenCommandLinkFieldName(formInfo);
258: addHiddenCommandParameter(facesContext, nestingForm,
259: hiddenFieldName);
260:
261: //render hidden field - todo: in here for backwards compatibility
262: String hiddenFieldNameMyFacesOld = HtmlRendererUtils
263: .getHiddenCommandLinkFieldNameMyfacesOld(formInfo);
264: addHiddenCommandParameter(facesContext, nestingForm,
265: hiddenFieldNameMyFacesOld);
266: }
267:
268: writer.startElement(HTML.ANCHOR_ELEM, component);
269: writer.writeURIAttribute(HTML.HREF_ATTR, "#", null);
270: writer.writeAttribute(HTML.ONCLICK_ATTR, onClick.toString(),
271: null);
272: }
273:
274: private String getTarget(UIComponent component) {
275: // for performance reason: double check for the target attribute
276: String target;
277: if (component instanceof HtmlCommandLink) {
278: target = ((HtmlCommandLink) component).getTarget();
279: } else {
280: target = (String) component.getAttributes().get(
281: HTML.TARGET_ATTR);
282: }
283: return target;
284: }
285:
286: private StringBuffer addChildParameters(UIComponent component,
287: UIComponent nestingForm) {
288: //add child parameters
289: StringBuffer params = new StringBuffer();
290: params.append("[");
291: for (Iterator it = getChildren(component).iterator(); it
292: .hasNext();) {
293:
294: UIComponent child = (UIComponent) it.next();
295: if (child instanceof UIParameter) {
296: String name = ((UIParameter) child).getName();
297:
298: if (name == null) {
299: throw new IllegalArgumentException(
300: "Unnamed parameter value not allowed within command link.");
301: }
302:
303: addHiddenCommandParameter(FacesContext
304: .getCurrentInstance(), nestingForm, name);
305:
306: Object value = ((UIParameter) child).getValue();
307:
308: //UIParameter is no ValueHolder, so no conversion possible - calling .toString on value....
309: String strParamValue = value != null ? org.apache.myfaces.shared_impl.renderkit.html.util.HTMLEncoder
310: .encode(value.toString(), false, false)
311: : "";
312:
313: if (params.length() > 1) {
314: params.append(",");
315: }
316:
317: params.append("['");
318: params.append(name);
319: params.append("','");
320: params.append(strParamValue);
321: params.append("']");
322: }
323: }
324: params.append("]");
325: return params;
326: }
327:
328: /**
329: * find nesting form<br />
330: * need to be overrideable to deal with dummyForm stuff in tomahawk.
331: */
332: protected FormInfo findNestingForm(UIComponent uiComponent,
333: FacesContext facesContext) {
334: return _ComponentUtils.findNestingForm(uiComponent,
335: facesContext);
336: }
337:
338: protected void addHiddenCommandParameter(FacesContext facesContext,
339: UIComponent nestingForm, String hiddenFieldName) {
340: if (nestingForm != null) {
341: HtmlFormRendererBase.addHiddenCommandParameter(
342: facesContext, nestingForm, hiddenFieldName);
343: }
344: }
345:
346: protected void renderNonJavaScriptAnchorStart(
347: FacesContext facesContext, ResponseWriter writer,
348: UIComponent component, String clientId) throws IOException {
349: ViewHandler viewHandler = facesContext.getApplication()
350: .getViewHandler();
351: String viewId = facesContext.getViewRoot().getViewId();
352: String path = viewHandler.getActionURL(facesContext, viewId);
353:
354: StringBuffer hrefBuf = new StringBuffer(path);
355:
356: //add clientId parameter for decode
357:
358: if (path.indexOf('?') == -1) {
359: hrefBuf.append('?');
360: } else {
361: hrefBuf.append('&');
362: }
363: String hiddenFieldName = HtmlRendererUtils
364: .getHiddenCommandLinkFieldName(findNestingForm(
365: component, facesContext));
366: hrefBuf.append(hiddenFieldName);
367: hrefBuf.append('=');
368: hrefBuf.append(clientId);
369:
370: if (getChildCount(component) > 0) {
371: addChildParametersToHref(component, hrefBuf, false, //not the first url parameter
372: writer.getCharacterEncoding());
373: }
374:
375: StateManager stateManager = facesContext.getApplication()
376: .getStateManager();
377: hrefBuf.append("&");
378: if (stateManager.isSavingStateInClient(facesContext)) {
379: hrefBuf.append(URL_STATE_MARKER);
380: } else {
381: hrefBuf.append(RendererUtils.SEQUENCE_PARAM);
382: hrefBuf.append('=');
383: hrefBuf
384: .append(org.apache.myfaces.shared_impl.renderkit.RendererUtils
385: .getViewSequence(facesContext));
386: }
387: String href = facesContext.getExternalContext()
388: .encodeActionURL(hrefBuf.toString());
389: writer.startElement(HTML.ANCHOR_ELEM, component);
390: writer.writeURIAttribute(HTML.HREF_ATTR, facesContext
391: .getExternalContext().encodeActionURL(href), null);
392: }
393:
394: private void addChildParametersToHref(UIComponent linkComponent,
395: StringBuffer hrefBuf, boolean firstParameter,
396: String charEncoding) throws IOException {
397: for (Iterator it = getChildren(linkComponent).iterator(); it
398: .hasNext();) {
399: UIComponent child = (UIComponent) it.next();
400: if (child instanceof UIParameter) {
401: String name = ((UIParameter) child).getName();
402: Object value = ((UIParameter) child).getValue();
403:
404: addParameterToHref(name, value, hrefBuf,
405: firstParameter, charEncoding);
406: firstParameter = false;
407: }
408: }
409: }
410:
411: protected void renderOutputLinkStart(FacesContext facesContext,
412: UIOutput output) throws IOException {
413: ResponseWriter writer = facesContext.getResponseWriter();
414:
415: //calculate href
416: String href = org.apache.myfaces.shared_impl.renderkit.RendererUtils
417: .getStringValue(facesContext, output);
418: if (getChildCount(output) > 0) {
419: StringBuffer hrefBuf = new StringBuffer(href);
420: addChildParametersToHref(output, hrefBuf, (href
421: .indexOf('?') == -1), //first url parameter?
422: writer.getCharacterEncoding());
423: href = hrefBuf.toString();
424: }
425: href = facesContext.getExternalContext()
426: .encodeResourceURL(href); //TODO: or encodeActionURL ?
427:
428: //write anchor
429: writer.startElement(HTML.ANCHOR_ELEM, output);
430: HtmlRendererUtils.writeIdAndNameIfNecessary(writer, output,
431: facesContext);
432: writer.writeURIAttribute(HTML.HREF_ATTR, href, null);
433: HtmlRendererUtils
434: .renderHTMLAttributes(
435: writer,
436: output,
437: org.apache.myfaces.shared_impl.renderkit.html.HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES);
438: writer.flush();
439: }
440:
441: private void renderLinkParameter(String name, Object value,
442: StringBuffer onClick, String jsForm, UIComponent nestingForm) {
443: if (name == null) {
444: throw new IllegalArgumentException(
445: "Unnamed parameter value not allowed within command link.");
446: }
447: onClick.append(jsForm);
448: onClick.append(".elements['").append(name).append("']");
449: //UIParameter is no ValueHolder, so no conversion possible
450: String strParamValue = value != null ? org.apache.myfaces.shared_impl.renderkit.html.util.HTMLEncoder
451: .encode(value.toString(), false, false)
452: : "";
453: onClick.append(".value='").append(strParamValue).append("';");
454:
455: addHiddenCommandParameter(FacesContext.getCurrentInstance(),
456: nestingForm, name);
457: }
458:
459: private static void addParameterToHref(String name, Object value,
460: StringBuffer hrefBuf, boolean firstParameter,
461: String charEncoding) throws UnsupportedEncodingException {
462: if (name == null) {
463: throw new IllegalArgumentException(
464: "Unnamed parameter value not allowed within command link.");
465: }
466:
467: hrefBuf.append(firstParameter ? '?' : '&');
468: hrefBuf.append(URLEncoder.encode(name, charEncoding));
469: hrefBuf.append('=');
470: if (value != null) {
471: //UIParameter is no ConvertibleValueHolder, so no conversion possible
472: hrefBuf.append(URLEncoder.encode(value.toString(),
473: charEncoding));
474: }
475: }
476:
477: protected void renderOutputLinkEnd(FacesContext facesContext,
478: UIComponent component) throws IOException {
479: ResponseWriter writer = facesContext.getResponseWriter();
480: // force separate end tag
481: writer.writeText("", null);
482: writer.endElement(HTML.ANCHOR_ELEM);
483: }
484:
485: protected void renderCommandLinkEnd(FacesContext facesContext,
486: UIComponent component) throws IOException {
487: if (HtmlRendererUtils.isDisabled(component)) {
488: ResponseWriter writer = facesContext.getResponseWriter();
489: writer.endElement(HTML.SPAN_ELEM);
490: } else {
491: renderOutputLinkEnd(facesContext, component);
492: }
493: }
494:
495: }
|