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
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
The date formatting support in Webwork is threadbare. They suggest that you use the
<%@ 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.
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.
- 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.
http://tersesystems.com/post/5800058.jhtml has the really fun JSTL tricks.
"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.