 |
New Feature for JSF 1.2
Posted by jhook on February 25, 2006 at 09:03 AM | Comments (19)
First off, thanks to the JCP and JEE EG for allowing us to add this feature so late in the game. The system *does* work! Now, on to the details...
The core of JSF views are mutable component trees, uniquely identified, black boxes of encapsulated features that can be dropped in and combined in the true academic definition of a 'component'. Each component can participate not only in rendering, but all other phases of MVC including updating, validation, invoking actions, etc as part of an event system. One way to look at it is Action chaining to the Nth degree in the form of widgets that will coordinate/handle MVC request processing.
So here we have trees of components. How do I ask for a particular component in the tree? Well, currently the JSF API only had UIComponent.findComponent(FacesContext, String id), which used a specialized logic of interpreting that passed String id to find the associated component. Developers immediately assumed that the ids JSF produces work for the findComponent() method. Sorry to say, they don't.
The reason why findComponent() doesn't work is in how JSF works with iterative contexts-- something traditionally done with a c:forEach, h:dataTable, or d:displayTag. If you have a parent component who will render its children a hundred times, it doesn't mean there's a hundred children. The parent component simply evaluates its one or two children 100 times, saving on memory. Makes sense doesn't it? So, now we have to uniquely identify each child for each iteration, so JSF produces ids (Client Ids) in the form of mytable:3:text, mytable:4:text, mytable:5:text, etc. You get the picture.
Now, lets say you wanted to take that identifier of mytable:4:text and operate on it later, such as re-rendering part of the page without evaluating the rest of the page. We know that UIComponent.findComponent doesn't work since it returns a UIComponent instance-- but for what row, what state-- there's only one instance in the component tree! And, findComponent knows nothing of the client identifiers your custom UIComponents produce. What do we do?
To solve this, we've added boolean UIComponent.invokeOnComponent(FacesContext faces, String clientId, ContextCallback callback). This allows, in a Chain of Responsibility fashion, a callback to be passed into the tree and when a UIComponent decides to handle it, it invokes the callback and returns true, telling the process that we're done.
This allows black box components, like UIData (h:dataTable) to allow you to operate on a specific component within a given row. Keep in mind, each iteration, just like with a for loop in Java is volatile, so by passing a callback, as in an event system, you can operate on state within a given iteration without collision.
Here's a simple example of partially rendering a JSF component model for things like AJAX. JSF renders your page and produces a series of client identifiers in the page. Now, you want to refresh only part of the rendered page for a known client identifier. Simple!
public static final ContextCallback RENDER = new ContextCallback() {
public void invokeContextCallback(FacesContext ctx, UIComponent c) {
c.renderAll(ctx);
}
};
// custom ajax request
String clientId = paramMap.get("renderId");
UIViewRoot root = faces.getViewRoot();
boolean found = root.invokeOnComponent(faces, clientId, RENDER);
if (!found) throw new FacesException(clientId + " not found!");
Notice, we didn't need to process or render the whole tree and mess with special content buffering or wasteful evaluation. We just pick out what we want to operate on. The RENDER ContextCallback was a simple example, but could be extended for any number of things. Since each UIComponent knows how it produces client identifiers, you can optimize the invokeOnComponent seach to jump to the correct component state in just a few hops, avoiding visiting or evaluating 100 child component iterations when you know the client id was for index 101.
Fantastic stuff in my opinion. At the 30,000 foot view, here we've broken through the traditional URI/resource in request/action based frameworks such as RoR, where each thing you want to partially handle has to be separated out and coordinated as a specialized endpoint. Not so with JSF since we've extended the concept of a URI into the page itself, allowing you to uniquely operate on resources embeded into the page. The best part is that your components don't need to know that they are processed as part of a whole or part. In truth, the same applies to your domain model-- refresh that list of employees or modify just one employee without needing to coordinate actions or special cases.
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Very cool!
Posted by: jessewilson on February 25, 2006 at 11:38 AM
-
Great blog Jacob. I'm interest to know if this kind of feature is only available in Glassfish for now, or it can be deployed in a standard web container like tomcat???
Best Regards
Posted by: rafanami on February 25, 2006 at 01:36 PM
-
It's a JSF 1.2 feature, but could be used with Facelets/JSF 1.2 RI and Tomcat now.
Posted by: jhook on February 25, 2006 at 03:10 PM
-
I just wondering, did EG consider using instead callback, some kind of wrapper UIComponent that incapsulate actual UIComponent instance with associated ClientId state?
Posted by: scskaarj on February 26, 2006 at 01:30 AM
-
We originally thought about a wrapper UIComponent/facade. This presents a couple of issues:
People may still want to 'touch' the target UIComponent instance
The wrapper would require duplicating existing logic with perculating FacesEvents.
There's way to many methods/resposibilities of UIComponent to wrap, especially via convention, we would almost need to get into proxies to handle this.
With this solution, you can still create your own facade object that delegates to this solution.
Posted by: jhook on February 26, 2006 at 07:06 AM
-
Jacob, pretty clear.
For what I like JSF, is that it's enouph flexible to implement all I want by myself =)
Posted by: scskaarj on February 26, 2006 at 09:26 AM
-
That's really great! This used to be a real beast to work around in 1.1Nice job!
Posted by: heaththegreat on February 27, 2006 at 06:45 AM
-
Thanks for bringing some attention to the work that we did. I would also like to point out that the Java EE SDK now runs Oracle ADF Faces.
Ed
JSF Co-Spec Lead.
Posted by: edburns on February 27, 2006 at 08:17 AM
-
Whats the ETA on JSF 1.2?
Also, where would your example code above reside?
Posted by: dhergert on February 27, 2006 at 08:39 PM
-
This is an interesting idea......However, taking things a step further I'm curious how state would transition around these custom ajax partial tree processing requests? Would the entire state of the tree have to be rebuilt each time? Would this custom ajax processor need some way to get a new view variable to send back to the browser so state successfully transitions on the client? What if these async calls get out of order and the wrong state gets written to the browser state variable last? :)
In general I guess I'm asking how you would think of tackling state with these partial ajax requests?
Mike
Posted by: youngm on March 01, 2006 at 01:22 PM
-
for state, we've already standardized on a parameter name to represent state that could be communicated from the client w/ ajax requests. The state would be restored and tree would be built as normal (see this blog too). So you could have some component that would iteract with a completely different component elsewhere in the tree before responding. Asynch ordering could be handled by the JSF extensions on the server side. The other alternative is to go stateless with these requests and simply produce a new tree for processing with each request. It all depends, but it is something we'd still like to make better across the board with JSF.
Posted by: jhook on March 01, 2006 at 01:33 PM
-
Very nice hook.
Did I read correctly that Its available now in RI 1.2 and works with Facelets ? A hook like this is a requirement for any flexible JSF/AJAX components.
Any word if the MyFaces guys are going to put it into their nightly build ?
Posted by: colintoal on March 02, 2006 at 11:22 AM
-
Would this work with compound ids? Let's say I want to find a "firstName" control as a child of "users".
...
Would getting a component of "users:firstName" return a component or not? If not, would we have to have a callback that initiates another callback?
Posted by: arobinson74 on March 07, 2006 at 07:48 PM
-
I know your example code didn't come through on the site, but it was:
<t:dataTable id="users" ...>
<t:column>
<t:inputText id="firstName".../>
...
Now, in this case, for each row, JSF would produce rendered client IDs which are unique to each component 'instance'. So in your example, UIComponent.findComponent("users:firstName") would work fine to find the common UIComponent, this has always worked. The problem is that that component's state isn't set for any particular row (which may be fine for you).
BUT, if you wanted to operate on the 5th or 6th row in the table, JSF would've rendered the client ID "users:5:firstName" and that ID can be passed to the UIComponent.invokeOnComponent such that your callback will receive the same component as w/ findComponent, except that it will be scoped for when all the data of the table will be pointing at the 5th row.
Posted by: jhook on March 07, 2006 at 09:15 PM
-
Thanks for this helpful information
Regards,
Alan Mehio
London
Posted by: alanmehio on January 09, 2007 at 12:55 AM
-
Here is my code:
public void invokeContextCallback(FacesContext ctx, UIComponent c) {
c.getAttributes().put("style", "background:yellow");
}
and the UI component client id is vehicleTransportAdjustments:j_id_id143:0:transportPriceAdjUS
Instead of setting the background of this specific UI component, it sets background of all UI components of which client id end with transportPriceAdjUS to yellow.
vehicleTransportAdjustments:j_id_id143:1:transportPriceAdjUS
vehicleTransportAdjustments:j_id_id143:2:transportPriceAdjUS
vehicleTransportAdjustments:j_id_id143:3:transportPriceAdjUS
So the whole column of the datatable are changed, why?
Posted by: lindaqxl on January 12, 2008 at 12:40 AM
-
datatable's implementation only saves editable values per row. This means that setting the style yellow will set the style for all rows. I believe Apache MyFaces Tomahawk has an enhanced datatable or you can take the route of expression your style as an EL expression and control the style via backing beans instead of right on the component.
Posted by: jhook on January 13, 2008 at 08:59 AM
-
Thanks a lot for unexpected quick answer!
Regards, Kevin
Posted by: lindaqxl on January 13, 2008 at 10:42 AM
-
What I am trying to do is to highlight the fields in the datatable that has validation error. so your second suggestion of route of expression is not helping here.
Do you know what attribute I need to set for Tomahawk to enable this? Simply changing h:datatable to t:datatable doesn't work either.
Thanks.
Posted by: lindaqxl on January 13, 2008 at 10:56 AM
|