A Blog Application with Warp (continued)
First check out the previous tutorial on creating a Blog application
to get yourself going. In this article, I'll show you how to add forms
and a bit of interactivity. You will learn the following Warp concepts:
- Event Handling and Navigation
- Events and the Button component
- The TextField and TextArea components
- Page scopes
Continuing
from the previous tutorial, let's now make a "compose new entry" page.
This will be very similar to the other pages with some different
components:
<html>
<head w:component="meta">
<title>Warp :: Compose New Blog Entry</title>
</head>
<body w:component="frame">
<input w:component="textfield" w:bind="newBlog.subject" />
<input w:component="textarea" w:bind="newBlog.text" />
<input w:component="button" w:label="post blog" />
</body>
</html>
The first important part to notice is that the <head> tag is
decorated with a Meta component. This is very important indeed--along with the Frame component on <body>, Meta forms the foundation for Warp page behavior.
The other things to notice are the input components. TextField is
a simple text box, we use the attribute w:bind to tell Warp where to
bind the user input to. In this case I am giving it a path to a
property of newBlog, which is a variable in my page object. OK, let's
create ourselves this property:
@URIMapping("/blogs/compose")
public class ComposeBlog {
private Blog newBlog = new Blog("", ""); //an empty blog
@OnEvent
public void save() {
System.out.println(newBlog.getSubject() + " - "
+ newBlog.getText());
}
public Blog getNewBlog() {
return newBlog;
}
}
Here I have provided an event handler method named save(). By tagging it with the @OnEvent annotation,
I have told Warp to invoke this method whenever the page triggers an
event (in our case, the clicking of the button). First, the data is
synchronized between the input components (TextField and TextArea) and the bound properties, then the event handler is fired which prints out their content.
Let's
add some more functionality, where our list of blogs (from the previous
tutorial) actually gets updated. For this we first need to add a method
in ListBlogs that stores a new blog entry into the map. That part is
easy enough:
@URIMapping("/home")
public class ListBlogs {
private Map<String, Blog> blogs = new HashMap<String, Blog>();
public ListBlogs() { .. }
public void addNewBlog(Blog blog) {
blogs.put(blog.getSubject(), blog);
}
//...
}
Ok
now let us invoke the store method from our compose page's event
handler (remember Page-injection from the previous tutorial):
@URIMapping("/blogs/compose")
public class ComposeBlog {
private Blog newBlog = new Blog("", ""); //an empty blog
@Inject @Page ListBlogs listBlogs;
@OnEvent
public void save() {
listBlogs.addNewBlog(newBlog);
}
public Blog getNewBlog() {
return newBlog;
}
}
We're almost there, now when I save the entry, I want it to come back to the blog list. This follows the post-and-redirect design
pattern common in web development. Warp supports this in an intuitive,
type-safe manner. After saving the blog in my event handler, I simply
return the page object that I want shown:
@OnEvent
public ListBlogs save() {
listBlogs.addNewBlog(newBlog);
return listBlogs;
}
Neat! Now when you click the "post blog" button, it runs the save()
method and redirects you to back to the blog list. You can return any
type of object from an event handler so long as it is a page object (or
a subclass of one). You can also redirect to an arbitrary URL or use
JSP-style forwarding instead of post-and-redirect. Check out the Event Handling guide on the wiki for details.
One last step concerning Page scoping.
Typically, Warp obtains an instance of a page object from the Guice
injector on every request. This effectively means that any page object
that is not bound (see Guice user guide for information on scopes) with
a given scope is instantiated once per request. Since our HashMap is a
property of the ListBlogs page, this means that when we redirect (in a
new request), the Map gets blown away and we lose our new entry.
To
fix this, we can scope the ListBlogs page object as a singleton. This
means that ListBlogs is only created once for the lifetime of the
webapp, and the Map of entries is retained.
@URIMapping("/home")
@Singleton
public class ListBlogs { .. }
You should be careful about relying on page objects to maintain
state and do a lot of thinking before declaring a scope on a page.
The
singleton scope is an easy fix for our example but in the real world
you will want to store the blogs in a more permanent storage medium
(such as a database or persistent store). In my next tutorial, we'll
see how to do just that using JPA and Hibernate.