Skip to main content

A tale of two components (JSF2)

Posted by driscoll on July 3, 2009 at 12:44 PM PDT

Today, I'd like to take a look at two different ways to create a poll component. Poll components are a way to periodically update a page with new information. We'll take a look at examples of these in a second, but first, a caveat: I've assumed throughout my blogs on Ajax components in JSF that you have at least a passing familiarity with JavaScript. This post assumes a bit more knowledge of JavaScript than some other posts. I'll try to explain what I'm doing as I go along, but if you find yourself mystified by closures, I'd like to suggest the book JavaScript: The Good Parts. It's a wonderful book, and quite short. Check it out.


With that out of the way, here's how you'd use these two poll components within a page:


Count:<br/>
<h:outputText id="target" value="#{count.count}"/><br/>
<ez:polltag id="poll" interval="200" timeout="5000" render=":form:target"/>
<h:outputText id="target2" value="#{count.count2}"/>
<ez:poll id="poll2" interval="1000" timeout="10000" render="form:target2"/>

Let's go over what this does: We have a two components, polltag and poll. They each have three attributes, interval (how often to refresh the target), timeout (when to stop), and render (the target we'll refresh). These tags are identical in function, with the only difference being the format of their render attribute (more on that momentarily) and the method they use to make the Ajax call. Run this page, and you'll see the numbers increment - one quickly, and the other more slowly (count.count just increments every time it's accessed).


Let's start by taking a look at the polltag component. I decided to start with just a quick, naive component that uses the Ajax tag. Let's take a look at what that tag looks like (resources/poll/polltag.xhtml):


<cc:implementation>
    <span id="#{cc.clientId}">
    <h:outputScript name="poll/polltag.js" target="head" />
        <h:commandButton id="hidden" type="button" style="display: none;">
            <f:ajax render="#{cc.attrs.render}"/>
        </h:commandButton>
    </span>
    <script type="text/javascript">
        jsfdemo.polltag.init("#{cc.clientId}","#{cc.attrs.interval}", "#{cc.attrs.timeout}");
    </script>
</cc:implementation>

For those of you following my previous blogs, much of this isn't new: We wrap the whole component to get a span on the page with the supplied id. We include a script (resources/poll/polltag.js) which adds the backing JavaScript. We also inject a small script which calls that backing JavaScript function. And that injected script includes the necessary context to allow multiple tags to be placed in the page.


But the new thing here is that we're including a hidden button, and attaching an Ajax tag to it. That Ajax tag will then inject code that activates when the button is clicked. Since the button is hidden, we'll "click" the button programatically, inside the init function. Let's take a look at that backing JavaScript.


if (!window.jsfdemo) {
    var jsfdemo = {};
}

if (!jsfdemo.polltag) {
    jsfdemo.polltag = {};
}

if (!jsfdemo.polltag.init) {
    jsfdemo.polltag.init = function init(id, inc, to) {
        var componentID = id;
        var increment = inc;
        var timeout = to;
        var elapsed = 0;
        var token = null;

        // If increment isn't set, or it's less than some reasonable value (50) default to 200ms
        if (isNaN(increment) || increment <= 50) {
            increment = 200;
        }
        // If timeout isn't set, default to no timeout
        if (isNaN(timeout) || timeout == 0) {
            timeout = -1;
        }

        var poll = function poll() {
            var hiddenID = componentID + ":" + "hidden";
            var hidden = document.getElementById(hiddenID);
            hidden.click();  // this executes the ajax request
            if (timeout != -1) {
                // Not an accurate timeout - but simple to compute
                elapsed += increment;
                if (elapsed > timeout) {
                    window.clearInterval(token);
                }
            }
        }

        token = window.setInterval(poll, increment);
    }
}

OK, this is a little long, let's break it down to see what's going on here.

First, we do namespacing: by creating objects, and placing the function onto that object, we make sure that we don't accidentally have two init() functions in a page - such as when we're creating two different poll tags with init functions. We also need to check if those objects already exist before creating them - after all, we may want to have multiple namespaces placed onto the "jsfdemo" object.


Then, we define a module - we have an init() function, and inside that init() function, we also define a poll() function. The poll() function has access to the variables inside the init() function, but calling the init function multiple times results in multiple contexts. This is a pretty common pattern in JavaScript, but it does look awkward to folks coming from Java.


At the end of the file, after setting up the poll() function, we have the line:


token = window.setInterval(poll, increment);

Which sets up the poll - we're simply using the JavaScript setInterval function to periodically call the poll() function, every increment milliseconds.


When the poll function is called, we find the button, and click it. This will, in turn, trigger the Ajax call. Then, we determine if the timeout time has come, and if it has, we turn off the timer with the clearInterval call.


