View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.myfaces.shared.renderkit.html;
20  
21  import java.io.IOException;
22  import java.io.UnsupportedEncodingException;
23  import java.net.URLEncoder;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.RandomAccess;
29  
30  import javax.faces.application.ViewHandler;
31  import javax.faces.component.UICommand;
32  import javax.faces.component.UIComponent;
33  import javax.faces.component.UIOutcomeTarget;
34  import javax.faces.component.UIOutput;
35  import javax.faces.component.UIParameter;
36  import javax.faces.component.behavior.ClientBehavior;
37  import javax.faces.component.behavior.ClientBehaviorContext;
38  import javax.faces.component.behavior.ClientBehaviorHint;
39  import javax.faces.component.behavior.ClientBehaviorHolder;
40  import javax.faces.component.html.HtmlCommandLink;
41  import javax.faces.component.html.HtmlOutputLink;
42  import javax.faces.context.FacesContext;
43  import javax.faces.context.ResponseWriter;
44  import javax.faces.event.ActionEvent;
45  
46  import org.apache.myfaces.shared.config.MyfacesConfig;
47  import org.apache.myfaces.shared.renderkit.ClientBehaviorEvents;
48  import org.apache.myfaces.shared.renderkit.JSFAttr;
49  import org.apache.myfaces.shared.renderkit.RendererUtils;
50  import org.apache.myfaces.shared.renderkit.html.util.FormInfo;
51  import org.apache.myfaces.shared.renderkit.html.util.JavascriptUtils;
52  import org.apache.myfaces.shared.renderkit.html.util.ResourceUtils;
53  import org.apache.myfaces.shared.util._ComponentUtils;
54  
55  /**
56   * @author Manfred Geiler
57   * @version $Revision: 1298100 $ $Date: 2012-03-07 15:44:29 -0500 (Wed, 07 Mar 2012) $
58   */
59  public abstract class HtmlLinkRendererBase
60      extends HtmlRenderer
61  {
62      /* this one is never used
63      public static final String URL_STATE_MARKER      = "JSF_URL_STATE_MARKER=DUMMY";
64      public static final int    URL_STATE_MARKER_LEN  = URL_STATE_MARKER.length();
65      */
66  
67      //private static final Log log = LogFactory.getLog(HtmlLinkRenderer.class);
68      
69      public static final String END_LINK_OUTCOME_AS_SPAN = 
70          "oam.shared.HtmlLinkRendererBase.END_LINK_OUTCOME_AS_SPAN";
71  
72      public boolean getRendersChildren()
73      {
74          // We must be able to render the children without a surrounding anchor
75          // if the Link is disabled
76          return true;
77      }
78  
79      public void decode(FacesContext facesContext, UIComponent component)
80      {
81          super.decode(facesContext, component);  //check for NP
82  
83          if (component instanceof UICommand)
84          {
85              String clientId = component.getClientId(facesContext);
86              FormInfo formInfo = findNestingForm(component, facesContext);
87              if (formInfo != null)
88              {
89                  String reqValue = (String) facesContext.getExternalContext().getRequestParameterMap().get(
90                          HtmlRendererUtils.getHiddenCommandLinkFieldName(formInfo));
91                  if (reqValue != null && reqValue.equals(clientId)
92                      || HtmlRendererUtils.isPartialOrBehaviorSubmit(facesContext, clientId))
93                  {
94                      component.queueEvent(new ActionEvent(component));
95  
96                      RendererUtils.initPartialValidationAndModelUpdate(component, facesContext);
97                  }
98              }
99              if (component instanceof ClientBehaviorHolder &&
100                     !HtmlRendererUtils.isDisabled(component))
101             {
102                 HtmlRendererUtils.decodeClientBehaviors(facesContext, component);
103             }
104         }
105         else if (component instanceof UIOutput)
106         {
107             //do nothing
108             if (component instanceof ClientBehaviorHolder &&
109                     !HtmlRendererUtils.isDisabled(component))
110             {
111                 HtmlRendererUtils.decodeClientBehaviors(facesContext, component);
112             }
113         }
114         else
115         {
116             throw new IllegalArgumentException("Unsupported component class " + component.getClass().getName());
117         }
118     }
119 
120 
121     public void encodeBegin(FacesContext facesContext, UIComponent component) throws IOException
122     {
123         super.encodeBegin(facesContext, component);  //check for NP
124 
125         Map<String, List<ClientBehavior>> behaviors = null;
126         if (component instanceof ClientBehaviorHolder)
127         {
128             behaviors = ((ClientBehaviorHolder) component).getClientBehaviors();
129             if (!behaviors.isEmpty())
130             {
131                 ResourceUtils.renderDefaultJsfJsInlineIfNecessary(facesContext, facesContext.getResponseWriter());
132             }
133         }
134         
135         if (component instanceof UICommand)
136         {
137             renderCommandLinkStart(facesContext, component,
138                                    component.getClientId(facesContext),
139                                    ((UICommand)component).getValue(),
140                                    getStyle(facesContext, component),
141                                    getStyleClass(facesContext, component));
142         }
143         else if (component instanceof UIOutcomeTarget)
144         {
145             renderOutcomeLinkStart(facesContext, (UIOutcomeTarget)component);
146         }        
147         else if (component instanceof UIOutput)
148         {
149             renderOutputLinkStart(facesContext, (UIOutput)component);
150         }
151         else
152         {
153             throw new IllegalArgumentException("Unsupported component class " + component.getClass().getName());
154         }
155     }
156 
157 
158     /**
159      * Can be overwritten by derived classes to overrule the style to be used.
160      */
161     protected String getStyle(FacesContext facesContext, UIComponent link)
162     {
163         if (link instanceof HtmlCommandLink)
164         {
165             return ((HtmlCommandLink)link).getStyle();
166         }
167 
168         return (String)link.getAttributes().get(HTML.STYLE_ATTR);
169 
170     }
171 
172     /**
173      * Can be overwritten by derived classes to overrule the style class to be used.
174      */
175     protected String getStyleClass(FacesContext facesContext, UIComponent link)
176     {
177         if (link instanceof HtmlCommandLink)
178         {
179             return ((HtmlCommandLink)link).getStyleClass();
180         }
181 
182         return (String)link.getAttributes().get(HTML.STYLE_CLASS_ATTR);
183 
184     }
185 
186     public void encodeChildren(FacesContext facesContext, UIComponent component) throws IOException
187     {
188         RendererUtils.renderChildren(facesContext, component);
189     }
190 
191     public void encodeEnd(FacesContext facesContext, UIComponent component) throws IOException
192     {
193         super.encodeEnd(facesContext, component);  //check for NP
194 
195         if (component instanceof UICommand)
196         {
197             renderCommandLinkEnd(facesContext, component);
198 
199             FormInfo formInfo = findNestingForm(component, facesContext);
200             
201             if (formInfo != null)
202             {
203                 HtmlFormRendererBase.renderScrollHiddenInputIfNecessary(
204                         formInfo.getForm(), facesContext, facesContext.getResponseWriter());
205             }
206         }
207         else if (component instanceof UIOutcomeTarget)
208         {
209             renderOutcomeLinkEnd(facesContext, component);
210         }
211         else if (component instanceof UIOutput)
212         {
213             renderOutputLinkEnd(facesContext, component);
214         }
215         else
216         {
217             throw new IllegalArgumentException("Unsupported component class " + component.getClass().getName());
218         }
219     }
220 
221     protected void renderCommandLinkStart(FacesContext facesContext, UIComponent component,
222                                           String clientId,
223                                           Object value,
224                                           String style,
225                                           String styleClass)
226             throws IOException
227     {
228         ResponseWriter writer = facesContext.getResponseWriter();
229         Map<String, List<ClientBehavior>> behaviors = null;
230 
231         // h:commandLink can be rendered outside a form, but with warning (jsf 2.0 TCK)
232         FormInfo formInfo = findNestingForm(component, facesContext);
233         
234         if (HtmlRendererUtils.isDisabled(component) || formInfo == null)
235         {
236             writer.startElement(HTML.SPAN_ELEM, component);
237             if (component instanceof ClientBehaviorHolder && JavascriptUtils.isJavascriptAllowed(
238                     facesContext.getExternalContext()))
239             {
240                 behaviors = ((ClientBehaviorHolder) component).getClientBehaviors();
241                 if (!behaviors.isEmpty())
242                 {
243                     HtmlRendererUtils.writeIdAndName(writer, component, facesContext);
244                 }
245                 else
246                 {
247                     HtmlRendererUtils.writeIdIfNecessary(writer, component, facesContext);
248                 }
249                 long commonPropertiesMarked = 0L;
250                 if (isCommonPropertiesOptimizationEnabled(facesContext))
251                 {
252                     commonPropertiesMarked = CommonPropertyUtils.getCommonPropertiesMarked(component);
253                 }
254                 if (behaviors.isEmpty() && isCommonPropertiesOptimizationEnabled(facesContext))
255                 {
256                     CommonPropertyUtils.renderEventProperties(writer, 
257                             commonPropertiesMarked, component);
258                     CommonPropertyUtils.renderFocusBlurEventProperties(writer,
259                             commonPropertiesMarked, component);
260                 }
261                 else
262                 {
263                     if (isCommonEventsOptimizationEnabled(facesContext))
264                     {
265                         Long commonEventsMarked = CommonEventUtils.getCommonEventsMarked(component);
266                         CommonEventUtils.renderBehaviorizedEventHandlers(facesContext, writer, 
267                                 commonPropertiesMarked, commonEventsMarked, component, behaviors);
268                         CommonEventUtils.renderBehaviorizedFieldEventHandlersWithoutOnchangeAndOnselect(
269                             facesContext, writer, commonPropertiesMarked, commonEventsMarked, component, behaviors);
270                     }
271                     else
272                     {
273                         HtmlRendererUtils.renderBehaviorizedEventHandlers(facesContext, writer, component, behaviors);
274                         HtmlRendererUtils.renderBehaviorizedFieldEventHandlersWithoutOnchangeAndOnselect(
275                                 facesContext, writer, component, behaviors);
276                     }
277                 }
278                 if (isCommonPropertiesOptimizationEnabled(facesContext))
279                 {
280                     CommonPropertyUtils.renderAnchorPassthroughPropertiesWithoutEvents(writer, 
281                             commonPropertiesMarked, component);
282                 }
283                 else
284                 {
285                     HtmlRendererUtils.renderHTMLAttributes(writer, component, 
286                             HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES_WITHOUT_EVENTS);
287                 }
288             }
289             else
290             {
291                 HtmlRendererUtils.writeIdIfNecessary(writer, component, facesContext);
292                 if (isCommonPropertiesOptimizationEnabled(facesContext))
293                 {
294                     CommonPropertyUtils.renderAnchorPassthroughProperties(writer, 
295                             CommonPropertyUtils.getCommonPropertiesMarked(component), component);
296                 }
297                 else
298                 {
299                     HtmlRendererUtils.renderHTMLAttributes(writer, component, HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES);
300                 }
301             }
302         }
303         else
304         {
305             //String[] anchorAttrsToRender;
306             if (JavascriptUtils.isJavascriptAllowed(facesContext.getExternalContext()))
307             {
308                 if (component instanceof ClientBehaviorHolder)
309                 {
310                     behaviors = ((ClientBehaviorHolder) component).getClientBehaviors();
311                     renderBehaviorizedJavaScriptAnchorStart(
312                             facesContext, writer, component, clientId, behaviors, formInfo);
313                     if (!behaviors.isEmpty())
314                     {
315                         HtmlRendererUtils.writeIdAndName(writer, component, facesContext);
316                     }
317                     else
318                     {
319                         HtmlRendererUtils.writeIdIfNecessary(writer, component, facesContext);
320                     }
321                     long commonPropertiesMarked = 0L;
322                     if (isCommonPropertiesOptimizationEnabled(facesContext))
323                     {
324                         commonPropertiesMarked = CommonPropertyUtils.getCommonPropertiesMarked(component);
325                     }
326                     if (behaviors.isEmpty() && isCommonPropertiesOptimizationEnabled(facesContext))
327                     {
328                         CommonPropertyUtils.renderEventPropertiesWithoutOnclick(writer,
329                             commonPropertiesMarked, component);
330                         CommonPropertyUtils.renderFocusBlurEventProperties(writer,
331                                 commonPropertiesMarked, component);
332                     }
333                     else
334                     {
335                         HtmlRendererUtils.renderBehaviorizedEventHandlersWithoutOnclick(
336                                 facesContext, writer, component, behaviors);
337                         HtmlRendererUtils.renderBehaviorizedFieldEventHandlersWithoutOnchangeAndOnselect(
338                                 facesContext, writer, component, behaviors);
339                     }
340                     if (isCommonPropertiesOptimizationEnabled(facesContext))
341                     {
342                         CommonPropertyUtils.renderAnchorPassthroughPropertiesWithoutStyleAndEvents(writer, 
343                                 commonPropertiesMarked, component);
344                     }
345                     else
346                     {
347                         HtmlRendererUtils.renderHTMLAttributes(writer, component, 
348                                 HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES_WITHOUT_STYLE_AND_EVENTS);
349                     }
350                 }
351                 else
352                 {
353                     renderJavaScriptAnchorStart(facesContext, writer, component, clientId, formInfo);
354                     HtmlRendererUtils.writeIdIfNecessary(writer, component, facesContext);
355                     if (isCommonPropertiesOptimizationEnabled(facesContext))
356                     {
357                         CommonPropertyUtils.renderAnchorPassthroughPropertiesWithoutOnclickAndStyle(writer, 
358                                 CommonPropertyUtils.getCommonPropertiesMarked(component), component);
359                     }
360                     else
361                     {
362                         HtmlRendererUtils.renderHTMLAttributes(writer, component, 
363                                 HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES_WITHOUT_ONCLICK_WITHOUT_STYLE);
364                     }
365                 }
366             }
367             else
368             {
369                 renderNonJavaScriptAnchorStart(facesContext, writer, component, clientId, formInfo);
370                 HtmlRendererUtils.writeIdIfNecessary(writer, component, facesContext);
371                 if (isCommonPropertiesOptimizationEnabled(facesContext))
372                 {
373                     CommonPropertyUtils.renderAnchorPassthroughPropertiesWithoutStyle(writer, 
374                             CommonPropertyUtils.getCommonPropertiesMarked(component), component);
375                 }
376                 else
377                 {
378                     HtmlRendererUtils.renderHTMLAttributes(writer, component, 
379                             HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES_WITHOUT_STYLE);
380                 }
381             }
382 
383             //HtmlRendererUtils.renderHTMLAttributes(writer, component,
384             //                                       anchorAttrsToRender);
385             HtmlRendererUtils.renderHTMLAttribute(writer, HTML.STYLE_ATTR, HTML.STYLE_ATTR,
386                                                   style);
387             HtmlRendererUtils.renderHTMLAttribute(writer, HTML.STYLE_CLASS_ATTR, HTML.STYLE_CLASS_ATTR,
388                                                   styleClass);
389         }
390 
391         // render value as required by JSF 1.1 renderkitdocs
392         if(value != null)
393         {
394             writer.writeText(value.toString(), JSFAttr.VALUE_ATTR);
395         }
396         
397         // render warning message for a h:commandLink with no nesting form
398         if (formInfo == null)
399         {
400             writer.writeText(": This link is deactivated, because it is not embedded in a JSF form.", null);
401         }
402     }
403 
404     protected void renderJavaScriptAnchorStart(FacesContext facesContext,
405                                                ResponseWriter writer,
406                                                UIComponent component,
407                                                String clientId,
408                                                FormInfo formInfo)
409         throws IOException
410     {
411         UIComponent nestingForm = formInfo.getForm();
412         String formName = formInfo.getFormName();
413 
414         StringBuilder onClick = new StringBuilder();
415 
416         String commandOnclick;
417         if (component instanceof HtmlCommandLink)
418         {
419             commandOnclick = ((HtmlCommandLink)component).getOnclick();
420         }
421         else
422         {
423             commandOnclick = (String)component.getAttributes().get(HTML.ONCLICK_ATTR);
424         }
425         if (commandOnclick != null)
426         {
427             onClick.append("var cf = function(){");
428             onClick.append(commandOnclick);
429             onClick.append('}');
430             onClick.append(';');
431             onClick.append("var oamSF = function(){");
432         }
433 
434         if (RendererUtils.isAdfOrTrinidadForm(formInfo.getForm()))
435         {
436             onClick.append("submitForm('");
437             onClick.append(formInfo.getForm().getClientId(facesContext));
438             onClick.append("',1,{source:'");
439             onClick.append(component.getClientId(facesContext));
440             onClick.append("'});return false;");
441         }
442         else
443         {
444             HtmlRendererUtils.renderFormSubmitScript(facesContext);
445 
446             StringBuilder params = addChildParameters(facesContext, component, nestingForm);
447 
448             String target = getTarget(component);
449 
450             if (MyfacesConfig.getCurrentInstance(facesContext.getExternalContext()).isRenderFormSubmitScriptInline())
451             {
452                 onClick.append("return ").
453                     append(HtmlRendererUtils.SUBMIT_FORM_FN_NAME).append("('").
454                     append(formName).append("','").
455                     append(clientId).append("'");
456             }
457             else
458             {
459                 onClick.append("return ").
460                     append(HtmlRendererUtils.SUBMIT_FORM_FN_NAME_JSF2).append("('").
461                     append(formName).append("','").
462                     append(clientId).append("'");
463             }
464 
465             if (params.length() > 2 || target != null)
466             {
467                 onClick.append(",").
468                     append(target == null ? "null" : ("'" + target + "'")).append(",").
469                     append(params);
470             }
471             onClick.append(");");
472 
473             //Not necessary since we are using oamSetHiddenInput to create input hidden fields
474             //render hidden field - todo: in here for backwards compatibility
475             if (MyfacesConfig.getCurrentInstance(
476                     facesContext.getExternalContext()).isRenderHiddenFieldsForLinkParams())
477             {
478                 String hiddenFieldName = HtmlRendererUtils.getHiddenCommandLinkFieldName(formInfo);
479                 addHiddenCommandParameter(facesContext, nestingForm, hiddenFieldName);
480             }
481 
482         }
483         
484         if (commandOnclick != null)
485         {
486             onClick.append('}');
487             onClick.append(';');
488             onClick.append("return (cf.apply(this, [])==false)? false : oamSF.apply(this, []); ");
489         }        
490 
491         writer.startElement(HTML.ANCHOR_ELEM, component);
492         writer.writeURIAttribute(HTML.HREF_ATTR, "#", null);
493         writer.writeAttribute(HTML.ONCLICK_ATTR, onClick.toString(), null);
494     }
495 
496     
497     protected void renderBehaviorizedJavaScriptAnchorStart(FacesContext facesContext,
498             ResponseWriter writer,
499             UIComponent component,
500             String clientId,
501             Map<String, List<ClientBehavior>> behaviors,
502             FormInfo formInfo)
503     throws IOException
504     {
505         String commandOnclick;
506         if (component instanceof HtmlCommandLink)
507         {
508             commandOnclick = ((HtmlCommandLink)component).getOnclick();
509         }
510         else
511         {
512             commandOnclick = (String)component.getAttributes().get(HTML.ONCLICK_ATTR);
513         }
514 
515         //Calculate the script necessary to submit form
516         String serverEventCode = buildServerOnclick(facesContext, component, clientId, formInfo);
517         
518         String onclick = null;
519         
520         if (commandOnclick == null && (behaviors.isEmpty() || 
521             (!behaviors.containsKey(ClientBehaviorEvents.CLICK) && 
522              !behaviors.containsKey(ClientBehaviorEvents.ACTION) ) ) )
523         {
524             //we need to render only the submit script
525             onclick = serverEventCode;
526         }
527         else
528         {
529             boolean hasSubmittingBehavior = hasSubmittingBehavior(behaviors, ClientBehaviorEvents.CLICK)
530                 || hasSubmittingBehavior(behaviors, ClientBehaviorEvents.ACTION);
531             if (!hasSubmittingBehavior)
532             {
533                 //Ensure required resource javascript is available
534                 ResourceUtils.renderDefaultJsfJsInlineIfNecessary(facesContext, writer);
535             }
536             
537             //render a javascript that chain the related code
538             Collection<ClientBehaviorContext.Parameter> paramList = 
539                 HtmlRendererUtils.getClientBehaviorContextParameters(
540                     HtmlRendererUtils.mapAttachedParamsToStringValues(facesContext, component));
541             
542             onclick = HtmlRendererUtils.buildBehaviorChain(facesContext, component,
543                     ClientBehaviorEvents.CLICK, paramList, ClientBehaviorEvents.ACTION, paramList, behaviors,
544                     commandOnclick , hasSubmittingBehavior ? null : serverEventCode);
545         }
546         
547         writer.startElement(HTML.ANCHOR_ELEM, component);
548         writer.writeURIAttribute(HTML.HREF_ATTR, "#", null);
549         writer.writeAttribute(HTML.ONCLICK_ATTR, onclick, null);
550     }
551 
552     private boolean hasSubmittingBehavior(Map<String, List<ClientBehavior>> clientBehaviors, String eventName)
553     {
554         List<ClientBehavior> eventBehaviors = clientBehaviors.get(eventName);
555         if (eventBehaviors != null && !eventBehaviors.isEmpty())
556         {
557             // perf: in 99% cases is  eventBehaviors javax.faces.component._DeltaList._DeltaList(int) = RandomAccess
558             // instance created in javax.faces.component.UIComponentBase.addClientBehavior(String, ClientBehavior), but
559             // component libraries can provide own implementation
560             if (eventBehaviors instanceof RandomAccess)
561             {
562                 for (int i = 0, size = eventBehaviors.size(); i < size; i++)
563                 {
564                     ClientBehavior behavior = eventBehaviors.get(i);
565                     if (behavior.getHints().contains(ClientBehaviorHint.SUBMITTING))
566                     {
567                         return true;
568                     }
569                 }
570             }
571             else
572             {
573                 for (ClientBehavior behavior : eventBehaviors)
574                 {
575                     if (behavior.getHints().contains(ClientBehaviorHint.SUBMITTING))
576                     {
577                         return true;
578                     }
579                 }
580             }
581         }
582         return false;
583     }
584 
585     protected String buildServerOnclick(FacesContext facesContext, UIComponent component, 
586             String clientId, FormInfo formInfo) throws IOException
587     {
588         UIComponent nestingForm = formInfo.getForm();
589         String formName = formInfo.getFormName();
590 
591         StringBuilder onClick = new StringBuilder();
592 
593         if (RendererUtils.isAdfOrTrinidadForm(formInfo.getForm()))
594         {
595             onClick.append("submitForm('");
596             onClick.append(formInfo.getForm().getClientId(facesContext));
597             onClick.append("',1,{source:'");
598             onClick.append(component.getClientId(facesContext));
599             onClick.append("'});return false;");
600         }
601         else
602         {
603             HtmlRendererUtils.renderFormSubmitScript(facesContext);
604 
605             StringBuilder params = addChildParameters(facesContext, component, nestingForm);
606 
607             String target = getTarget(component);
608 
609             if (MyfacesConfig.getCurrentInstance(
610                     facesContext.getExternalContext()).isRenderFormSubmitScriptInline())
611             {
612                 onClick.append("return ").
613                     append(HtmlRendererUtils.SUBMIT_FORM_FN_NAME).append("('").
614                     append(formName).append("','").
615                     append(clientId).append("'");
616             }
617             else
618             {
619                 onClick.append("return ").
620                     append(HtmlRendererUtils.SUBMIT_FORM_FN_NAME_JSF2).append("('").
621                     append(formName).append("','").
622                     append(clientId).append("'");
623             }
624 
625             if (params.length() > 2 || target != null)
626             {
627                 onClick.append(",").
628                     append(target == null ? "null" : ("'" + target + "'")).append(",").
629                     append(params);
630             }
631             onClick.append(");");
632 
633             //Not necessary since we are using oamSetHiddenInput to create input hidden fields
634             //render hidden field - todo: in here for backwards compatibility
635             //String hiddenFieldName = HtmlRendererUtils.getHiddenCommandLinkFieldName(formInfo);
636             //addHiddenCommandParameter(facesContext, nestingForm, hiddenFieldName);
637 
638         }
639         return onClick.toString();
640     }
641 
642     private String getTarget(UIComponent component)
643     {
644         // for performance reason: double check for the target attribute
645         String target;
646         if (component instanceof HtmlCommandLink)
647         {
648             target = ((HtmlCommandLink) component).getTarget();
649         }
650         else
651         {
652             target = (String) component.getAttributes().get(HTML.TARGET_ATTR);
653         }
654         return target;
655     }
656 
657     private StringBuilder addChildParameters(FacesContext context, UIComponent component, UIComponent nestingForm)
658     {
659         //add child parameters
660         StringBuilder params = new StringBuilder();
661         params.append("[");
662         
663         List<UIComponent> childrenList = null;
664         if (getChildCount(component) > 0)
665         {
666             childrenList = getChildren(component);
667         }
668         else
669         {
670            childrenList = Collections.emptyList();
671         }
672         List<UIParameter> validParams = HtmlRendererUtils.getValidUIParameterChildren(
673                 context, childrenList, false, false);
674         for (int j = 0, size = validParams.size(); j < size; j++) 
675         {
676             UIParameter param = validParams.get(j);
677             String name = param.getName();
678 
679             //Not necessary, since we are using oamSetHiddenInput to create hidden fields
680             if (MyfacesConfig.getCurrentInstance(context.getExternalContext()).isRenderHiddenFieldsForLinkParams())
681             {
682                 addHiddenCommandParameter(context, nestingForm, name);
683             }
684 
685             Object value = param.getValue();
686 
687             //UIParameter is no ValueHolder, so no conversion possible - calling .toString on value....
688             // MYFACES-1832 bad charset encoding for f:param
689             // if HTMLEncoder.encode is called, then
690             // when is called on writer.writeAttribute, encode method
691             // is called again so we have a duplicated encode call.
692             // MYFACES-2726 All '\' and "'" chars must be escaped 
693             // because there will be inside "'" javascript quotes, 
694             // otherwise the value will not correctly restored when
695             // the command is post.
696             //String strParamValue = value != null ? value.toString() : "";
697             String strParamValue = "";
698             if (value != null)
699             {
700                 strParamValue = value.toString();
701                 StringBuilder buff = null;
702                 for (int i = 0; i < strParamValue.length(); i++)
703                 {
704                     char c = strParamValue.charAt(i); 
705                     if (c == '\'' || c == '\\')
706                     {
707                         if (buff == null)
708                         {
709                             buff = new StringBuilder();
710                             buff.append(strParamValue.substring(0,i));
711                         }
712                         buff.append('\\');
713                         buff.append(c);
714                     }
715                     else if (buff != null)
716                     {
717                         buff.append(c);
718                     }
719                 }
720                 if (buff != null)
721                 {
722                     strParamValue = buff.toString();
723                 }
724             }
725 
726             if (params.length() > 1) 
727             {
728                 params.append(",");
729             }
730 
731             params.append("['");
732             params.append(name);
733             params.append("','");
734             params.append(strParamValue);
735             params.append("']");
736         }
737         params.append("]");
738         return params;
739     }
740 
741     /**
742      * find nesting form<br />
743      * need to be overrideable to deal with dummyForm stuff in tomahawk.
744      */
745     protected FormInfo findNestingForm(UIComponent uiComponent, FacesContext facesContext)
746     {
747         return _ComponentUtils.findNestingForm(uiComponent, facesContext);
748     }
749 
750     protected void addHiddenCommandParameter(
751             FacesContext facesContext, UIComponent nestingForm, String hiddenFieldName)
752     {
753         if (nestingForm != null)
754         {
755             HtmlFormRendererBase.addHiddenCommandParameter(facesContext, nestingForm, hiddenFieldName);
756         }
757     }
758 
759 
760     protected void renderNonJavaScriptAnchorStart(FacesContext facesContext,
761                                                   ResponseWriter writer,
762                                                   UIComponent component,
763                                                   String clientId,
764                                                   FormInfo formInfo)
765         throws IOException
766     {
767         ViewHandler viewHandler = facesContext.getApplication().getViewHandler();
768         String viewId = facesContext.getViewRoot().getViewId();
769         String path = viewHandler.getActionURL(facesContext, viewId);
770 
771         boolean strictXhtmlLinks
772                 = MyfacesConfig.getCurrentInstance(facesContext.getExternalContext()).isStrictXhtmlLinks();
773 
774         StringBuilder hrefBuf = new StringBuilder(path);
775 
776         //add clientId parameter for decode
777 
778         if (path.indexOf('?') == -1)
779         {
780             hrefBuf.append('?');
781         }
782         else
783         {
784             if (strictXhtmlLinks)
785             {
786                 hrefBuf.append("&amp;");
787             }
788             else
789             {
790                 hrefBuf.append('&');
791             }
792         }
793         String hiddenFieldName = HtmlRendererUtils.getHiddenCommandLinkFieldName(formInfo);
794         hrefBuf.append(hiddenFieldName);
795         hrefBuf.append('=');
796         hrefBuf.append(clientId);
797 
798         if (getChildCount(component) > 0)
799         {
800             addChildParametersToHref(facesContext, component, hrefBuf,
801                                      false, //not the first url parameter
802                                      writer.getCharacterEncoding());
803         }
804 
805         String href = facesContext.getExternalContext().encodeActionURL(hrefBuf.toString());
806         writer.startElement(HTML.ANCHOR_ELEM, component);
807         writer.writeURIAttribute(HTML.HREF_ATTR,
808                                  facesContext.getExternalContext().encodeActionURL(href),
809                                  null);
810     }
811 
812     private void addChildParametersToHref(FacesContext facesContext,
813                                           UIComponent linkComponent,
814                                           StringBuilder hrefBuf,
815                                           boolean firstParameter,
816                                           String charEncoding)
817             throws IOException
818     {
819         boolean strictXhtmlLinks
820                 = MyfacesConfig.getCurrentInstance(facesContext.getExternalContext()).isStrictXhtmlLinks();
821         List<UIComponent> childrenList = null;
822         if (getChildCount(linkComponent) > 0)
823         {
824             childrenList = getChildren(linkComponent);
825         }
826         else
827         {
828            childrenList = Collections.emptyList();
829         }
830         List<UIParameter> validParams = HtmlRendererUtils.getValidUIParameterChildren(
831                 facesContext, childrenList, false, false);
832         
833         for (int i = 0, size = validParams.size(); i < size; i++)
834         {
835             UIParameter param = validParams.get(i);
836             String name = param.getName();
837             Object value = param.getValue();
838             addParameterToHref(name, value, hrefBuf, firstParameter, charEncoding, strictXhtmlLinks);
839             firstParameter = false;
840         }
841     }
842 
843     protected void renderOutputLinkStart(FacesContext facesContext, UIOutput output)
844             throws IOException
845     {
846         ResponseWriter writer = facesContext.getResponseWriter();
847         Map<String, List<ClientBehavior>> behaviors = null;
848 
849         if (HtmlRendererUtils.isDisabled(output))
850         {
851             writer.startElement(HTML.SPAN_ELEM, output);
852             if (output instanceof ClientBehaviorHolder && JavascriptUtils.isJavascriptAllowed(
853                     facesContext.getExternalContext()))
854             {
855                 behaviors = ((ClientBehaviorHolder) output).getClientBehaviors();
856                 if (!behaviors.isEmpty())
857                 {
858                     HtmlRendererUtils.writeIdAndName(writer, output, facesContext);
859                 }
860                 else
861                 {
862                     HtmlRendererUtils.writeIdIfNecessary(writer, output, facesContext);
863                 }
864                 long commonPropertiesMarked = 0L;
865                 if (isCommonPropertiesOptimizationEnabled(facesContext))
866                 {
867                     commonPropertiesMarked = CommonPropertyUtils.getCommonPropertiesMarked(output);
868                 }
869 
870                 if (behaviors.isEmpty() && isCommonPropertiesOptimizationEnabled(facesContext))
871                 {
872                     CommonPropertyUtils.renderEventProperties(writer, 
873                             commonPropertiesMarked, output);
874                     CommonPropertyUtils.renderFocusBlurEventProperties(writer,
875                             commonPropertiesMarked, output);
876                 }
877                 else
878                 {
879                     if (isCommonEventsOptimizationEnabled(facesContext))
880                     {
881                         Long commonEventsMarked = CommonEventUtils.getCommonEventsMarked(output);
882                         CommonEventUtils.renderBehaviorizedEventHandlers(facesContext, writer, 
883                                 commonPropertiesMarked, commonEventsMarked, output, behaviors);
884                         CommonEventUtils.renderBehaviorizedFieldEventHandlersWithoutOnchangeAndOnselect(
885                             facesContext, writer, commonPropertiesMarked, commonEventsMarked, output, behaviors);
886                     }
887                     else
888                     {
889                         HtmlRendererUtils.renderBehaviorizedEventHandlers(facesContext, writer, output, behaviors);
890                         HtmlRendererUtils.renderBehaviorizedFieldEventHandlersWithoutOnchangeAndOnselect(
891                                 facesContext, writer, output, behaviors);
892                     }
893                 }
894                 if (isCommonPropertiesOptimizationEnabled(facesContext))
895                 {
896                     CommonPropertyUtils.renderAnchorPassthroughPropertiesWithoutEvents(writer, 
897                             commonPropertiesMarked, output);
898                 }
899                 else
900                 {
901                     HtmlRendererUtils.renderHTMLAttributes(writer, output, 
902                             HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES_WITHOUT_EVENTS);
903                 }
904             }
905             else
906             {
907                 HtmlRendererUtils.writeIdIfNecessary(writer, output, facesContext);
908                 if (isCommonPropertiesOptimizationEnabled(facesContext))
909                 {
910                     CommonPropertyUtils.renderAnchorPassthroughProperties(writer, 
911                             CommonPropertyUtils.getCommonPropertiesMarked(output), output);
912                 }
913                 else
914                 {
915                     HtmlRendererUtils.renderHTMLAttributes(writer, output, HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES);
916                 }
917             }
918         }
919         else
920         { 
921             //calculate href
922             String href = org.apache.myfaces.shared.renderkit.RendererUtils.getStringValue(facesContext, output);
923             
924             //check if there is an anchor # in it
925             int index = href.indexOf('#');
926             String anchorString = null;
927             boolean isAnchorInHref = (index > -1);
928             if (isAnchorInHref)
929             {
930                 // remove anchor element and add it again after the parameter are encoded
931                 anchorString = href.substring(index,href.length());
932                 href = href.substring(0,index);
933             }
934             if (getChildCount(output) > 0)
935             {
936                 StringBuilder hrefBuf = new StringBuilder(href);
937                 addChildParametersToHref(facesContext, output, hrefBuf,
938                                      (href.indexOf('?') == -1), //first url parameter?
939                                      writer.getCharacterEncoding());
940                 href = hrefBuf.toString();
941             }
942             // check for the fragement attribute
943             String fragmentAttr = null;
944             if (output instanceof HtmlOutputLink)
945             {
946                 fragmentAttr = ((HtmlOutputLink) output).getFragment();
947             }
948             else
949             {
950                 fragmentAttr = (String) output.getAttributes().get(JSFAttr.FRAGMENT_ATTR);
951             }
952             if (fragmentAttr != null && !"".equals(fragmentAttr)) 
953             {
954                 href += "#" + fragmentAttr;
955             }
956             else if (isAnchorInHref)
957             {
958                 href += anchorString;
959             }
960             href = facesContext.getExternalContext().encodeResourceURL(href);    //TODO: or encodeActionURL ?
961 
962             //write anchor
963             writer.startElement(HTML.ANCHOR_ELEM, output);
964             writer.writeURIAttribute(HTML.HREF_ATTR, href, null);
965             if (output instanceof ClientBehaviorHolder && JavascriptUtils.isJavascriptAllowed(
966                     facesContext.getExternalContext()))
967             {
968                 behaviors = ((ClientBehaviorHolder) output).getClientBehaviors();
969                 if (!behaviors.isEmpty())
970                 {
971                     HtmlRendererUtils.writeIdAndName(writer, output, facesContext);
972                 }
973                 else
974                 {
975                     HtmlRendererUtils.writeIdAndNameIfNecessary(writer, output, facesContext);
976                 }
977                 long commonPropertiesMarked = 0L;
978                 if (isCommonPropertiesOptimizationEnabled(facesContext))
979                 {
980                     commonPropertiesMarked = CommonPropertyUtils.getCommonPropertiesMarked(output);
981                 }
982                 if (behaviors.isEmpty() && isCommonPropertiesOptimizationEnabled(facesContext))
983                 {
984                     CommonPropertyUtils.renderEventProperties(writer, 
985                             commonPropertiesMarked, output);
986                     CommonPropertyUtils.renderFocusBlurEventProperties(writer,
987                             commonPropertiesMarked, output);
988                 }
989                 else
990                 {
991                     if (isCommonEventsOptimizationEnabled(facesContext))
992                     {
993                         Long commonEventsMarked = CommonEventUtils.getCommonEventsMarked(output);
994                         CommonEventUtils.renderBehaviorizedEventHandlers(facesContext, writer, 
995                                 commonPropertiesMarked, commonEventsMarked, output, behaviors);
996                         CommonEventUtils.renderBehaviorizedFieldEventHandlersWithoutOnchangeAndOnselect(
997                             facesContext, writer, commonPropertiesMarked, commonEventsMarked, output, behaviors);
998                     }
999                     else
1000                     {
1001                         HtmlRendererUtils.renderBehaviorizedEventHandlers(facesContext, writer, output, behaviors);
1002                         HtmlRendererUtils.renderBehaviorizedFieldEventHandlersWithoutOnchangeAndOnselect(
1003                                 facesContext, writer, output, behaviors);
1004                     }
1005                 }
1006                 if (isCommonPropertiesOptimizationEnabled(facesContext))
1007                 {
1008                     CommonPropertyUtils.renderAnchorPassthroughPropertiesWithoutEvents(writer, 
1009                             commonPropertiesMarked, output);
1010                 }
1011                 else
1012                 {
1013                     HtmlRendererUtils.renderHTMLAttributes(writer, output, 
1014                             HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES_WITHOUT_EVENTS);
1015                 }
1016             }
1017             else
1018             {
1019                 HtmlRendererUtils.writeIdAndNameIfNecessary(writer, output, facesContext);
1020                 if (isCommonPropertiesOptimizationEnabled(facesContext))
1021                 {
1022                     CommonPropertyUtils.renderAnchorPassthroughProperties(writer, 
1023                             CommonPropertyUtils.getCommonPropertiesMarked(output), output);
1024                 }
1025                 else
1026                 {
1027                     HtmlRendererUtils.renderHTMLAttributes(writer, output, HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES);
1028                 }
1029             }
1030             writer.flush();
1031         }
1032     }
1033     
1034     protected void renderOutcomeLinkStart(FacesContext facesContext, UIOutcomeTarget output)
1035             throws IOException
1036     {
1037         ResponseWriter writer = facesContext.getResponseWriter();
1038         Map<String, List<ClientBehavior>> behaviors = null;
1039         
1040         //calculate href
1041         String targetHref = HtmlRendererUtils.getOutcomeTargetHref(facesContext, output);
1042         
1043         if (HtmlRendererUtils.isDisabled(output) || targetHref == null)
1044         {
1045             //output.getAttributes().put(END_LINK_OUTCOME_AS_SPAN, Boolean.TRUE);
1046             //Note one h:link cannot have a nested h:link as a child, so it is safe
1047             //to just put this flag on FacesContext attribute map
1048             facesContext.getAttributes().put(END_LINK_OUTCOME_AS_SPAN, Boolean.TRUE);
1049             writer.startElement(HTML.SPAN_ELEM, output);
1050             if (output instanceof ClientBehaviorHolder && JavascriptUtils.isJavascriptAllowed(
1051                     facesContext.getExternalContext()))
1052             {
1053                 behaviors = ((ClientBehaviorHolder) output).getClientBehaviors();
1054                 if (!behaviors.isEmpty())
1055                 {
1056                     HtmlRendererUtils.writeIdAndName(writer, output, facesContext);
1057                 }
1058                 else
1059                 {
1060                     HtmlRendererUtils.writeIdIfNecessary(writer, output, facesContext);
1061                 }
1062                 long commonPropertiesMarked = 0L;
1063                 if (isCommonPropertiesOptimizationEnabled(facesContext))
1064                 {
1065                     commonPropertiesMarked = CommonPropertyUtils.getCommonPropertiesMarked(output);
1066                 }
1067                 if (behaviors.isEmpty() && isCommonPropertiesOptimizationEnabled(facesContext))
1068                 {
1069                     CommonPropertyUtils.renderEventProperties(writer, 
1070                             commonPropertiesMarked, output);
1071                     CommonPropertyUtils.renderFocusBlurEventProperties(writer,
1072                             commonPropertiesMarked, output);
1073                 }
1074                 else
1075                 {
1076                     if (isCommonEventsOptimizationEnabled(facesContext))
1077                     {
1078                         Long commonEventsMarked = CommonEventUtils.getCommonEventsMarked(output);
1079                         CommonEventUtils.renderBehaviorizedEventHandlers(facesContext, writer, 
1080                                 commonPropertiesMarked, commonEventsMarked, output, behaviors);
1081                         CommonEventUtils.renderBehaviorizedFieldEventHandlersWithoutOnchangeAndOnselect(
1082                             facesContext, writer, commonPropertiesMarked, commonEventsMarked, output, behaviors);
1083                     }
1084                     else
1085                     {
1086                         HtmlRendererUtils.renderBehaviorizedEventHandlers(facesContext, writer, output, behaviors);
1087                         HtmlRendererUtils.renderBehaviorizedFieldEventHandlersWithoutOnchangeAndOnselect(
1088                                 facesContext, writer, output, behaviors);
1089                     }
1090                 }
1091                 if (isCommonPropertiesOptimizationEnabled(facesContext))
1092                 {
1093                     CommonPropertyUtils.renderAnchorPassthroughPropertiesWithoutEvents(writer, 
1094                             commonPropertiesMarked, output);
1095                 }
1096                 else
1097                 {
1098                     HtmlRendererUtils.renderHTMLAttributes(writer, output, 
1099                             HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES_WITHOUT_EVENTS);
1100                 }
1101             }
1102             else
1103             {
1104                 HtmlRendererUtils.writeIdIfNecessary(writer, output, facesContext);
1105                 if (isCommonPropertiesOptimizationEnabled(facesContext))
1106                 {
1107                     CommonPropertyUtils.renderAnchorPassthroughProperties(writer, 
1108                             CommonPropertyUtils.getCommonPropertiesMarked(output), output);
1109                 }
1110                 else
1111                 {
1112                     HtmlRendererUtils.renderHTMLAttributes(writer, output, HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES);
1113                 }
1114             }
1115 
1116             Object value = output.getValue();
1117 
1118             if(value != null)
1119             {
1120                 writer.writeText(value.toString(), JSFAttr.VALUE_ATTR);
1121             }
1122         }
1123         else
1124         {
1125             //write anchor
1126             writer.startElement(HTML.ANCHOR_ELEM, output);
1127             writer.writeURIAttribute(HTML.HREF_ATTR, targetHref, null);
1128             if (output instanceof ClientBehaviorHolder && JavascriptUtils.isJavascriptAllowed(
1129                     facesContext.getExternalContext()))
1130             {
1131                 behaviors = ((ClientBehaviorHolder) output).getClientBehaviors();
1132                 if (!behaviors.isEmpty())
1133                 {
1134                     HtmlRendererUtils.writeIdAndName(writer, output, facesContext);
1135                 }
1136                 else
1137                 {
1138                     HtmlRendererUtils.writeIdAndNameIfNecessary(writer, output, facesContext);
1139                 }
1140                 long commonPropertiesMarked = 0L;
1141                 if (isCommonPropertiesOptimizationEnabled(facesContext))
1142                 {
1143                     commonPropertiesMarked = CommonPropertyUtils.getCommonPropertiesMarked(output);
1144                 }
1145                 if (behaviors.isEmpty() && isCommonPropertiesOptimizationEnabled(facesContext))
1146                 {
1147                     CommonPropertyUtils.renderEventProperties(writer, 
1148                             commonPropertiesMarked, output);
1149                     CommonPropertyUtils.renderFocusBlurEventProperties(writer,
1150                             commonPropertiesMarked, output);
1151                 }
1152                 else
1153                 {
1154                     if (isCommonEventsOptimizationEnabled(facesContext))
1155                     {
1156                         Long commonEventsMarked = CommonEventUtils.getCommonEventsMarked(output);
1157                         CommonEventUtils.renderBehaviorizedEventHandlers(facesContext, writer, 
1158                                 commonPropertiesMarked, commonEventsMarked, output, behaviors);
1159                         CommonEventUtils.renderBehaviorizedFieldEventHandlersWithoutOnchangeAndOnselect(
1160                             facesContext, writer, commonPropertiesMarked, commonEventsMarked, output, behaviors);
1161                     }
1162                     else
1163                     {
1164                         HtmlRendererUtils.renderBehaviorizedEventHandlers(facesContext, writer, output, behaviors);
1165                         HtmlRendererUtils.renderBehaviorizedFieldEventHandlersWithoutOnchangeAndOnselect(
1166                                 facesContext, writer, output, behaviors);
1167                     }
1168                 }
1169                 if (isCommonPropertiesOptimizationEnabled(facesContext))
1170                 {
1171                     CommonPropertyUtils.renderAnchorPassthroughPropertiesWithoutEvents(writer, 
1172                             commonPropertiesMarked, output);
1173                 }
1174                 else
1175                 {
1176                     HtmlRendererUtils.renderHTMLAttributes(writer, output, 
1177                             HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES_WITHOUT_EVENTS);
1178                 }
1179             }
1180             else
1181             {
1182                 HtmlRendererUtils.writeIdAndNameIfNecessary(writer, output, facesContext);
1183                 if (isCommonPropertiesOptimizationEnabled(facesContext))
1184                 {
1185                     CommonPropertyUtils.renderAnchorPassthroughProperties(writer, 
1186                             CommonPropertyUtils.getCommonPropertiesMarked(output), output);
1187                 }
1188                 else
1189                 {
1190                     HtmlRendererUtils.renderHTMLAttributes(writer, output, 
1191                             HTML.ANCHOR_PASSTHROUGH_ATTRIBUTES);
1192                 }
1193             }
1194 
1195             writer.flush();
1196         }
1197     }
1198     
1199 
1200     private void renderLinkParameter(String name,
1201                                      Object value,
1202                                      StringBuilder onClick,
1203                                      String jsForm,
1204                                      UIComponent nestingForm)
1205     {
1206         if (name == null)
1207         {
1208             throw new IllegalArgumentException("Unnamed parameter value not allowed within command link.");
1209         }
1210         onClick.append(jsForm);
1211         onClick.append(".elements['").append(name).append("']");
1212         //UIParameter is no ValueHolder, so no conversion possible
1213         String strParamValue = value != null ? org.apache.myfaces.shared.renderkit.html.util.HTMLEncoder.encode(
1214                 value.toString(), false, false) : "";
1215         onClick.append(".value='").append(strParamValue).append("';");
1216 
1217         addHiddenCommandParameter(FacesContext.getCurrentInstance(), nestingForm, name);
1218     }
1219 
1220     private static void addParameterToHref(String name,
1221                                            Object value,
1222                                            StringBuilder hrefBuf,
1223                                            boolean firstParameter,
1224                                            String charEncoding,
1225                                            boolean strictXhtmlLinks) throws UnsupportedEncodingException
1226     {
1227         if (name == null)
1228         {
1229             throw new IllegalArgumentException("Unnamed parameter value not allowed within command link.");
1230         }
1231 
1232         if (firstParameter)
1233         {
1234             hrefBuf.append('?');
1235         }
1236         else
1237         {
1238             if (strictXhtmlLinks)
1239             {
1240                 hrefBuf.append("&amp;");
1241             }
1242             else
1243             {
1244                 hrefBuf.append('&');
1245             }
1246         }
1247 
1248         hrefBuf.append(URLEncoder.encode(name, charEncoding));
1249         hrefBuf.append('=');
1250         if (value != null)
1251         {
1252             //UIParameter is no ConvertibleValueHolder, so no conversion possible
1253             hrefBuf.append(URLEncoder.encode(value.toString(), charEncoding));
1254         }
1255     }
1256 
1257     protected void renderOutcomeLinkEnd(FacesContext facesContext, UIComponent component)
1258             throws IOException
1259     {
1260         ResponseWriter writer = facesContext.getResponseWriter();
1261         
1262         if (HtmlRendererUtils.isDisabled(component) || Boolean.TRUE.equals(
1263                 facesContext.getAttributes().get(END_LINK_OUTCOME_AS_SPAN)))
1264         {
1265             writer.endElement(HTML.SPAN_ELEM);
1266             facesContext.getAttributes().put(END_LINK_OUTCOME_AS_SPAN, Boolean.FALSE);
1267         }
1268         else
1269         {
1270             writer.writeText (org.apache.myfaces.shared.renderkit.RendererUtils.getStringValue
1271                  (facesContext, component), null);
1272             writer.endElement(HTML.ANCHOR_ELEM);
1273         }
1274     }
1275     
1276     protected void renderOutputLinkEnd(FacesContext facesContext, UIComponent component)
1277             throws IOException
1278     {
1279         ResponseWriter writer = facesContext.getResponseWriter();
1280 
1281         if (HtmlRendererUtils.isDisabled(component))
1282         {
1283             writer.endElement(HTML.SPAN_ELEM);
1284         }
1285         else
1286         {
1287             // force separate end tag
1288             writer.writeText("", null);
1289             writer.endElement(HTML.ANCHOR_ELEM);
1290         }
1291     }
1292 
1293     protected void renderCommandLinkEnd(FacesContext facesContext, UIComponent component)
1294             throws IOException
1295     {
1296         FormInfo formInfo = findNestingForm(component, facesContext);
1297         
1298         ResponseWriter writer = facesContext.getResponseWriter();
1299         if (HtmlRendererUtils.isDisabled(component) || formInfo == null)
1300         {
1301 
1302             writer.endElement(HTML.SPAN_ELEM);
1303         }
1304         else
1305         {
1306             writer.writeText("", null);
1307             writer.endElement(HTML.ANCHOR_ELEM);
1308         }
1309     }
1310 }