3

I am busy writing my own JSF2 UIComponents and their relevant renderers. All of my UIComponents implements ClientBehaviorHolder. What I don't understand is how to really render ClientBehaviorHolder.

For example, the following code illustrates how ClientBehaviorHolder is rendered in Mojarra.

private static void renderHandler(FacesContext context,
                                  UIComponent component,
                                  Collection<ClientBehaviorContext.Parameter> params,
                                  String handlerName,
                                  Object handlerValue,
                                  String behaviorEventName,
                                  String submitTarget,
                                  boolean needsSubmit,
                                  boolean includeExec)
    throws IOException {

    ResponseWriter writer = context.getResponseWriter();
    String userHandler = getNonEmptyUserHandler(handlerValue);
    List<ClientBehavior> behaviors = getClientBehaviors(component, behaviorEventName);

    // Don't render behavior scripts if component is disabled
    if ((null != behaviors) && 
        (behaviors.size() > 0) && 
         Util.componentIsDisabled(component)) {
        behaviors = null;
    }

    if (params == null) {
        params = Collections.emptyList();
    }
    String handler = null;
    switch (getHandlerType(behaviors, params, userHandler, needsSubmit, includeExec)) {

        case USER_HANDLER_ONLY:
            handler = userHandler;
            break;

        case SINGLE_BEHAVIOR_ONLY:
            handler = getSingleBehaviorHandler(context, 
                                               component,
                                               behaviors.get(0),
                                               params,
                                               behaviorEventName,
                                               submitTarget,
                                               needsSubmit);
            break;

        case SUBMIT_ONLY:
            handler = getSubmitHandler(context, 
                                       component,
                                       params,
                                       submitTarget,
                                       true);
            break;

        case CHAIN:
            handler = getChainedHandler(context,
                                        component,
                                        behaviors,
                                        params,
                                        behaviorEventName,
                                        userHandler,
                                        submitTarget,
                                        needsSubmit);
            break;
        default:
            assert(false);
    }


    writer.writeAttribute(handlerName, handler, null);
}

For submit handlers, Mojarra adds the mojarra.jsfcljs javascript, UIParameters and other scripts. For chain handlers, jsf.util.chain is used.

My question is:

  • How does one determine if we have to render handlers in chain or a single behaviour or user specific handler?
  • mojarra.jsfcljs is only unique to Mojarra. PrimeFaces have their own implementation, so does Apache Tomahawk. Question is: what does mojarra.jsfcljs do and what is its use? This is so that I can write one for my own? Also, where can I find the implementation of mojarra.jsfcljs?
  • What is the specification to render ClientBehaviorHolder?

My sincere thanks in advance.

Buhake Sindi
  • 87,898
  • 29
  • 167
  • 228

1 Answers1

6

How does one determine if we have to render handlers in chain or a single behaviour or user specific handler?

