A Blog Application with Wideplay's Warp Framework
First check out the Hello World example
to get yourself going. In this article, I'll show you how to knock
together a simple Blog application where you can browse and read blog
entries. You will learn the following Warp concepts:
- Page Injection
- RESTful behavior and the HyperLink component
- The Table and Column components
- Simple Internationalization (i18n)
First let's create ourselves a data model object representing a blog. This will be a simple POJO with 2 fields: subject and text.
public class Blog {
private String subject;
private String text;
//don't forget your getters/setters here...
}
OK, now we make a list page where you can see a list of all
the blogs currently in the system. For simplicity, we will store blogs
in a HashMap. The Page object class for this list should look something
like this:
@URIMapping("/blogs")
public class ListBlogs {
private final Map<String, Blog> blogs = new HashMap<String, Blog>();
public ListBlogs() {
//setup a blog as dummy data
blog = new Blog("MyBlog", "Warp is so great...");
blogs.put(blog.getSubject(), blog);
}
public Collection<Blog> getBlogList() {
return blogs.values();
}
}
Note the @URIMapping annotation which tells warp to map this page object to the URI "/blogs". The getter for property blogList,
simply returns a list of all the values contained in our HashMap of
blogs. For the interesting part, let's make a template to layout our
blogs:
<?xml version="1.0" encoding="UTF-8"?>
<html>
<head>
<title>Warp :: Blogs</title>
</head>
<body w:component="frame">
<h1>A list of blog entries</h1>
<table w:component="table" w:items="${blogList}"/>
</body>
</html>
This
is a very simple template. Inside our body we have only one real
component (Table) and we pass it our list of blogs in the w:items
property that we declared above. Go ahead and run the app now and point
your browser to: http://localhost:8080/blogs,
you should see a table with 2 columns (subject and text) corresponding
to the Blog data model object we declared above. Table is smart enough
to inspect the items given it and construct an appropriate set of
display columns. The rows of the table reflect the data in each
instance of the Blog object in our HashMap. Nice!
OK, having the
property names as the title of each column is not ideal--let's
customize this. Customizing is as simple as adding a properties file in
the same package as our Blog class, with the same name:
Blog.properties:
subject=Subject of Blog
text=Content
Now,
run the app again and you should see the column names changed. In this
way, you can also achieve internationalization (i18n). If you want to
hide a property (i.e. not render a column for it), specify an empty key
(so to hide subject, you would have just "subject=").
OK, let's create a view blog page now. This page will display one blog entry. Let's start with the view portion (called ViewBlog.html):
<?xml version="1.0" encoding="UTF-8"?>
<html>
<head w:component="meta">
<title>Reading :: ${blog.subject}</title>
</head>
<body w:component="frame">
<h1>Read...</h1>
<h3>${blog.subject}</h3>
${blog.text}
<a href="../home">back to list</a>
</body>
And its Page object:
@URIMapping("/blog")
public class ViewBlog {
private Blog blog;
//getters/setters...
}
Ok, this is fairly simple, but how does it know what blog to display? Let us use a RESTful idiom to achieve this. First, change your ViewBlog page object so that it takes a parameter indicating what blog to show:
@URIMapping("/blog/{subject}")
public class ViewBlog {
private Blog blog;
@OnEvent @PreRender
public void init(String subject) { .. }
}
Ok, this tells warp that when the URL http://localhost:8080/blog/myblog is requested, to inject anything matching the {subject}
portion of the @URIMapping (in this case "myblog") to the @PreRender
event handler method. Hold on, we're not done yet--we still need to
obtain the appropriate blog from our HashMap which is stored in the
ListBlogs page. This is done via page-injection:
@URIMapping("/blog/{subject}")
public class ViewBlog {
private Blog blog;
@Inject @Page private ListBlogs listBlogs;
@OnEvent @PreRender
public void init(String subject) {
this.blog = listBlogs.getBlog(subject);
}
}
Finally, we need to modify ListBlogs and give it a getBlog() method to fetch a Blog by subject:
@URIMapping("/blogs")
public class ListBlogs {
private Map<String, Blog> blogs = new HashMap<String, Blog>();
public ListBlogs() { .. }
public Collection<Blog> getBlogList() {
return blogs.values();
}
public Blog getBlog(String subject) {
return blogs.get(subject);
}
}
Ok,
now let's wire the pages together so clicking on a blog in the list
will take us to the view page for that blog. I want to make the subject
of the blog clickable, so let's use the Column component to override
the default behavior of table (which just prints out the subject as
text):
<?xml version="1.0" encoding="UTF-8"?>
<html>
<head w:component="meta">
<title>Warp :: Blogs</title>
</head>
<body w:component="frame">
<h1>A list of blog entries</h1>
<table w:component="table" w:items="${blogList}" class="mytableCss">
<td w:component="column" w:property="subject">
<a w:component="hyperlink" w:target="/blog"
w:topic="${subject}">${subject}</a>
</td>
</table>
</body>
</html>
Notice
that we nest the hyperlink component inside the column override. This
tells the Table component not to draw the column, instead to use our
overridden layout instead. The attribute w:target simply tells the
hyperlink the URI that we're linking (in this case /blog, which is the
ViewBlog's mapping) and w:topic tells hyperlink to append the subject
of the blog to the URI. So for a blog entitled "MyBlog," Warp will
generate a URI as follows: /blog/MyBlog. And "MyBlog" gets stripped out
and injected into the ViewBlog page's @PreRender handler so it can set
it up.
Also notice the addition of a non-Warp attribute class to the <table> tag,
which refers to the CSS class I want to style my table with. This is a
useful tool for both for previewability as well as the end result HTML--generally whatever HTML attributes you
write on a component will be passed through the final rendered page.
Done!