Skip to main content

Making a YUI Calendar Component in JSF2

Posted by driscoll on August 9, 2009 at 4:17 PM PDT

In my last blog entry, I went over getting a YUI widget working on JSF2. This time, let's go over what's required to move that widget into a JSF component. No Java required, but a fair bit of JavaScript.

In a lot of ways, this is just like other components that I've written about. The tricks are much the same - saving values into a JavaScript context. Including scripts into the component, that sort of thing.

Let's go over the component, one file at a time. First, the using page:

   1    <ez:yuical id="cal1" value="#{date.date1}" render="out1"/>
   2    <h:outputText id="out1" value="#{date.date1}">
   3        <f:convertDateTime pattern="MM/dd/yyyy"/>
   4    </h:outputText>

To use the component, we just pass it two attributes - value, which is a managed property of type Date, and an optional render attribute, which will be updated via an ajax call when we click on a date in the component.

Simple enough. But since we're using the version of the YUI code that's served from Google, we'll have to include the code in the head.

<script type="text/javascript" src=""></script>

Why? Because JSF's resource API assumes all resources are local - meaning that you can't use the h:outputScript and h:outputStylesheet with resources external to your server. I thought about showing this example with locally available resources (it's cleaner, of course), but thought the point was worth making. Hopefully, the 2.1 version of JSF will have the ability to specify a URL, in addition to just a local resource name.

So, that's the using page: what's the component look like? Not much, it turns out:

   1    <composite:interface name="yuical"
   2                         displayName="YUI Cal Component"
   3                         shortDescription="YUI Calendar Component">
   4        <composite:attribute name="value" required="true" type="java.util.Date"/>
   5        <composite:attribute name="render" required="false" type="java.lang.String"/>
   6    </composite:interface>
   8    <composite:implementation>
   9        <h:outputScript name="jsf.js" library="javax.faces" target="head" />
  10        <h:outputScript name="yuical/calendar.js" target="head" />
  12        <h:panelGrid class="yui-skin-sam" id="holdingContainer">
  13            <h:panelGroup layout="block" id="calContainer"/>
  14            <h:inputHidden id="date" value="#{cc.attrs.value}">
  15                <f:convertDateTime pattern="MM/dd/yyyy"/>
  16            </h:inputHidden>
  17        </h:panelGrid>
  18        <script type="text/javascript">
  19             demo.calendar.init("#{cc.clientId}", "#{cc.attrs.render}");
  20        </script>
  21    </composite:implementation>

The interface section (lines 1-6) just details what I've already gone over - two attributes, a value that's a date, and required, and a value that's a string, and that optionally points to an id that will be updated when the date value is selected.

Lines 9-10 are where we include the scripts for the component - and as I've previously mentioned, if we were to have the YUI JavaScripts locally, this is where we'd include those as well.

Lines 12-17 are the same as our previous blog, where we set up the necessary structure for the widget, as well as the hidden field which we'll update when a date is selected in the widget.

Last, we have the script at line 19 - a simple call to init, passing in the contextual render value, so we can use more than one of these components in a page.

Now, let's look at the JavaScript code. It's long (for a simple demo), but much of it is the same as my previous blog, and quite a bit is simply necessary fluff. We'll take it on in three chunks, corresponding to the three sections of the code - the setup, the init function, and the handler function.

   1    if (typeof demo == "undefined" && !demo) {
   2        var demo = {};
   3    }
   5    if (typeof demo.calendar == "undefined" && !demo.calendar) {
   6        demo.calendar = {};
   7    }
   9    if (typeof demo.calendar.contextMap === "undefined" && !demo.calendar.contextMap) {
  10        demo.calendar.contextMap = [];
  11    }

This is just a simple initialization of the objects we'll be using the JavaScript. We escape it, so we don't set them up twice - this lets us use more than one component in a page.

Now we'll look at the init function that's called within the component's XHTML:

  13    demo.calendar.init = function init(context, render) {
  15        // record the render attribute, if applied
  16        demo.calendar.contextMap[context] = render;
  18        demo.calendar.loader = new YAHOO.util.YUILoader({
  19            base: "",
  20            require: ["calendar"],
  21            loadOptional: false,
  22            combine: false,
  23            filter: "RAW",
  24            allowRollup: false,
  25            onSuccess: function() {
  26                try {
  27                    demo.calendar.cal1 = new YAHOO.widget.Calendar("demo.calendar.cal1", context+":calContainer");
  28                    demo.calendar.cal1.render();
  29                    demo.calendar.cal1.selectEvent.subscribe(demo.calendar.handleSelect, demo.calendar.cal1, true);
  30                } catch (e) {
  31                    alert(e);
  32                }
  33            },
  34            // should a failure occur, the onFailure function will be executed
  35            onFailure: function(o) {
  36                alert("error: " + YAHOO.lang.dump(o));
  37            }
  39        });
  41        //
  42        // Calculate the dependency and insert the required scripts and css resources
  43        // into the document
  44        demo.calendar.loader.insert();
  45    }

This code is almost the same as in the previous blog - naturally, since most of this is necessary for setting up the Calendar itself. Two changes, to let it be in a reusable component: line 16, which records the render attribute, and associates it with the component's id value, and line 27, where we also take into account the component's value.

And, finally, here's the handler, which does most of the heavy (JSF specific) lifting for this component:

  48    demo.calendar.handleSelect = function handleSelect(type, args, obj) {
  50        if (type === "select") {
  51            var calId = obj.containerId;
  52            var index = calId.indexOf(":") + 1;
  53            var tmpindex = calId.substring(index).indexOf(":") + 1;
  54            // keep looking until you get the last child index
  55            while (tmpindex !== 0) {
  56                index += tmpindex;
  57                tmpindex = calId.substr(index).indexOf(":") + 1;
  58            }
  59            var containerId = calId.substring(0,index - 1);
  60            var dateId = containerId + ":" + "date";
  61            var dates = args[0];
  62            var date = dates[0];
  63            var year = date[0], month = date[1], day = date[2];
  65            var txtDate = document.getElementById(dateId);
  66            txtDate.value = month + "/" + day + "/" + year;
  68            var render = demo.calendar.contextMap[containerId];
  69            try {
  70                // if a render is defined for the component, then include it.
  71                if (typeof render !== "undefined" && render ) {
  72                    jsf.ajax.request(dateId,null,{
  73                        render: render,
  74                        execute: dateId
  75                    })
  76                } else {
  77                    jsf.ajax.request(dateId,null,{
  78                        execute: dateId
  79                    })
  80                }
  81            } catch (e) {
  82                alert(e);
  83            }
  84        }
  85    }

By using the passed in ID of the widget that was triggered, we obtain the id's of the component, and from there the id of the date field (lines 51-60).

As in the previous blog, we then get the element that corresponds to the hidden field, and set it (lines 65-66).

Then retrieve the render value based on the component's id. If there is a value (remember, it's an optional value), use it, otherwise, don't.

And finally, perform a jsf.ajax call with the hidden value as the executed portion - which will set it on the server.

And that's it - we now have a component that graphically allows for selecting a date, and having it reflected on the server. Neat, huh?

Anyway, I'll be uploading this into the Project Mojarra demo directory now that it's complete, in the ajax-component demo. You can check out the full code there.

Feel free to ask questions, below.

Related Topics >>