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