-
Notifications
You must be signed in to change notification settings - Fork 18
Porting ZenContact to Japid
Bing Ran, bing_ran@hotmail.com
2011-3-1
The ZenContact application is one of the samples in Play!‘s distribution. It’s a very simple application involving only one model class and one controller with a few views. I feel it would make a good example of a simple Japid/Play application and how to port a Play application using the default rendering engine to using Japid.
I’m using the code in the 1.1.x from the Github. Since I’m using Eclipse as my primary/only IDE, I’ll reference some Eclipse specific steps occasionally. I’m using Eclipse Helios version 3.6.1.
Please keep The Japid User Guide handy in case you don’t understand some of the Japid usage.
- Go to the
samples-and-tests/zencontact
directory of your Play installation. - Create an Eclipse project:
play eclipsify
- Import the application to Eclipse. I suppose the readers know the detailed steps.
- Run the application by using the Zenconatct, by zenexity.launch script.
The application would start fine.
Japid can get along with the default template engine well. One action can using the default template system and another can use Japid.
- From the commandline:
play install Japid
- Once the installation completes, go to the
{play_root}/modules
directory and please find a Japid module directory. It should bejapid-0.9.2
or newer. Let’s assume you get exactly this version. - Add a line in the application’s
application.conf
file:
module.japid=${play.path}/modules/japid-0.9.2
- Create the necessary Japid template tree root:
play japid:gen
This step is not necessary if you use the Japid plugin for Eclipse, as explained later. Neither is it necessary if you write code while you’re running the app in DEV mode.
- Re-create the Eclipse project:
play eclipsify
- Refresh or reload the project into the Eclipse IDE.
Japid transforms html-based templates to Java source files to integrate into the rest of the Play applications. Every time the author changes a template file, the derived Java file will be updated too. For the IDE to display any changes made to the derived Java files, it’s highly recommended to turn on auto-refresh on external file changes. Take Eclipse for an example:
- Open the
Window
menu in the main menu bar and open thePreferences
menu item. - Open the
General -> Workspace
panel and check theRefresh automatically
checkbox.
Now the changes to the artifacts of Japid transforming process will be displayed instantaneously or with a short delay, depending on the size of the project and the OS you’re running. If you have an error in your template, the derived Java file will be marked with an error marker and you can take a look at it to find out what’s wrong with it. This is the next best thing to using the Eclipse plugin.
- Copy the
org.playframework.playclipse_0.8.7.jar
in the{play_root}\modules\japid-0.7.1\eclipse-plugin
to{eclipse_root}/dropins
directory. Remove any previously installed PlayClipse jar if there is one. - Start/Restart the Eclipse IDE. Please verify that a new menu named JapidPlay is installed in the main menu bar. Start Eclipse with
-clean
option if you don’t see the new menu. - Right click on the project and issue the
JapidPlay -> Add Play! nature
, you’ll notice a new directory tree is created in theapp/japidviews
.
Please note that you don’t need this plugin to develop with Japid. Developing with Japid in a plain Eclipse project is almost as easy as developing with the plain Play! application without a plugin.
Now let’s get started porting the code.
Make sure that your have this line in the conf/application.conf
:
application.mode=dev
and start the application. Hit http://localhost:9000/
and verify that the application works.
We are going to take advantage of the hot code replacement of Play! and edit code while the server is running in “development” mode.
- Open the
controller.Application.java
file. - Change the super class to
cn.bran.play.JapidController
so it looks like this:
package controllers;
import java.util.Date;
import java.util.List;
import models.Contact;
import play.data.validation.Valid;
import cn.bran.play.JapidController;
public class Application extends JapidController {
//....
}
Reload the home page and you’ll find that that everything should just work since the JapidController
is a subclass of Controller
class.
from:
public static void index() {
Date now = new Date();
render(now);
}
to:
public static void index() {
Date now = new Date();
renderJapid(now);
}
Essentially change render(now)
to renderJapid(now)
.
There are a couple of ways to create the view template file at the right location.
- Manually by following the naming pattern: Copy the
app/views/Application/index.html
toapp/japidviews/Application/index.html
, which is the default template for theindex
action using Japid. Create the necessary file path structure if ti’s missing. - With the Japid plugin for Eclipse installed, leave you cursor anywhere on the action method definition and press
ctrl-alt-v
, which is the shortcut for “Go to the view”, A dialog will pop up and ask if you would like to create the view file. Hit “Yes” and a default view file is created for you. You then copy the content of theapp/views/Application/index.html
to override the default sample content.
- Modify the new index.html from:
#{extends 'main.html' /}
#{set title:'Home' /}
<p id="time">
<span>${now.format("EEEE',' MMMM dd',' yyyy")}</span>${now.format("hh'h' MM'mn' ss's'")}
</p>
<script type="text/javascript" charset="utf-8">
setInterval(function() {
$('section').load('@{index()} #time')
}, 1000)
</script>
to:
`args Date now
`extends main
`set title:"Home"
<p id="time">
<span>${format(now, "EEEE',' MMMM dd',' yyyy")}</span>${format(now, "hh'h' MM'mn' ss's'")}
</p>
<script type="text/javascript" charset="utf-8">
setInterval(function() {
$('section').load('@{index()} #time')
}, 1000)
</script>
Changes made:
- added
`args Date now
. All Japid templates will need to have`args ...
if they actually render any dynamic data. TheDate
type is actuallyjava.util.Date
, I don’t need to use the FQN since thejava.util
package is automatically imported in Japid templates. - Changed
#{extends 'main.html' /}
to`extends main
. Actually you don’t need to do this. Japid supports the classic syntax. But I like the new syntax. Note The location of the layout is changed too. See later. - Changed
#{set title:'Home' /}
to`set title:"Home"
. Again I have changed this solely for showing the new simple syntax. - Changed all
now.format(...)
toformat(now, ....)
. The former syntax takes advantage of Groovy’s object extension mechanism and adds new methods in the Play!JavaExtensions
class and make them look as if they’re the methods of the extended object,java.util.Date
in this case. The latter is a simple invocation of thejavaExtensions.format()
method. All of the utility methods defined in the JavaExtensions class are automatically imported in Japid templates.
- Copy the
app/views/main.html
toapp/japidviews/_layouts
. The default layout location is the_layouts
directory in Japid.
We’ll need to make a few changes to the layout file.
The original file is:
<!DOCTYPE html>
<html>
<head>
<title>Zencontact, by zenexity ★ #{get 'title' /}</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="stylesheet" type="text/css" media="screen" reef="@{'public/stylesheets/style.css'}" />
<link rel="stylesheet" type="text/css" media="screen" href="@{'public/stylesheets/south-street/jquery-ui-1.7.2.custom.css'}" />
<script src="@{'public/javascripts/jquery-1.4.min.js'}" type="text/javascript" charset="utf-8"></script>
<script src="@{'public/javascripts/jquery-ui-1.7.2.custom.min.js'}" type="text/javascript" charset="utf-8"></script>
<script src="@{'public/javascripts/jquery.editinplace.packed.js'}" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="zencontact">
<header>
<img src="@{'public/images/logo.png'}" alt="logo" id="logo" />
<h1>Zencontact <span>by zenexity</span></h1>
</header>
<nav>
<a id="home" href="@{index()}" class="${request.action =~ /index/ ? 'selected' : ''}">Home</a>
<a id="list" href="@{list()}" class="${request.action =~ /list/ ? 'selected' : ''}">List</a>
<a id="new" href="@{form()}" class="${request.action =~ /form|save/ ? 'selected' : ''}">New</a>
</nav>
<section>
#{doLayout /}
</section>
<footer>
<a href="http://www.w3.org/TR/html5/">html5</a> -
<a href="http://www.w3.org/TR/css3-roadmap/">css3</a> -
<a href="http://www.playframework.org/">playframework</a>
</footer>
</div>
</body>
</html>
Let’s make a few changes:
- Change
#{get 'title' /}
to`get title`
. Both are good Japid syntax, the but the new syntax is a lot less verbose. - The only necessary change is this line:
${request.action =~ /index/ ? 'selected' : ''}
for each of the anchors in thenav
block.
1) the straightforward translation:${request.action.matches(".*index") ? "selected" : ""}
2) The above works. But it is repeated three times and it’s a lot of typing. Let’s simplify it with Japid local methods. Go to the end of the file and add:
`def selected(String pattern) ${request.action.matches(pattern) ? "selected" : ""} `
Here I have defined a local method named “selected” that returns a string from the one line template.
Now the class selector looks like this: class="${selected(".*index")}"
, much cleaner. The other two anchors are changed likewise.
- Change
#{doLayout/}
to`doLayout
. Again both syntax are supported but the new syntax is cleaner.
The final main.html
<!DOCTYPE html>
<html>
<head>
<title>JapidContact, by zenexity/Bing Ran ★ `get title` </title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="stylesheet" type="text/css" media="screen" href="@{'public/stylesheets/style.css'}" />
<link rel="stylesheet" type="text/css" media="screen" href="@{'public/stylesheets/south-street/jquery-ui-1.7.2.custom.css'}" />
<script src="@{'public/javascripts/jquery-1.4.min.js'}" type="text/javascript" charset="utf-8"></script>
<script src="@{'public/javascripts/jquery-ui-1.7.2.custom.min.js'}" type="text/javascript" charset="utf-8"></script>
<script src="@{'public/javascripts/jquery.editinplace.packed.js'}" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="zencontact">
<header>
<img src="@{'public/images/logo.png'}" alt="logo" id="logo" />
<h1>Japid Contact <span>by zenexity & Bing Ran</span></h1>
</header>
<nav>
<a id="home" href="@{index()}" class="${selected(".*index")}">Home</a>
<a id="list" href="@{list()}" class="${selected(".*list")}">List</a>
<a id="new" href="@{form()}" class="${selected(".*form|.*save")}">New</a>
</nav>
<section>
`doLayout
</section>
<footer>
<a href="http://www.w3.org/TR/html5/">html5</a> -
<a href="http://www.w3.org/TR/css3-roadmap/">css3</a> -
<a href="http://www.playframework.org/">playframework with Japid</a>
</footer>
</div>
</body>
</html>
`def selected(String pattern)
${request.action.matches(pattern) ? "selected" : ""}
`
Now hit http://localhost:9000
you’ll get the home page that’s rendered with Japid while the rest of the application is still using the classic template system. The point to take away is that porting to Japid does not have to be a all-or-none case. One can port the actions and views one-by-one and have great confidence in each step to ensure a smooth transition.
The List page displays all the contacts in the database.
Open the Application
controller and change the action list()
from:
public static void list() {
List<Contact> contacts = Contact.find("order by name, firstname").fetch();
render(contacts);
}
to:
public static void list() {
List<Contact> contacts = Contact.find("order by name, firstname").fetch();
renderJapidWith("@listAll", contacts);
}
It’s a one line change from render(contacts);
to renderJapidWith("@listAll", contacts);
.
I should have been able to change it to
renderJapid(contacts);
But for some unknown reason, I could not use list.html
, the default template for the list
action, as the template name, just for this particular application. The Play! compiler would complain that the list class had already compiled or it should have been in a separate file. So I work around this and invoke another template named listAll.html
to render to the contact list. The at sign before the listAll indicates that the template is named listAll
in the same directory as the default template directory of this controller.
- Copy
app/views/Application/list.html
toapp/japidviews/Application/listAll.html
.
- Change the content from:
#{extends 'main.html' /}
#{set title:'List' /}
<table>
<thead>
<tr>
<th class="name">Name</th>
<th class="firstname">First name</th>
<th class="birthdate">Birth date</th>
<th class="email">Email</th>
<th class="edit"></th>
</tr>
</thead>
<tbody>
#{list contacts, as:'contact'}
<tr class="contact" contactId="${contact.id}" draggable="true">
<td id="name-${contact.id}">${contact.name}</td>
<td id="firstname-${contact.id}">${contact.firstname}</td>
<td id="birthdate-${contact.id}">${contact.birthdate?.format('yyyy-MM-dd')}</td>
<td id="email-${contact.id}">${contact.email}</td>
<td><a href="@{form(contact.id)}">></a></td>
</tr>
#{/list}
<tr>
#{form @save()}
<td><input type="text" name="contact.name"></td>
<td><input type="text" name="contact.firstname"></td>
<td><input type="text" name="contact.birthdate"></td>
<td><input type="text" name="contact.email"></td>
<td><input type="submit" value="+"></td>
#{/form}
</tr>
</tbody>
</table>
<script type="text/javascript" charset="utf-8">
// In place edition
$(".contact td").editInPlace({
bg_over: 'transparent',
callback: function(el, n, o) {
var m = /([a-z]+)-(\d+)/.exec(el), data = {}
data['contact.id'] = m[2]
data['contact.' + m[1]] = n
// Save result
$.ajax({
url: '@{save()}',
type: 'POST',
data: data,
success: function() {$('#' + el).html(n)},
error: function() {$('#' + el).html(o)}
})
return true
}
})
// Drag & Drop
var dragIcon = document.createElement('img')
dragIcon.src = '@{'public/images/avatar.png'}'
var action = #{jsAction @form(':id') /}
var cancel = function cancel(e) {e.preventDefault()}
$('#new')
.bind('dragover', cancel)
.bind('dragenter', cancel)
.bind('drop', function(e) {
document.location = action({id: e.originalEvent.dataTransfer.getData('contactId')})
})
$('[draggable]').bind('dragstart', function(e) {
e.originalEvent.dataTransfer.setData('contactId', $(this).attr('contactId'));
e.originalEvent.dataTransfer.setDragImage(dragIcon, 0, -10);
})
</script>
`args List<Contact> contacts
`extends main
`set title:"List of all"
<table>
<thead>
<tr>
<th class="name">Name</th>
<th class="firstname">First name</th>
<th class="birthdate">Birth date</th>
<th class="email">Email</th>
<th class="edit"></th>
</tr>
</thead>
<tbody>
`for (Contact contact : contacts) {
<tr class="contact" contactId="${contact.id}" draggable="true">
<td id="name-${contact.id}">${contact.name}</td>
<td id="firstname-${contact.id}">${contact.firstname}</td>
<td id="birthdate-${contact.id}">${format(contact.birthdate, "yyyy-MM-dd") ?: ""}</td>
<td id="email-${contact.id}">${contact.email}</td>
<td><a href="@{form(contact.id)}">></a></td>
</tr>
`}
<tr>
<form action="@{save()}" method="POST">
${authenticityToken()}
<td><input type="text" name="contact.name"></td>
<td><input type="text" name="contact.firstname"></td>
<td><input type="text" name="contact.birthdate"></td>
<td><input type="text" name="contact.email"></td>
<td><input type="submit" value="+"></td>
</form>
</tr>
</tbody>
</table>
<script type="text/javascript" charset="utf-8">
// In place edition
$(".contact td").editInPlace({
bg_over: 'transparent',
callback: function(el, n, o) {
var m = /([a-z]+)-(\d+)/.exec(el), data = {}
data['contact.id'] = m[2]
data['contact.' + m[1]] = n
// Save result
$.ajax({
url: '@{save()}',
type: 'POST',
data: data,
success: function() {$('#' + el).html(n)},
error: function() {$('#' + el).html(o)}
})
return true
}
})
*{
}*
// Drag & Drop
var dragIcon = document.createElement('img')
dragIcon.src = '@{'public/images/avatar.png'}'
var action = $jsAction("form", ":id")
var cancel = function cancel(e) {e.preventDefault()}
$('#new')
.bind('dragover', cancel)
.bind('dragenter', cancel)
.bind('drop', function(e) {
document.location = action({id: e.originalEvent.dataTransfer.getData('contactId')})
})
$('[draggable]').bind('dragstart', function(e) {
e.originalEvent.dataTransfer.setData('contactId', $(this).attr('contactId'));
e.originalEvent.dataTransfer.setDragImage(dragIcon, 0, -10);
})
</script>
Here are the changes to make:
- added:
`args List<Contact> contacts
-
#{extends 'main.html' /}
→`extends main
-
#{set title:'List' /}
→`set title:"List"
-
#{list contacts, as:'contact'}
→`for (Contact contact : contacts) {
. Since we’re not using any extra loop properties, a simple Java for loop is used. -
#{/list}
→`}
to close the Java for loop. -
${contact.birthdate?.format('yyyy-MM-dd')}
→${format(contact.birthdate, "yyyy-MM-dd") ?: ""}
. Two things here:
1) the use of theformat()
static method imported from theJavaExtensions
class.
2) the use of the?:
, theElvis
operator, in Japid expressions, to catch a missing birthdate field. -
#{form @ save()}
→<form action="@ {save()}" method="POST">
- Accordingly we change
#{/form}
to the plain</form>
- added:
${authenticityToken()}
right below the form tag. -
#{jsAction @form(':id') /}
→$jsAction("form", ":id")
. Japid provides a method to do the same thing as the#{jsAction}
.
Now click on the list
button on the home page you should be able to the contact list rendered by the Japid template.
The two use case hinge upon the form.html
template. But let’s start with the controller first.
There are two methods to change.
before change
public static void form(Long id) {
if(id == null) {
render();
}
Contact contact = Contact.findById(id);
render(contact);
}
public static void save(@Valid Contact contact) {
if(validation.hasErrors()) {
if(request.isAjax())
error("Invalid value");
render("@form", contact);
}
System.out.println(contact.toString());
contact.save();
list();
}
after change
/**
* for editing or displaying a new contact form
*/
public static void form(Long id) {
if(id == null) {
renderJapid((Object)null);
}
Contact contact = Contact.findById(id);
renderJapid(contact);
}
/**
* for saving a contact
*/
public static void save(@Valid Contact contact) {
if(validation.hasErrors()) {
if(request.isAjax())
error("Invalid value");
renderJapidWith("@form", contact);
}
System.out.println(contact.toString());
contact.save();
list();
}
Again the changes are made to the render()
method invocations. The only notable thing is the render()
→ renderJapidWith(null)
. Japid requires the argument list to perfectly match that in the template. The template accepts one Contact
object, so we pass a null for creating a new contact.
The template is the form.html
file.
- Copy
app/views/Application/form.html
toapp/japidviews/Application/form.html
The original form.html
#{extends 'main.html' /}
#{set title:'Form' /}
#{form @save()}
<input type="hidden" name="contact.id" value="${contact?.id}">
<p class="field">
<label for="name">Name:</label>
<input type="text" id="name" name="contact.name" value="${contact?.name}">
<span class="error">${errors.forKey('contact.name')}</span>
</p>
<p class="field">
<label for="firstname">First name:</label>
<input type="text" id="firstname" name="contact.firstname" value="${contact?.firstname}">
<span class="error">${errors.forKey('contact.firstname')}</span>
</p>
<p class="field">
<label for="birthdate">Birth date:</label>
<input type="text" id="birthdate" name="contact.birthdate" value="${contact?.birthdate?.format('yyyy-MM-dd')}">
<span class="error">${errors.forKey('contact.birthdate')}</span>
</p>
<p class="field">
<label for="email">Email:</label>
<input type="text" id="email" name="contact.email" value="${contact?.email}">
<span class="error">${errors.forKey('contact.email')}</span>
</p>
<p class="buttons">
<a href="@{list()}">Cancel</a> or <input type="submit" value="Save this contact" id="saveContact">
</p>
<script type="text/javascript" charset="utf-8">
$("#birthdate").datepicker({dateFormat:'yy-mm-dd', showAnim:'fadeIn'})
</script>
#{/form}
Changes to make:
- modify the “headers” from
#{extends 'main.html' /}
#{set title:'Form' /}
to:
`args Contact contact
`extends main
`set title:"Form"
`suppressNull on
Nothing has not be seen except the `suppressNull
directive. This directive will catch any NullPointerException
thrown in any Japid expressions, the ${}
expression, and output an empty string instead. Effectively you can use Java “dot” notation to safely access bean properties.
- Change
#{form @save()}
to<form action="@{save()}" method="POST">
. - Add
$authenticityToken()
right after the form tag. - Replace all instances of
?.
, the Groovy safe bean navigation operator, to a simple.
. Thanks tosupressNull
, we don’t need that any more. - Change
${errors.forKey('contact.name')}
to${error("contact.name")}
, Theerror(...)
is a Japid method that is a simple wrapper oferrors.forKey()
. Alternatively you just change it to use double quotation marks to make it a valid Java method invocation:${errors.forKey("contact.name")}
. - Go into each of the fields to fix the
errors.forKey()
. - For the “birthdate” field, change the
value
attribute from${contact?.birthdate?.format('yyyy-MM-dd')}
to${format(contact.birthdate, "yyyy-MM-dd")}
. - Change
#{/form}
to</form>
.
The form.html after change
`args Contact contact
`extends main
`set title:"Form"
`suppressNull on
<form action="@{save()}" method="POST">
$authenticityToken()
<input type="hidden" name="contact.id" value="${contact.id}">
<p class="field">
<label for="name">Name:</label>
<input type="text" id="name" name="contact.name" value="${contact.name}">
<span class="error">${error("contact.name")}</span>
</p>
<p class="field">
<label for="firstname">First name:</label>
<input type="text" id="firstname" name="contact.firstname" value="${contact.firstname}">
<span class="error">${error("contact.firstname")}</span>
</p>
<p class="field">
<label for="birthdate">Birth date:</label>
<input type="text" id="birthdate" name="contact.birthdate" value="${format(contact.birthdate, "yyyy-MM-dd")}">
<span class="error">${error("contact.birthdate")}</span>
</p>
<p class="field">
<label for="email">Email:</label>
<input type="text" id="email" name="contact.email" value="${contact.email}">
<span class="error">${error("contact.email")}</span>
</p>
<p class="buttons">
<a href="@{list()}">Cancel</a> or <input type="submit" value="Save this contact" id="saveContact">
</p>
<script type="text/javascript" charset="utf-8">
$("#birthdate").datepicker({dateFormat:'yy-mm-dd', showAnim:'fadeIn'})
</script>
</form>
Now the porting work is pretty much finished.
Porting to Japid is mostly straightforward.
Japid provides `
leading directives for many of the built-in tags in the classic Play! templates.
If you see any errors, inpecting the Java files derived from the templates is a great way to debug your templates.