Imagine that the enduser (read: the JSF developer who's using your component) programmed:

<your:component onclick="return foo()" />

And you intented to ultimately render for your component's own purpose:

<someHtmlElement onclick="jsf.ajax.request(...); return false;" />

Then you can't just concatenate the enduser's onclick in front of your component's jsf.ajax.request() like so

<someHtmlElement onclick="return foo(); jsf.ajax.request(...); return false;" />

Even if it returned true, your component's jsf.ajax.request won't be invoked at all. You ultimately want to end up something like:

<someHtmlElement onclick="if returnsTrue('return foo();') { jsf.ajax.request(...); } return false;" />

That's exactly what jsf.util.chain() is doing under the covers.


mojarra.jsfcljs is only unique to Mojarra. PrimeFaces have their own implementation, so does Apache Tomahawk. Question is: what does mojarra.jsfcljs do and what is its use? This is so that I can write one for my own? Also, where can I find the implementation of mojarra.jsfcljs?

It's inside the jsf.js file. Easy way to find it is to open a JSF page with <f:ajax> embedded and look in the generated <head> source for the <script> with its URL. This file is by default minified. If you set javax.faces.PROJECT_STAGE context param to Development, then this will be served unminified. The task of the jsfcljs() function is to submit the parent form with the necessary parameters. Here's an extract of relevance coming from Mojarra 2.1.21.

/*
 * This is called by command link and command button.  It provides
 * the form it is nested in, the parameters that need to be
 * added and finally, the target of the action.  This function
 * will delete any parameters added <em>after</em> the form
 * has been submitted to handle DOM caching issues.
 *
 * @param f - the target form
 * @param pvp - associative array of parameter
 *  key/value pairs to be added to the form as hidden input
 *  fields.
 * @param t - the target of the form submission
 */
mojarra.jsfcljs = function jsfcljs(f, pvp, t) {

What is the specification to render ClientBehaviorHolder?

Use ClientBehavior#getScript() to get the autogenerated script. It requires a ClientBehaviorContext argument which can be created using ClientBehaviorContext#createClientBehaviorContext(). It's in turn your responsibility to render it into the appropriate HTML attribute, such as onclick.

FacesContext context = FacesContext.getCurrentInstance();
UIComponent inputOrCommandComponent = ...; // Your component.
String event = "click"; // Just the particular HTML DOM event name you need to listen on.

ClientBehaviorContext clientBehaviorContext = ClientBehaviorContext.createClientBehaviorContext(context, component, event, component.getClientId(context), null);
StringBuilder builder = new StringBuilder();

for (ClientBehavior behavior : component.getClientBehaviors().get(event)) { // Collect all <f:ajax> declarations on the given event.
    builder.append(behavior.getScript(clientBehaviorContext));
    builder.append(';');
}

String script = builder.toString();
// Write it to the desired HTML attribute.

Note that you absolutely don't need to worry about writing JSF implementation specific scripts this way. They will be generated for you.

All with all, ClientBehaviorHolder is just an abstraction of ajax support. It allows developers to nest <f:ajax> in your component. All standard JSF UIInput and UICommand components implement it.

Buhake Sindi
  • 87,898
  • 29
  • 167
  • 228
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • thanks. I was just waiting for you to wake up! lol. So, `jsf.util.chain()` is standard in the JSF API or should the framework implementer must write a `jsf.util.chain()` JS code? – Buhake Sindi Jun 29 '13 at 20:10
  • Nope, `jsf` namespace is part of API. See also [jsdoc](https://javaserverfaces.java.net/nonav/docs/2.0/jsdocs/symbols/jsf.util.html). `mojarra` namespace is specific to Mojarra, for "under the covers" works. MyFaces has also its own. – BalusC Jun 29 '13 at 20:46
  • I get a HTTP 404 on the provided jsdoc link. Also, do I have to necessary write something similar to `mojarra.jsfcljs`? If so, what conditions is required to use the revelant JS function? – Buhake Sindi Jun 29 '13 at 21:13
  • Try this one on a different domain http://docs.oracle.com/javaee/7/javaserverfaces/2.2/jsdocs/symbols/jsf.util.html (warning: that jsdoc is for JSF 2.2). No, you don't need to write any JS code for that. As answered, `ClientBehavior#getScript()` already returns the necessary JS code. *That* script may in turn contain impl-specific functions. – BalusC Jun 29 '13 at 21:14
  • Sweet, sorry to bother again, when does one use `jsf.util.chain()`? – Buhake Sindi Jun 29 '13 at 21:19
  • I've never had the need to explicitly use it. The `ClientBehavior#getScript()` will already embed it when necessary. – BalusC Jun 29 '13 at 21:20
  • Thanks BalusC, I will take your solution to heart (and code). :-) I guess I don't need to ask why the `RenderKitUtils` classify the behavior by handlers then. – Buhake Sindi Jun 29 '13 at 21:27
  • I don't know if you wanna know but I think this is outdated : http://balusc.omnifaces.org/2011/09/communication-in-jsf-20.html . The part "render outside form". You have an example where you try to render something outside the UInamingContainer and it throws an error. With mojarra it apparently works if the component is not in another UINamingContainer I think.(I tested with a panelGroup) – Ced Mar 30 '16 at 14:36
  • @Ced: that's changed since Mojarra 2.2.5, validation was just removed, there's an open issue to bring it back. See also a.o. http://stackoverflow.com/q/8634156 – BalusC Mar 30 '16 at 18:07