December 21, 2005 00:38
Programming, Java, Blog
I understand Webwork now. For a while I was completely blocked. The documentation on the wiki just didn't give me enough information to really understand what was going.

Finally, I wrote to Sebastiano Pilla and asked for help. He sent me some source code that showed me how to use Webwork in practice, and I was able to use that as a model. I still don't understand some areas of Webwork, but I understand it well enough.

What I was meaning to do was:

<dsp:droplet name="DisplayPosts">
  <dsp:oparam name="output">
     <dsp:droplet name="ForEach">
       <dsp:param name="array" param="posts"/>
       <dsp:param name="elementName" value="item"/>
       <dsp:oparam name="output">
           <dsp:valueof param="item.title"/> <br />
           <dsp:valueof param="item.body"/>
       </dsp:oparam>
     </dsp:droplet>
  </dsp:oparam>
</dsp:droplet>

or using JSTL:

<dsp:droplet name="DisplayPosts">
  <dsp:oparam name="output">
     <!-- convert between DSP and JSTL -->
     <dspel:getvalueof var="postz" param="posts">
     <c:forEach var="item" items="${postz}">
       <c:out value="${item.title}"/> <br />    
       <c:out value="${item.body}"/> 
      </c:forEach>
     </dspel:getvalueof>
  </dsp:oparam>
</dsp:droplet>

Webwork does not expose objects to JSTL by default. It says it can, but nothing happened when I tried:

<ww:action name="'displayPosts'" id="displayPostsObj"/>
<ww:set name="postz" value="#displayPostsObj.posts" scope="page"/>
<c:forEach var="item" items="${postz}">
    <c:out value="${item.title}"/> <br />    
    <c:out value="${item.body}"/>
</c:forEach>

Webwork uses OGNL to expose data. Which means it has almost nothing in common with either JSTL objects or DSP droplets. So the correct way of doing this in Webwork is:

<ww:action name="'displayPosts'" id="displayPostsObj" />
<ww:set name="postz" value="#displayPostsObj.posts" scope="page"/>
<ww:iterator value="#postz">
  <ww:property value="title"/> <br />
  <ww:property value="body"/>
</ww:iterator>

This works. But it's not using the same model that you would see in JSTL. Whereas DSP is scope based (params only exist in oparam tags) and JSTL is mostly flat, OGNL is stack based. So when you iterate over a collection, it takes the element and puts it onto the value stack. When you access properties without the # syntax, it assumes you're talking about the first element on the stack. So gets item.title, and so on. If anything, it's like $_ in Perl. It's a little bit of syntax magic.

But what if you want to get the item itself? Well, then you have to understand OGNL. Webwork itself doesn't document OGNL: you have to go to the OGNL web site to do that.

<ww:iterator value="#postz">
   <ww:set name="post" value="[0]"/>
   Post: <ww:property value="#post"/>
</ww:iterator>

In OGNL, [0] returns the item itself. I don't know why.

The # syntax can again be understood by reference to Perl: any time you try to get something that isn't on the default stack, you have to specify it with # beforehand to indicate it's a variable. There are a number of variables that are defined in Webwork; #parameters, #application, and so on. If you have http://example.org/myapp/index.jsp?foo=bar and you call , you can get "bar". Instead of <%= request.getContextPath %>, you can do .

The date formatting support in Webwork is threadbare. They suggest that you use the tag, but that requires that you parameterize it into a ResourceBundle. There's nothing like a DSP tag converter as far as I can tell. The most effective way of formatting a date I've found is:

<%@ taglib uri="webwork" prefix="ww" %>
<%@ page import="com.opensymphony.xwork.util.OgnlValueStack" %>
<%@ page import="java.util.*" %>
<%@ page import="com.tersesystems.blog.util.*" %>
Assume the code's inside an iterator and timestamp is a property of the element...
<%
  OgnlValueStack stack = (OgnlValueStack)request.getAttribute("webwork.valueStack");
  Date date = (Date) stack.findValue("timestamp");
  String formattedDate = DateUtils.format("M/d/yy h:mm aa", date);
%>

(If you do something like this yourself, make sure you get the synchronization right. DateFormat isn't thread-safe.)

If you want to do something more complex in OGNL, you need to know the internal model down cold. Say that you want to dereference an element inside a map using a specific key in Webwork. You'd think that using the [0] syntax described earlier would work:

<ww:iterator value="#myObject.keyList">
    <ww:set name="element" value="#myObject.map[[0]]"/>
</ww:iterator>

But this doesn't work. (Nope, still not sure why.) The way to do this is:

<ww:iterator value="#myObject.keyList">
  <ww:set name="element" value="#myObject.map[top]"/>
</ww:iterator>

I found this from the forums. I don't know what "top" means. I imagine it's some special syntax that tells OGNL to peek at the stack, but I can't see it documented anywhere. (EDIT: docs here. Apparently it's part of XWork.)

OGNL has interesting type conversion as well. For example, you set pages=3 as a query parameter. You'd expect #parameters.pages[0] - 1 == 2, and it does. But #parameters.pages[0] + 1 == 31. The only way I found to get around this was to cast to an int by hand, which meant:

<ww:if test="#displayPostsObj.page.nextPage == true">
 <ww:url id="pastURL" value="/index.jsp"><ww:param name="'page'" value="@java.lang.Integer@parseInt(#pageNum) + 1"/></ww:url>
 <a href="<ww:property value="#pastURL"/>">Past</a>
</ww:if>

That's enough for now about Webwork syntax. Next post, I go into Webwork actions.

« Webwork Actions in a Nutshell | Home | New Hackystat out »

Glad to have helped :-)

At least we can hope that the future merger of WebWork with Struts should improve the documentation and increase the quality and quantity of WW examples.

A few notes:

- when you evaluate an expression against the value stack, it doesn't just look at the top object, it will keep looking down the stack.
- formatting a date with a text works great. It's not really much work to set up a resource bundle to use for your whole site, and it gives you localized date and number formatting options without needing to resort to scriptlets.
- the [0] notation is telling the stack to cut at the first object, similarly you can do [1] to get the one just below the top, etc. Usually, if you want the topmost object, you use "top", as you found later.
- For the params, you're not usually directly accessing request parameters, you'd have them set onto an action property so that the type conversion can take place, you can use the values in your action, etc. Request parameters are Strings (actually String[]) when they come from the request, so that's what you're seeing. The "-" operator lets it know that it should convert to a number, but "+" is overloaded to include string concatenation, so you don't get type conversion. Again, bind the parameters to object properties for more predictable type conversion.

Overall, I'd say make your view simpler, and do more work in your model.

I'd say my view's as simple as it can be. I'm down to things like CountCommentsAction, and trying very hard to keep all the display logic internal to the controllers. But simple date and number manipulation shouldn't require custom actions or scriptlets, IMHO.

http://tersesystems.com/post/5800058.jhtml has the really fun JSTL tricks.

Oh crud. How do I bind parameters to object properties? I googled for the phrase, but no luck.

Sorry for the previous comment, I forgot to escape the HTML tags... Will, feel free to delete it, I try again.

"How do I bind parameters to object properties? I googled for the phrase, but no luck."

As far as I understand, it's equivalent to <dsp:input type="..." bean="..." value="..."/> and <dsp:setvalue bean="..." value="..."/>, in WW that would be <ww:textfield/>, <ww:textarea/> etc and <ww:param/> inside <ww:action/>.

Of course Jason, feel free to correct me if I'm wrong.

But I don't want to use a form just to do a cast...

name
url