4141import com .sun .faces .config .WebConfiguration ;
4242import com .sun .faces .el .ELUtils ;
4343import com .sun .faces .facelets .util .DevTools ;
44+ import com .sun .faces .renderkit .html_basic .ScriptRenderer ;
4445import com .sun .faces .util .FacesLogger ;
4546import com .sun .faces .util .RequestStateManager ;
4647import com .sun .faces .util .Util ;
@@ -143,6 +144,8 @@ public class RenderKitUtils {
143144 */
144145 private static final String ATTRIBUTES_THAT_ARE_SET_KEY = UIComponentBase .class .getName () + ".attributesThatAreSet" ;
145146
147+ private static final String BEHAVIOR_EVENT_ATTRIBUTE_PREFIX = "on" ;
148+
146149 /**
147150 * UIViewRoot attribute key of a boolean value which remembers whether the view will be rendered with a HTML5 doctype.
148151 */
@@ -347,10 +350,10 @@ public static void renderPassThruAttributes(FacesContext context, ResponseWriter
347350 }
348351 }
349352
350- // Renders the onchange handler for input components. Handles
353+ // Renders the onchange event listener for input components. Handles
351354 // chaining together the user-provided onchange handler with
352355 // any Behavior scripts.
353- public static void renderOnchange (FacesContext context , UIComponent component , boolean incExec ) throws IOException {
356+ public static void renderOnchangeEventListener (FacesContext context , UIComponent component , boolean incExec ) throws IOException {
354357
355358 final String handlerName = "onchange" ;
356359 final Object userHandler = component .getAttributes ().get (handlerName );
@@ -369,11 +372,11 @@ public static void renderOnchange(FacesContext context, UIComponent component, b
369372 params = new LinkedList <>();
370373 params .add (new ClientBehaviorContext .Parameter ("incExec" , true ));
371374 }
372- renderHandler (context , component , params , handlerName , userHandler , behaviorEventName , null , false , incExec );
375+ renderHandler (context , component , null , params , handlerName , userHandler , behaviorEventName , "change" , null , false , incExec , true );
373376 }
374377
375- // Renders onclick handler for SelectRaidio and SelectCheckbox
376- public static void renderSelectOnclick (FacesContext context , UIComponent component , boolean incExec ) throws IOException {
378+ // Renders onclick event listener for SelectRadio and SelectCheckbox
379+ public static void renderSelectOnclickEventListener (FacesContext context , UIComponent component , String clientId , boolean incExec ) throws IOException {
377380
378381 final String handlerName = "onclick" ;
379382 final Object userHandler = component .getAttributes ().get (handlerName );
@@ -392,14 +395,16 @@ public static void renderSelectOnclick(FacesContext context, UIComponent compone
392395 params = new LinkedList <>();
393396 params .add (new ClientBehaviorContext .Parameter ("incExec" , true ));
394397 }
395- renderHandler (context , component , params , handlerName , userHandler , behaviorEventName , null , false , incExec );
398+ renderHandler (context , component , clientId , params , handlerName , userHandler , behaviorEventName , "click" , null , false , incExec , true );
396399 }
397400
398- // Renders the onclick handler for command buttons. Handles
401+ // Renders the onclick event listener for command buttons. Handles
399402 // chaining together the user-provided onclick handler, any
400403 // Behavior scripts, plus the default button submit script.
401- public static void renderOnclick (FacesContext context , UIComponent component , Collection <ClientBehaviorContext .Parameter > params , String submitTarget ,
402- boolean needsSubmit ) throws IOException {
404+ public static void renderOnclickEventListener (FacesContext context , UIComponent component ,
405+ Collection <ClientBehaviorContext .Parameter > params ,
406+ String submitTarget ,
407+ boolean needsSubmit ) throws IOException {
403408
404409 final String handlerName = "onclick" ;
405410 final Object userHandler = component .getAttributes ().get (handlerName );
@@ -418,18 +423,17 @@ public static void renderOnclick(FacesContext context, UIComponent component, Co
418423 }
419424 }
420425
421- renderHandler (context , component , params , handlerName , userHandler , behaviorEventName , submitTarget , needsSubmit , false );
426+ renderHandler (context , component , null , params , handlerName , userHandler , behaviorEventName , "click" , submitTarget , needsSubmit , false , true );
422427 }
423428
424- // Renders the script function for command scripts.
429+ // Renders the script element with the function for command scripts.
425430 public static void renderFunction (FacesContext context , UIComponent component , Collection <ClientBehaviorContext .Parameter > params , String submitTarget )
426431 throws IOException {
427432
428433 ClientBehaviorContext behaviorContext = ClientBehaviorContext .createClientBehaviorContext (context , component , "action" , submitTarget , params );
429434 AjaxBehavior behavior = (AjaxBehavior ) context .getApplication ().createBehavior (AjaxBehavior .BEHAVIOR_ID );
430435 mapAttributes (component , behavior , "execute" , "render" , "onerror" , "onevent" , "resetValues" );
431-
432- context .getResponseWriter ().append (behavior .getScript (behaviorContext ));
436+ renderScript (context , component , null , behavior .getScript (behaviorContext ));
433437 }
434438
435439 private static void mapAttributes (UIComponent component , AjaxBehavior behavior , String ... attributeNames ) {
@@ -623,7 +627,19 @@ private static void renderPassThruAttributesOptimized(FacesContext context, Resp
623627 Attribute attr = knownAttributes [index ];
624628
625629 if (isBehaviorEventAttribute (attr , behaviorEventName )) {
626- renderHandler (context , component , null , name , value , behaviorEventName , null , false , false );
630+ renderHandler (context , component , null , null , name , value , behaviorEventName , behaviorEventName , null , false , false , false );
631+
632+ renderedBehavior = true ;
633+ } else {
634+ writer .writeAttribute (prefixAttribute (name , isXhtml ), value , name );
635+ }
636+ }
637+ }
638+ else if (isBehaviorEventAttribute (name )) {
639+ Object value = attrMap .get (name );
640+ if (value != null && shouldRenderAttribute (value )) {
641+ if (name .substring (2 ).equals (behaviorEventName )) {
642+ renderHandler (context , component , null , null , name , value , behaviorEventName , behaviorEventName , null , false , false , false );
627643
628644 renderedBehavior = true ;
629645 } else {
@@ -644,8 +660,8 @@ private static void renderPassThruAttributesOptimized(FacesContext context, Resp
644660 Attribute attr = knownAttributes [i ];
645661 String [] events = attr .getEvents ();
646662 if (events != null && events .length > 0 && behaviorEventName .equals (events [0 ])) {
647-
648- renderHandler ( context , component , null , attr . getName (), null , behaviorEventName , null , false , false ) ;
663+ renderHandler ( context , component , null , null , attr . getName (), null , behaviorEventName , behaviorEventName , null , false , false , false );
664+ return ;
649665 }
650666 }
651667
@@ -683,12 +699,34 @@ private static void renderPassThruAttributesUnoptimized(FacesContext context, Re
683699
684700 // If we've got a behavior for this attribute,
685701 // we may need to chain scripts together, so use
686- // renderHandler ().
687- renderHandler (context , component , null , attrName , value , events [0 ], null , false , false );
702+ // renderEventListener ().
703+ renderHandler (context , component , null , null , attrName , value , events [0 ], events [ 0 ], null , false , false , false );
688704 }
689705 }
690706 }
691707
708+ private static void renderPassthruAttribute (FacesContext context , ResponseWriter writer , UIComponent component ,
709+ Map <String , List <ClientBehavior >> behaviors , boolean isXhtml , Map <String , Object > attrMap , String attrName ,
710+ String eventName ) throws IOException {
711+ boolean hasBehavior = eventName != null && behaviors .containsKey (eventName );
712+
713+ Object value = attrMap .get (attrName );
714+
715+ if (value != null && shouldRenderAttribute (value ) && !hasBehavior ) {
716+ writer .writeAttribute (prefixAttribute (attrName , isXhtml ), value , attrName );
717+ } else if (hasBehavior ) {
718+
719+ // If we've got a behavior for this attribute,
720+ // we may need to chain scripts together, so use
721+ // renderEventListener().
722+ renderHandler (context , component , null , null , attrName , value , eventName , eventName , null , false , false , false );
723+ }
724+ }
725+
726+ public static boolean isBehaviorEventAttribute (String name ) {
727+ return name .startsWith (BEHAVIOR_EVENT_ATTRIBUTE_PREFIX ) && name .length () > 2 ;
728+ }
729+
692730 /**
693731 * <p>
694732 * Determines if an attribute should be rendered based on the specified #attributeVal.
@@ -1492,7 +1530,7 @@ private static String getSubmitHandler(FacesContext context, UIComponent compone
14921530 builder .append ("')" );
14931531
14941532 if (preventDefault ) {
1495- builder .append (";return false " );
1533+ builder .append (";event.preventDefault() " );
14961534 }
14971535
14981536 return builder .toString ();
@@ -1530,10 +1568,10 @@ private static String getChainedHandler(FacesContext context, UIComponent compon
15301568 builder .append (")" );
15311569
15321570 // If we're submitting (either via a behavior, or by rendering
1533- // a submit script), we need to return false to prevent the
1534- // default button/link action.
1571+ // a submit script), we need to prevent the
1572+ // default button/link action event .
15351573 if (submitting && ("action" .equals (behaviorEventName ) || "click" .equals (behaviorEventName ))) {
1536- builder .append (";return false " );
1574+ builder .append (";event.preventDefault() " );
15371575 }
15381576
15391577 return builder .toString ();
@@ -1554,7 +1592,7 @@ private static String getSingleBehaviorHandler(FacesContext context, UIComponent
15541592 script = getSubmitHandler (context , component , params , submitTarget , preventDefault );
15551593 }
15561594 } else if (preventDefault ) {
1557- script = script + ";return false " ;
1595+ script = script + ";event.preventDefault() " ;
15581596 }
15591597
15601598 return script ;
@@ -1583,15 +1621,16 @@ private static boolean isSubmitting(ClientBehavior behavior) {
15831621 * @param handlerValue the user-specified value for the handler attribute
15841622 * @param behaviorEventName the name of the behavior event that corresponds to this handler (eg. "action" or
15851623 * "mouseover").
1624+ * @param domEventName the name of the DOM event that corresponds to this handler (eg. "click" or
1625+ * "change").
15861626 * @param needsSubmit indicates whether the mojarra.cljs() "submit" script is required by the component. Most
15871627 * components do not need this, either because they submit themselves (eg. commandButton), or because they do not
15881628 * perform submits (eg. non-command components). This flag is mainly here for the commandLink case, where we need to
15891629 * render the submit script to make the link submit.
15901630 */
1591- private static void renderHandler (FacesContext context , UIComponent component , Collection <ClientBehaviorContext .Parameter > params , String handlerName ,
1592- Object handlerValue , String behaviorEventName , String submitTarget , boolean needsSubmit , boolean includeExec ) throws IOException {
1631+ private static void renderHandler (FacesContext context , UIComponent component , String clientId , Collection <ClientBehaviorContext .Parameter > params , String handlerName ,
1632+ Object handlerValue , String behaviorEventName , String domEventName , String submitTarget , boolean needsSubmit , boolean includeExec , boolean asEventListener ) throws IOException {
15931633
1594- ResponseWriter writer = context .getResponseWriter ();
15951634 String userHandler = getNonEmptyUserHandler (handlerValue );
15961635 List <ClientBehavior > behaviors = getClientBehaviors (component , behaviorEventName );
15971636
@@ -1625,7 +1664,46 @@ private static void renderHandler(FacesContext context, UIComponent component, C
16251664 assert false ;
16261665 }
16271666
1628- writer .writeAttribute (handlerName , handler , null );
1667+ if (handler != null ) {
1668+ if (asEventListener ) {
1669+ addEventListener (context , component , clientId , domEventName , handler );
1670+ }
1671+ else {
1672+ context .getResponseWriter ().writeAttribute (handlerName , handler , null );
1673+ }
1674+ }
1675+ }
1676+
1677+ public static void addEventListener (FacesContext context , UIComponent component , String clientId , String domEventName , String function ) throws IOException {
1678+ StringBuilder script = new StringBuilder ("mojarra.ael('" )
1679+ .append (clientId != null ? clientId : component .getClientId (context ))
1680+ .append ("','" )
1681+ .append (domEventName )
1682+ .append ("',function(event){" + function + "})" );
1683+
1684+ if (context .getPartialViewContext ().isAjaxRequest ()) {
1685+ context .getPartialViewContext ().getEvalScripts ().add (script .toString ());
1686+ }
1687+ else {
1688+ renderScript (context , component , null , script .toString ());
1689+ }
1690+ }
1691+
1692+ public static void renderScript (FacesContext context , UIComponent component , String clientId , String script ) throws IOException {
1693+ ResponseWriter writer = context .getResponseWriter ();
1694+
1695+ writer .startElement ("script" , component );
1696+
1697+ if (clientId != null ) {
1698+ writer .writeAttribute ("id" , clientId , "id" );
1699+ }
1700+
1701+ if (!RenderKitUtils .isOutputHtml5Doctype (context )) {
1702+ writer .writeAttribute ("type" , ScriptRenderer .DEFAULT_CONTENT_TYPE , "type" );
1703+ }
1704+
1705+ writer .writeText (script , component , null );
1706+ writer .endElement ("script" );
16291707 }
16301708
16311709 // Determines the type of handler to render based on what sorts of
0 commit comments