Wednesday, August 01, 2012

JSF Tip of the Day: Adding Javascript to a Custom Component

I was looking through the JSF API and Implementation code, and found a gem that really demonstrates how to add a Javascript framework, or custom Javascript code to your custom JSF component.

It requires more than writing the code out to the page using a ResponseWriter. For example, you must determine if the script is already on the page.

The code I found was part of the com.sun.faces.renderkit.RenderKitUtils in a method called renderJsfJs. This is part of the JSF RI Implementation (jsf-impl). It is dual licensed with GPLv2, or CDDL so please check the license out from the original source.

Here is the abbreviated code for the method. It is well done and demonstrates not only how to add the Javascript, but architecturally how to check if it is already rendered, and installed. It also demonstrates how to use a ResourceHandler and Resource. This is by no means the only way, but it does provide a good example.



 public static void renderJsfJs(FacesContext context) throws IOException {


        if (hasScriptBeenRendered(context)) {
            // Already included, return
            return;
        }

        final String name = "jsf.js";
        final String library = "javax.faces";

        if (hasResourceBeenInstalled(context, name, library)) {
            setScriptAsRendered(context);
            return;
        }
        // Since we've now determined that it's not in the page, we need to add it.

        ResourceHandler handler = context.getApplication().getResourceHandler();
        Resource resource = handler.createResource(name, library);
        ResponseWriter writer = context.getResponseWriter();
        writer.write('\n');
        writer.startElement("script", null);
        writer.writeAttribute("type", "text/javascript", null);
        writer.writeAttribute("src", ((resource != null) ? resource.getRequestPath() : ""), null);
        writer.endElement("script");
        writer.append('\r');
        writer.append('\n');

        // Set the context to record script as included
        setScriptAsRendered(context);
    }

The code to determine if the code is installed is just as important as rendering it.

public static boolean hasResourceBeenInstalled(FacesContext ctx,
                                                   String name,
                                                   String library) {

        UIViewRoot viewRoot = ctx.getViewRoot();
        ListIterator iter = (viewRoot.getComponentResources(ctx, "head")).listIterator();
        while (iter.hasNext()) {
            UIComponent resource = (UIComponent)iter.next();
            String rname = (String)resource.getAttributes().get("name");
            String rlibrary = (String)resource.getAttributes().get("library");
            if (name.equals(rname) && library.equals(rlibrary)) {
                // Set the context to record script as included
                return true;
            }
        }
        iter = (viewRoot.getComponentResources(ctx, "body")).listIterator();
        while (iter.hasNext()) {
            UIComponent resource = (UIComponent)iter.next();
            String rname = (String)resource.getAttributes().get("name");
            String rlibrary = (String)resource.getAttributes().get("library");
            if (name.equals(rname) && library.equals(rlibrary)) {
                // Set the context to record script as included
                return true;
            }
        }
        iter = (viewRoot.getComponentResources(ctx, "form")).listIterator();
        while (iter.hasNext()) {
            UIComponent resource = (UIComponent)iter.next();
            String rname = (String)resource.getAttributes().get("name");
            String rlibrary = (String)resource.getAttributes().get("library");
            if (name.equals(rname) && library.equals(rlibrary)) {
                // Set the context to record script as included
                return true;
            }
        }

        return false;

    }

3 comments :

Alex Kochnev said...

Good tip for those who have to deal with JSF.

It is also a perfect example of why I'm not using JSF - the complexity of dealing w/ it is just overwhelming. Why couldn't this framework (that was supposed to be the mother of all web frameworks) bake this functionality out of the box (e.g just like Tapestry does - http://tapestry.apache.org/javascript.html) ?

John Yeary said...

Perhaps you missed the point in the article. This is a mechanism for adding in a framework, or Javascript into a custom component. There are a number of frameworks like Mojarra, and PrimeFaces which have Javascript baked into them already like Tapestry.

If you are injecting resources like Javascript you can use the @Resources annotation similar to Tapestry. The scripting still needs to be bound in Tapestry like JSF.

The one thing I like in the code I posted is that it checks to see if the resource has already been included in the page.

Anyway, thanks for the comment. I like Tapestry. I just like JSF more. I found I was more productive with it, and it is a standard with multiple implementations.

That being said, I would recommend that any sensible developer take a look a few frameworks before and try them out. Choose the one that makes you productive, and achieves the objectives you are after.

padmalcom said...

Thanks a lot for this helpful snippet!

Popular Posts