Skip to main content

JSF 2.0: Writing fully reusable Spinner Component

Posted by driscoll on November 13, 2008 at 12:48 AM PST

In my previous blog postings, I talked about making the Spinner component, and then added styles via an external css file. Please review those first, if you haven't looked at them yet.


This time, we'll move the JavaScript out to a separate file, and make sure that we can execute multiple spinners in a page, like this:


            <ez:spinner value="#{multinumber.number1}" increment="1" id="spinner1" />
            <ez:spinner value="#{multinumber.number2}" increment="10" id="spinner2" />

Now, we've changed the implementation of the component to this (I've marked the parts we'll discuss in bold):


<composite:implementation>
    <h:outputStylesheet name="spinner/spinner.css" />
    <h:outputScript name="ajax.js" library="javax.faces" target="head"/>
    <h:outputScript name="spinner/spinner.js" target="head" />
    <script type="text/javascript">
        init("#{compositeComponent.clientId}","#{compositeComponent.attrs.increment}");
    </script>
    <h:inputText id="number" value="#{compositeComponent.attrs.value}" styleClass="spinnerText"/>
    <h:panelGroup id="spinnerButtonPanel" styleClass="spinnerButtons">
        <h:commandButton id="forward"  value="&#652;" styleClass="spinnerButton"
                         onclick="return changeNumber('#{compositeComponent.clientId}',1);" />
        <h:commandButton id="back" value="v" styleClass="spinnerButton"
                         onclick="return changeNumber('#{compositeComponent.clientId}',-1);" />
    </h:panelGroup>
</composite:implementation>

Going through the important bits of this file, we have:


  • A call to load an external javascript file
  • A function invocation of an init function, passing two values that will be substituted in at the time the page is sent to the client. Note that, as in the previous example, I've wrapped these values in quotation marks, since they're really just text in a page.
  • And lastly, we have two onclick handlers, both of which call another function, passing in the clientId of the component, as well as what direction the spinner should turn. Again, the clientId of the component is substituted at the time the page is sent to the browser, so the Javascript never actually sees the EL, just text on a page.

Now, we'll take a look at that external JavaScript code.


if (!window["spinnerIncrement"]) {
    var spinnerIncrement = {};
}
function init(componentID, increment) {
    spinnerIncrement[componentID] = Number(increment);
    if (isNaN(spinnerIncrement[componentID]) || spinnerIncrement[componentID] == 0 ) {
        spinnerIncrement[componentID]= 1;
    }
}

function changeNumber(componentID, amount) {
    var entry = document.getElementById(componentID+":"+"number");
    var val = Number(entry.value);
    if (isNaN(val)) { val = 0; }
    entry.value = val + (amount * spinnerIncrement[componentID]);
    return false;
}

So, why do we do all this odd looking componentID passing? Because we want this file to be used by multiple components - and that means setting up some sort of namespace to store variables for each component. The way I'm showing here is just one possible way, but it works with a fairly short number of lines. Let go through the logic:


  • First, we check if spinnerIncrement exists, and create it if it does not.
  • Then, in init, we associate the value of the increment with the componentID of each component. Init will be called once per component, as we saw above.
  • Lastly, for each onclick, we modify the value of the text input field, as we've done in the prior two Spinner examples. The only difference is that we're now aware of the componentID of the calling component, and we're using it as a context for all our persistent variables.

Again, a simple trick, and now you can use multiple ajax aware components in a page.


As always, questions welcome - and remember, you can try these examples out in the new Glassfish v3 prelude release, if you use JSF 2.0.


I'll continue to build on these examples in future blogs, but for now, I think we've taken the spinner component about as far as it can go.

Related Topics >>

Comments

JSF 2.0 will be available when Java EE 6 is available - which should be sometime around JavaOne, though the exact date isn't set in stone.

Is it possible to change a

Is it possible to use a h:outputlabel instead of an h:inputText? If no, why? thanks in advance..

Congrats! Any prevision about when JSF is plain to use in production development?

What about situation when I need to change something inside the item located in h:dataTable. h:dataTable id="visit-list" value="#{patientServiceController.visitList}" var="item"> .... h:inputText value="#{item.copayAmount}"/> If I changed copayAmount in row of datable how to update related value on server side without refreshing whole dataTable? Thank you

Hi Good example and great work!. I cannot see AJAX in this example? RichFaces has a very strong and mature library Ajax4Jsf, I hope all that will be harvested and still work with JSF 2.0. Can you put some lines on Ajax examples, how Ajax4Jsf and other open source projects are being integrated or will get compatibe/interoperable in JSF 2.0

"the implementation keeps track of whether that tag's been called before" Thanks for answer. I'll try to find how this magic works in sources of jsf2.0

Remember that h:outputScript tag? It's not just there because JSF enjoys decorating every portion of the page :-) In the case of the h:outputScript tag, as well as the h:ouputStylesheet tag, the implementation keeps track of whether that tag's been called before. If it has, there's no need to output it a second time. So you'll have one call to script type="text/javascript" src="/basic-ezcomp/javax.faces.resource/spinner/spinner.js.jsf" and two calls to init in the page. The text decoration around the spinner.js file name comes from it being put in the resources directory, as with the css file.

Assume, I have two or more spinner components on page. How JSF achieves, that script "spinner/spinner.js", will not be added two or more times in the resulting rendered output?