OK, a little kludgy - but it works, after a fashion. There are some problems with this approach: Since we're using the f:ajax tag, we need to use the UIComponent.findComponent syntax for locating the target to update - some may prefer this, some won't. And while having a button that you could "unhide" to restart the poll might be handy, in general it's just cluttering up your page. Also, if the server stops responding for some reason, you'll get pummeled with error alerts if you're in development mode.


So, let's go ahead and rewrite this to instead use the jsf.ajax.request function, provided by jsf.js in JSF 2.


We'll only have to make a few quick changes to do this, as well as adding error handling. First, in the component page, we'll say add an output script call. And, we'll also change the call to init to be wrapped inside a jsf.ajax.addOnError function call. This will add a function to the list of listeners that get called if there's an error on the page. Note that the init function itself is not the function that will get added - rather, it's return value will be what's added. And we'll make that return value be a function (you'll see it in a second). We also remove all the markup associated with the button.


<cc:implementation>
    <span id="#{cc.clientId}">
        <h:outputScript name="jsf.js" library="javax.faces" target="head"/>
        <h:outputScript name="poll/poll.js" target="head" />
    </span>
    <script type="text/javascript">
        /* <![CDATA[ */
        jsf.ajax.addOnError(jsfdemo.poll.init("#{cc.clientId}","#{cc.attrs.interval}", "#{cc.attrs.timeout}", "#{cc.attrs.render}"));
        /* ]]> */
    </script>
</cc:implementation>

In the backing JavaScript, we're going to have to make two changes. We'll have to change the poll function, and we'll have to add a return value to the init function. Here's the entirety of the backing JavaScript file:


if (!window.jsfdemo) {
    var jsfdemo = {};
}

if (!jsfdemo.poll) {
    jsfdemo.poll = {};
}

if (!jsfdemo.poll.init) {
    jsfdemo.poll.init = function init(id, inc, to, rend) {
        var componentID = id;
        var increment = inc;
        var timeout = to;
        var elapsed = 0;
        var token = null;
        var render = rend;

        // If increment isn't set, or it's less than some reasonable value (50) default to 200ms
        if (isNaN(increment) || increment <= 50) {
            increment = 200;
        }
        // If timeout isn't set, default to no timeout
        if (isNaN(timeout) || timeout == 0) {
            timeout = -1;
        }

        var poll = function poll() {
            jsf.ajax.request(componentID, null, {render: render});
            if (timeout != -1) {
                // Not an accurate timeout - but simple to compute
                elapsed += increment;
                if (elapsed > timeout) {
                    window.clearInterval(token);
                }
            }
        }

        token = window.setInterval(poll, increment);

        return function cancelPoll(data) {
            if (data.source.id == componentID) {
                window.clearInterval(token);
            }
        }
    }
}

Let's look at the return value first:


return function cancelPoll(data) {
    if (data.source.id == componentID) {
        window.clearInterval(token);
    }
}

We add this at the end of the init function - which means that init will return this function as it's return value, and that this function will be what's added to the list of listeners that get called when there's an error. Because this function will get called regardless of what component causes the error, we add a check to make sure that the current component is the one that got the error. And, as in the poll function, because this function is defined inside init, it has access to all of the context that's there when init is called - in this case, the componentID variable, passed in by the script tag in the component.


The changes for the poll function to use jsf.ajax.request are relatively straightforward:


var poll = function poll() {
    jsf.ajax.request(componentID, null, {render: render});
    if (timeout != -1) {
        // Not an accurate timeout - but simple to compute
        elapsed += increment;
        if (elapsed > timeout) {
            window.clearInterval(token);
        }
    }
}

Much simpler than the corresponding code surrounding the tag method.


As always, this code can be found in the jsf-demo/ajax-components directory of the Project Mojarra codebase. You'll find this and much more code there, including most of the code I've blogged about in the past.


If you have questions, please let me know in the comments section.

Related Topics >>

Comments

Hi, I have bumped into your pages a few times and end ...

Hi,
I have bumped into your pages a few times and end up not being able to use what is probably useful advice. I know when writing web pages it is good to use codes instead of "<" characters for example. I.e. &lt instead of "<". However when trying to read your examples it just registers as a big mess since the codes are so much more difficult to read. You might consider just for the point of creating readable examples relenting and using the symbols rather than the codes for them. I think there might be something I could have used here but it is just way to hard for me to be able to read what you are doing.

Thanks anyway.

Regards,

BillR

where is the code

Can not find any links to this demo on the mojarra home page After a bit of hoking around I found this location on a forum https://mojarra.dev.java.net/source/browse/mojarra/trunk/jsf-demo/ but I cannot see the poll component. Please post a direct link to the source code so i can download it and try to get it working. cheers, paul cuningham

found it

the command to checkout mojarra is: svn checkout https://mojarra.dev.java.net/svn/mojarra/trunk mojarra --username [java.net userid] and the poll component seems to be there. will give ot a try cheers. paul http://www.niportals.com