Wednesday, May 23, 2007

 

Six Things JSF Breaks (and How To Fix Them)

Java Server Faces attempts to bring reusable stateful components to the web application world, and there are certainly a number of benefits to doing so. In order to make this model work, however, JSF steps on the toes of a number of client-side technologies. If you want to enrich your JSF application with some of the niceties that today's users (and developers) have come to know and love, then you will notice that there at least six things JSF breaks. Thankfully, most of them have usable (if not always fully convenient) workarounds. Read about them beforehand so that you'll know how to handle them when they come up! As a caveat, I am not a JSF expert, so there may be cleaner and more elegant fixes than the ones I suggest, but these should be enough to get you started.

  1. Standards-Compliant HTML
  2. We live in a day when your HTML can say exactly what it is using classes and IDs, and your stylesheets tell the browser how to display it. Even page layout can be handled by applying a few styles to descriptively-named divs. Unfortunately, JSF was written in a day when this was not the case. Table layouts predominated, and as a result the HTML code that JSF outputs is predominantly table-based. In the best case, this presents a cumbersome nonsequiter in the midst of otherwise-elegant generated code; in the worst case, this markup actually breaks the page and defies styling.

    How to fix it:

    If you want JSF to output clean XHTML, you will need to create your own RenderKit that implements everything JSF needs in a standards-compliant way (warning: non-trivial!!). If you just want to clean up a single component, extend it and override its encodeBegin() and encodeEnd() methods. You can look at the source code for two JSF implementations (the Reference Implementation or MyFaces) to see examples of how the components are rendered. Anyone know of a good open-source standards-compliant RenderKit?

  3. JavaScript form processing
  4. Long before JavaScript was given omnipotent reign over the DOM, its use was restricted to simple form processing. This task still finds frequent use in client-side form validation. Even such a simple task, however, is difficult with JSF. By default, JSF generates numeric IDs for your form (i.e. "_id42") and components (i.e. "_id42:_id27" - applied to both ID and name attributes). Since they are generated at runtime and can change from session to session, it's impossible to look up form elements by name or ID.

    How to fix it:

    By default, JSF lets you set the ID of the form explicitly. This allows you to reliably reference the form, but form elements still have partially numeric IDs (i.e. "myForm:_id27"). The Tomahawk extensions to MyFaces address this problem by adding a forceId attribute to most components. If forceId is set to "true", then Tomahawk will use the exact ID you entered as the name and ID of the element in the outputted HTML.

  5. AJAX
  6. AJAX stands for "asynchronous JavaScript and XML", and is a general term used for a technique of using JavaScript XmlHttpRequest objects to communicate the server to send and receive small amounts of data, rather than entire web pages. The result can be a better user experience because web pages can respond to user input quickly, rather than having to refresh the entire page. JSF, however, is not very flexible in the server-side requests it allows. Most MVC framworks tie one server-side action to one URL with clearly-defined parameters, which are easy to query using AJAX requests. By contrast, JSF allows multiple actions, action listeners, and value change listeners to be performed on one .jsf (or .faces) URL, depending on the values of hidden form fields that JSF populates. This architecture is easy to use when writing pages with the JSF tag library, but it's difficult to interface with using JavaScript - say, if you wanted JavaScript to send data to the server, retrieve the results, and process them.

    How to fix it:

    In this case, the best thing to do with JSF is to let it do its own thing by giving it a hidden iframe (width/height 0 or visibility:hidden) to submit the form into. An AJAX function can then submit the form into the iframe, which will save the data to JSF but will not execute a method. Then, do an AJAX request to another URL that will return the data you need. You will probably want to wait for the form submit to complete before calling the AJAX request, so that the data makes it up to the server correctly. To do this, set the iframe's location to "about:blank", and then have JavaScript check the iframe every 0.1 seconds or so, to see if the location is still "about:blank". Both FireFox and IE should keep the location as "about:blank" until the form submit is finished, which is when the data is saved to the server and available for other requests.

    If you also need regular submit buttons or action listener buttons that refresh the entire page, then don't make the form target the hidden iframe by default. Instead, have your AJAX function set the form's target to the iframe, submit it, and then reset the form's target back to the original value. In this case, your AJAX request will submit the form to the hidden iframe, but buttons will still submit the form to the browser window.

    Ajax4jsf is a good option to check into for rerendering certain components on the web page, but be aware that it re-requests the entire web page and then extracts the needed component. This may be fine for occasional component updates, but it's certainly doesn't get us any closer to the kind of responsiveness that AJAX is usually used for. Ajax4jsf may also have additional side-effects depending on how the rest of the page is generated.

  7. Incremental Development
  8. Users of JSP taglibs are used to application servers recompiling pages on the fly, so that all they need to do is modify the attributes of a tag and then refresh the page. In this case, however, a weakness arises from what is actually one of JSF's strengths: the retention of component instances in session memory. If JSF creates a component with certain settings, and then the markup for that component's tag is changed, the component will not detect these changes until the session is invalidated, forcing the component instance to be re-created. This introduces major inconveniences for incremental development, because every time a component's attributes need to be changed, you must discard and recreate a potentially complex session state.

    How to fix it:

    Up until now, I haven't discovered any easy way to fix this problem. You seem to be stuck with clearing your session! I would recommend using Selenium to record the steps necessary to restore your session to the state it needs to be in for testing (i.e. logging in, filling out forms, making selections) - but see below for a necessary Selenium workaround.

  9. Browser Unit Testing and Macros
  10. Selenium is a popular FireFox extension allowing browser interactions to be recorded and played back. It can record, for example, the user navigating to a certain page, inputting certain data into certain form fields, and clicking certain buttons. This is useful for automated unit testing to ensure a page or sequence behaves as it should, as well as for quickly navigating to a portion of an application of interest (say, when building the last page of a long wizard). However, the same nondeterministic field IDs that caused a problem with JavaScript also cause a problem with Selenium, because it identifies fields by name/ID. Although the elements may keep the same ID as long as you stay in the session in which you made the recording, the IDs are likely to reset when your session expires or you redeploy your webapp -- forcing you to re-record your Selenium macro from scratch.

    How to fix it:

    Use the Tomahawk extensions instead of the MyFaces built-in components for all form elements including submit buttons, and use forceId on all of them (see Javascript form processing above).

  11. Pre-Render Actions
  12. In most Java MVC frameworks, you have to go through classes to get to pages. That is to say, when you request a URL, a certain controller is activated, which then passes control to a certain page. This allows any necessary data to be populated and then sent to the page. Of course, most frameworks provide an easy facility for pages that don't need any data pre-populated. In JSF, however, instead of going through classes to get to pages, you go through pages to get to classes. A .jsf or .faces URL (or however you mapped it) goes directly to a page, and it is the page that passes control to a class if it needs to get data or process the results of a form. JSF instantiates certain beans and makes them accessible to the page using an EL to call getters. However, JSF does not seem written to put substantial code in these getters, as the MyFaces implementation may may execute a certain EL expression for a component up to five times per render! This, of course, is disastrously inefficient for accessing data from a database.

    How to fix it:

    JSF provides a facility for listeners that can be applied across multiple URLs, but this is not optimal for populating data into a single page. You can apply a caching pattern to your bean getters - having them check an instance variable: if it is not-null, returning it; if it is null, calling a helper method to load it from a database or elsewhere. This pattern works equally well for shared data needed for multiple getters. An alternative would be to put code in the action method of the previous bean to populate the next bean.

Conclusion

This is certainly no small list of challenges! JSF was built in a time when fewer client-side technologies were available, but it also seems to be targeted to projects that require less client-side interaction. If you are developing the next great web 2.0 application, you may want to consider a framework that intrudes less into the markup of the page, such as Struts or Spring.

Labels: , , , , ,


Comments:
Another approach to JSF Ajax is to use ICEfaces. ICEfaces renders the page into a DOM on the server, detects the differences, and sends just the differences to the browser (including pushing the differences to the browser if a server-side render is initiated by the application rather than a user event). This approach to page rendering means that the developer doesn't need to consider the details of how Ajax might be implemented, they can simply focus on their application.
 
Post a Comment





<< Home

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]