Skip to content

Porting ZenContact to Japid

branaway edited this page Nov 1, 2011 · 6 revisions

Porting ZenContact, Play! Version, 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.

Installing the application

  • 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.

Porting the application to Japid/Play.

Japid can get along with the default template engine well. One action can using the default template system and another can use Japid.

Install the Japid module.

  • 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 be japid-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:

  1. Open the Window menu in the main menu bar and open the Preferences menu item.
  2. Open the General -> Workspace panel and check the Refresh 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.

Optionally install the Japid plugin for Eclipse

  • 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 the app/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.

The “Home” page

  • 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.

Change the index method

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).

Create the Japid template for this action

There are a couple of ways to create the view template file at the right location.

  1. Manually by following the naming pattern: Copy the app/views/Application/index.html to app/japidviews/Application/index.html, which is the default template for the index action using Japid. Create the necessary file path structure if ti’s missing.
  2. 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 the app/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:

  1. added `args Date now. All Japid templates will need to have `args ... if they actually render any dynamic data. The Date type is actually java.util.Date, I don’t need to use the FQN since the java.util package is automatically imported in Japid templates.
  2. 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.
  3. Changed #{set title:'Home' /} to `set title:"Home". Again I have changed this solely for showing the new simple syntax.
  4. Changed all now.format(...) to format(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 the javaExtensions.format() method. All of the utility methods defined in the JavaExtensions class are automatically imported in Japid templates.

Change the layout file

  • Copy the app/views/main.html to app/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 the nav 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

The List page displays all the contacts in the database.

The action

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.

Create the listAll.html

  • Copy app/views/Application/list.html to app/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)}">&gt;</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>

to:

`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)}">&gt;</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 the format() static method imported from the JavaExtensions class.
    2) the use of the ?:, the Elvis 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.

Creating new Contact or Editing a contact

The two use case hinge upon the form.html template. But let’s start with the controller first.

The controller actions

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.

Modifying the template

The template is the form.html file.

  • Copy app/views/Application/form.html to app/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 to supressNull, we don’t need that any more.
  • Change ${errors.forKey('contact.name')} to ${error("contact.name")}, The error(...) is a Japid method that is a simple wrapper of errors.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.

Conclusions

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.

References