Developing the suggestion box application with the command-line interface

This tutorial provides an introduction to the Zero Resource Model (ZRM) and the JavaScript™ library and includes developing zero.suggest.demo with the WebSphere sMash CLI.

The following concepts are presented in this tutorial:

  • Automatic creation of databases from resource model definitions
  • Initialization of database tables with JSON documents
  • The RESTful API that allows clients to list, filter, retrieve, create, update, and delete data with ZRM, and
  • The use of Dojo-based widgets that allow client programmers to expose ZRM data with a few lines of JavaScript.

The tutorial is presented in five steps:

  • Creating the server-side code
  • Adding user authentication for server-side code
  • Creating the client-side code
  • Adding form controls
  • Styling Dojo widgets
  • Test the application
  • Completed example
  • Results

The sample application used for this tutorial will allow users to submit and vote for suggestions. Users will be able to authenticate with a simple ID and password combination and then see all of the current proposals, as well as the number of votes each has received. For the sake of brevity, we will not implement restrictions on how many times a user can vote; our main goal is to show how to store data with ZRM and then expose it on the client side with Dojo's data store and grid concepts.

Creating the server-side code

  1. Create a new IBM® WebSphere® sMash application named suggestionbox. From the command prompt, go to the directory and type:

    $ zero create suggestionbox
    

    A new directory named suggestionbox is created. Change into this directory:

    $ cd suggestionbox
    

    File paths that are provided for the rest of the tutorial are rooted in this directory.

  2. From config directory, open ivy.xml and add a dependency on the zero.resource and zero.dojo components. Specify the dependencies as follows:

    <dependency org="zero" name="zero.resource" rev="[1.0.0.0, 2.0.0.0["/>
    <dependency org="zero" name="zero.dojo" rev="[1.0.0.0, 2.0.0.0["/>
    
  3. Resolve the application's dependencies to ensure that the WebSphere sMash classpath resolver can find it. From application root, type:

    $ zero resolve
    
  4. When the resolve command is complete, you should see a Command resolve was successful message. You can check what dependencies your aplication is currently resolved against, including specific versions, by executing the following command:

    $ zero version
    
  5. Create the following directories:

    • app/models
    • app/models/fixtures
  6. Create a resource model definition. We will define one resource type - suggestions - that will map to one database table; each suggestion resource will a suggestion name, a submitter name, and the number of votes it has received. To create the resource model, use a text editor to create a file named suggestions.json in the app/models directory with the following content:

    {
        "fields": {
            "suggestion": {"type": "string", "max_length": 128},
            "submitter": {"type": "string", "max_length": 64},
            "votes": {"type": "integer"}
        }
    }
    

    In the previous model declaration, notice that each field in the suggestions type has an associated data type (string, integer, etc.) and, in the case of strings, a length. ZRM uses this definition file to create the database table definition and execute the proper SQL CREATE statement for it.

  7. Add sample data. You can populate the suggestions table with some default or sample data using another JSON document. Create a file named initial_data.json in the app/models/fixtures/ folder with the following content:

    [
        {
            "type": "suggestions",
            "fields": {
                "suggestion": "WebSphere Zero",
                "submitter": "dan",
                "votes": 14
            }
        },
        {
            "type": "suggestions",
            "fields": {
                "suggestion": "WebSphere2.0",
                "submitter": "bob",
                "votes": 25
            }
        },
        {
            "type": "suggestions",
            "fields": {
                "suggestion": "WebSphere X",
                "submitter": "sue",
                "votes": 18
            }
        }
    ]
    

    In the previous sample, notice that each item in the JSON array is an instance of the suggestions type, and each has values for the three fields in that type. In a later section of the tutorial, you will create the security rules for your application and create some sample user accounts to match the submitter names shown here.

  8. Enable the REST API for the ZRM-based resource. Without this step, ZRM will build and maintain your database, but you will not be able to read and write the data over HTTP (GET, POST, etc.). Fortunately, this step is very easy. Using a text editor, create a file named suggestions.groovy the app/resources directory with the single line of code:

    ZRM.delegate()
    

    This one line of code enables all of the LCRUD operations (list, create, retrieve, update, and delete) over HTTP for both collection and member URIs (/suggestions and /suggestions/{id} , respectively).

  9. From the application root directory, use the following command to tell ZRM to create and populate your database:

    $ zero model sync
    

    When the model sync command has finished, you should see Command model sync was successful messages. If you are using the defaultly configured Apache Derby database, you'll note that a db directory has been.

  10. Test the REST API. You have not added security or a user interface, but you can still do some initial tests to ensure that things are working so far.

    Note: Make sure you stop any existing WebSphere sMash applications that are running on the default port of 8080 before attempting to start the application. Start the suggestionbox application with the following command:

    $ zero start
    

    With the application up and running, point your favorite Web browser to http://localhost:8080/resources/suggestions

    You will receive a JSON file contains collection of suggestion resources in the database that was loaded from app/models/fixtures/initial_data.json. Once you have confirmed this, you can stop the application with the following command:

    $ zero stop
    

Adding user authentication for server-side code

  1. Enable HTTP basic authentication for the REST API. This will ensure that only registered users can read and write our suggestion data, whether they use our web interface or their own HTTP client. You can limit use of the REST API to authenticated users by adding the following security rule definition to the $APP_ROOT/config/zero.config file:

    @include "security/rule.config" {
        "authType": "Basic",
        "conditions": "/request/path =~ /resources/.+",
        "groups": ["ALL_AUTHENTICATED_USERS"]
    }
    

    The @include that starts this rule defines all of the default key-value pairs for a WebSphere sMash security rule; we then customize the rule as needed. The trigger that causes our rule to be enforced is a request URI that begins with /resources; this value is represented in WebSphere sMash's global context as /request/path, which explains the pattern-matching expression on the third line. The final line uses a special value (ALL_AUTHENTICATED_USERS) to limit usage to any registered user without dividing users into specialized groups.

  2. Add sample users. By default, authentication is handled with a file-based service that reads and writes user account data to a file named $APP_ROOT/config/zero.users. Below is a sample zero.users file that contains the three submitter names we saw in our sample data, each with the password set to mypassword:

    dan:34819d7beeabb9260a5c854bc85b3e44:
    bob:34819d7beeabb9260a5c854bc85b3e44:
    sue:34819d7beeabb9260a5c854bc85b3e44:
    

    You should reuse this zero.users file at first so that you can test the security settings and web interface. You can later add or remove user accounts using the WebSphere sMash CLI (type zero help user for more information).

  3. Retest the REST API. Restart the suggestionbox application and reload the http://localhost:8080/resources/suggestions URL. You should not be able to read the JSON data until you have entered one of our user names (dan, bob, or sue) with the password mypassword.

Developing the Client User Interface

  1. Enable form-based authentication for the web interface. The REST API is protected by basic authentication, but we will need something slightly different for the web interface implemented under $APP_ROOT/public. Add the following stanzas to the $APP_ROOT/config/zero.config file:

    @include "security/rule.config" {
        "authType": "Form",
        "conditions": "/request/path =~ /secure(/.*)?",
        "groups": ["ALL_AUTHENTICATED_USERS"]
    }
    
    @include "security/form.config" {
        "formLoginPage": "/index.html"
    }
    

    The first stanza is similar to the security rule that enabled basic authentication: it says that all request URIs that start with /secure will be protected by an HTML login form. We will put all user interface elements that are protected under the $APP_ROOT/public/secure directory to match this rule. The only non-protected element we will have is described in the second stanza, where we define the login page that will be used for all clients that need to authenticate.

  2. Provide the login form. Create a file directly under the application's virtual root: public/index.html. This file should contain the default login form used for WebSphere sMash applications. Do not change the names of the ID and password input fields.

    <html>
        <head>
            <title>Login Form</title>
        </head>
        <body>
            <form name="login" method="POST" action="/index.html">
                <table cellspacing="20" border="0">
                    <tr align="left" valign="top">
                        <td><strong>User Name</strong></td>
                        <td><input type="text" maxsize="40" size="20" name="zeroUserName"/></td>
                    </tr>
                    <tr align="left" valign="top">
                        <td><strong>Password</strong></td>
                        <td><input type="password" maxsize="40" size="20" name="zeroPassword"/></td>
                    </tr>
                    <tr>
                        <td colspan="2" align="left" valign="top">
                            <input type="hidden" name="postLoginTargetURI" value="/secure">
                            <input type="submit" value="Login"/>
                        </td>
                    </tr>
                </table>
            </form>
        </body>
    </html>
    

    The zeroUserName and zeroPassword names are standard names used by WebSphere sMash Core to process login forms without any additional programming or configuration by the user. Also notice the hidden field named postLoginTargetURI, which tells WebSphere sMash Core which page the user should be sent to after being authenticated. In this case, the root of the secure area, public/secure/index.html is used.

  3. Create the Web interface for your ZRM data. The majority of the UI development is the use of a grid widget from the Dojo Toolkit and a ZRM-based data store from WebSphere sMash. The ZRM data store will enable Dojo's grid widget to read and write data from the REST API with very little coding on your part. Start by adding the dependencies you need to use Dojo and the ZRM data store and create the HTML skeleton that is needed for this page. The ZRM data store that acts as the back end for the Dojo grid widget is part of a component named zero.dojo; this component has its own dependency on the Dojo toolkit, so all you have to do is add a dependency on zero.dojo in your Ivy file and resolve your application again by executing zero resolve from a command line prompt in your application root.

    To get started on the Web interface, create a file named public/secure/index.html with basic HTML tags in place:

    <html>
      <head>
    
      </head>
      <body>
    
      </body>
    </html>
    

    A completed example of index.html is at the end of the tutorial. Refer this this example as needed.

    Next add the following to the head section:

    <script type="text/javascript" src="/dojo/dojo.js" djConfig="parseOnLoad: true, isDebug: false, usePlainJson: true">
    </script>
    
    <script type="text/javascript">
    dojo.require("dojo.parser");
    dojo.require("zero.resource.DataStore");
    dojo.require("zero.grid.DataGrid");
    dojo.require("dijit.form.Form");
    dojo.require("dijit.form.Button");
    </script>
    

    This snippet of JavaScript points to the Dojo JavaScript toolkit and imports those pieces that you will be using: the grid widget and the ZRM data store (an implementation of the basic Dojo Data API).

  4. Create the ZRM Dojo Data impleentation using zero.resource.DataStore Add the following to create a ZRM data store:

    <span dojoType="zero.resource.DataStore" jsId="thestore"
        resourceCollection="suggestions"></span>
    

    Note that the resourceCollection attribute has the suggestions resource collection that will be used by DataStore to perform list, create, retrieve, update, and delete operations on the collection and member resources at /resources/suggestion URI.

  5. Enable the Dojo grid widget. This will create a spreadsheet-like table on the page, populated with the data from the ZRM data store. You will also enable addition and modification of items to the data store using the widget's edit controls. The following declaration should be placed following the previous two declarations in public/secure/index.html:

    <div id="grid">
        <div dojoType="zero.grid.DataGrid" id="thegrid"
            readonly="true"
            visibleFields="suggestion,submitter,votes"
            store="thestore"
            style="width: 500px; height: 300px;"></div>
    </div>
    

    The store attribute connects the grid widget to the data store. The visibleFields attribute is a comma delimited list of fields from the suggestions ZRM resource model.

Adding form controls

  1. Add controls for voting on and adding new suggestions. Currently, the code allows you to load a grid (table) on a page with the latest suggestion and voting data, but it doesn't let the user make new suggestions or vote. To do those things, you need to add some UI controls and respond to their actions with JavaScript.

    Further, a way for users to vote must be provided. This action requires that the user first select (single-click) an item in the grid and then click the Vote button.

    Add the following HTML to the space below the grid declaration:

    <div id="forms">
        <div dojoType="dijit.form.Button"
            id="vote_btn" label="Vote for selected"
            onclick="vote()"></div>
    </div>
    

    The voting button is associated with a JavaScript function. You can implement this function in a very straightforward manner using Dojo APIs. Place this JavaScript code - and the code that follows it - in the HTML section.

    <script type="text/javascript">
    function vote() {
        var thegrid = dijit.byId("thegrid");
        var rows = thegrid.grid.selection.getSelected();
        if (rows.length != 1) {
            alert("select one row!");
        } else {
            var item = thegrid.grid.model.getRow(rows[0]).__dojo_data_item;
            thestore.setValue(item, "votes", ++item.votes);
            thegrid.grid.selection.clear();
            thestore.save({
                onComplete: function() {
                    thegrid.onSaved();
                },
                onError: function() {
                    alert("error");
                }
            });
        }
    }
    </script>
    

    In the above code, the vote() function retrieves the current data selected in the grid by the user, increments the vote count, and saves the data on the server.

    In our simple scenario, we have not handled synchronization of vote counting or preventing users from voting multiple times. Such is out of scope for this tutorial.

    Next, we need a way for users to add new suggestions. We will accomplish this with a basic form and a submit button. When the button is clicked, the data is added to the grid and sent to the server. A submission is initialized, of course, with no votes.

    Add the following HTML form after the voting button:

    <form dojoType="dijit.form.Form" id="newform" name="newform">
        <p>
            <label for="suggestion">Suggestion:</label>
            <input dojoType="dijit.form.TextBox" id="suggestion" name="suggestion" type="text"></input>
        </p>
        <p>
            <label for="submitter">Submitter:</label>
            <input dojoType="dijit.form.TextBox" id="submitter" name="submitter" type="text"></input>
        </p>
        <p>
            <div dojoType="dijit.form.Button" id="Button_2" label="Submit" onclick="add()"></div>
        </p>
    </form>
    

    Again, the button is associated with a JavaScript function. The code below shows how we create the new row of data, add it to the backend, and save it to the server side:

    <script type="text/javascript">
    function add() {
        var newitem = {};
        newitem.submitter = document.newform.submitter.value;
        newitem.suggestion = document.newform.suggestion.value;
        newitem.votes = 0;
        var thegrid = dijit.byId("thegrid");
        thegrid.grid.addRow(newitem);
        thestore.save({
            onComplete: function() {
                thegrid.onSaved();
            },
            onError: function() {
                alert("error");
            }
        });
    }
    </script>
    

    With all of this JavaScript code added to your index.html file, you now have everything you need to test the web interface to your REST API.

Styling Dojo widgets

Add some style. In order to make the grid look nice, you need to import some CSS files and set some CSS properties. The CSS code below sets the minimum grid style properties to put a nice finish on the suggestions table.

<style type="text/css">
@import "../dijit/themes/soria/soria.css";
@import "../dojo/resources/dojo.css";
@import "../dojoz/grid/DataGrid/DataGrid.css";
@import "../dojox/grid/_grid/Grid.css";
@import "../dojox/grid/_grid/soriaGrid.css";

#grid { position: absolute; left: 9px; top: 11px; }
#forms { position: absolute; left: 14px; top: 350px; }
</style>

The final step towards enabling this style is to add an attribute to the HTML. This will allow the HTML document to take on the Dojo style imported in the above markup. Change the body tag to the following snippet:

<body class="soria">

Test the application

Test the UI. Restart the WebSphere sMash application if you had previously stopped it, and then visit http://localhost:8080/secure/ in your web browser. Use one of the sample user names and the password mypassword to login, and then wait while the Dojo grid widget loads the suggestion data. The initial load may take a few seconds, but will be more responsive on subsequent actions. Below is a screenshot of the page we've built:

The suggestion box user interface

You can now experiment with the add and vote functions and watch the data update in the grid; to confirm that everything is really working, you can vote in the web interface and then read the updated suggestion data using the REST API.

Completed example

The following code sample illustrates the completed index.html after following the above steps:

<html>
    <head>
        <script type="text/javascript" src="/dojo/dojo.js" djConfig="parseOnLoad: true, isDebug: false, usePlainJson: true"></script>

        <script type="text/javascript">
        dojo.require("dojo.parser");
        dojo.require("zero.resource.DataStore");
        dojo.require("zero.grid.DataGrid");
        dojo.require("dijit.form.Form");
        dojo.require("dijit.form.Button");
        </script>

        <script type="text/javascript">
        function vote() {
            var thegrid = dijit.byId("thegrid");
            var rows = thegrid.grid.selection.getSelected();
            if (rows.length != 1) {
                alert("select one row!");
            } else {
                var item = thegrid.grid.model.getRow(rows[0]).__dojo_data_item;
                thestore.setValue(item, "votes", ++item.votes);
                thegrid.grid.selection.clear();
                thestore.save({
                    onComplete: function() {
                        thegrid.onSaved();
                    },
                    onError: function() {
                        alert("error");
                    }
                });
            }
        }
        </script>

        <script type="text/javascript">
        function add() {
            var newitem = {};
            newitem.submitter = document.newform.submitter.value;
            newitem.suggestion = document.newform.suggestion.value;
            newitem.votes = 0;
            var thegrid = dijit.byId("thegrid");
            thegrid.grid.addRow(newitem);
            thestore.save({
                onComplete: function() {
                    thegrid.onSaved();
                },
                onError: function() {
                    alert("error");
                }
            });
        }
        </script>

        <style type="text/css">
        @import "../dijit/themes/soria/soria.css";
        @import "../dojo/resources/dojo.css";
        @import "../dojoz/grid/DataGrid/DataGrid.css";
        @import "../dojox/grid/_grid/Grid.css";
        @import "../dojox/grid/_grid/soriaGrid.css";

        #grid { position: absolute; left: 9px; top: 11px; }
        #forms { position: absolute; left: 14px; top: 350px; }
        </style>

    </head>

    <body class="soria">
        <span dojoType="zero.resource.DataStore" jsId="thestore"
            resourceCollection="suggestions"></span>

        <div id="grid">
            <div dojoType="zero.grid.DataGrid" id="thegrid"
                readonly="true"
                visibleFields="suggestion,submitter,votes"
                store="thestore"
                style="width: 500px; height: 300px;"></div>
        </div>

        <div id="forms">
            <div dojoType="dijit.form.Button"
                id="vote_btn" label="Vote for selected"
                onclick="vote()"></div>

            <form dojoType="dijit.form.Form" id="newform" name="newform">
                <p>
                    <label for="suggestion">Suggestion:</label>
                    <input dojoType="dijit.form.TextBox" id="suggestion" name="suggestion" type="text"></input>
                </p>
                <p>
                    <label for="submitter">Submitter:</label>
                    <input dojoType="dijit.form.TextBox" id="submitter" name="submitter" type="text"></input>
                </p>
                <p>
                    <div dojoType="dijit.form.Button" id="Button_2" label="Submit" onclick="add()"></div>
                </p>
            </form>
        </div>
    </body>
</html>

Results

This tutorial showed how to model a resource type using ZRM, create a database to represent it, and expose the data with a RESTful API. We were then able to read and write that data from a web browser with a few lines of JavaScript thanks to the Dojo Toolkit and WebSphere sMash's own ZRMStore class. The ease with which we can model a resource type and connect to it from a client allows anyone to build quick prototypes that serve as the foundation for a complete application.

If you wish to see the zero.suggest.demo application as completed by the author using the Application Builder IDE, you can get it from the WebSphere sMash CLI by typing:

$ zero create suggestionbox from zero:zero.suggest.demo

Running this will clone the application from the repository. In order to run correctly, you must first run zero model sync before zero start.

Version 1.0.0.4.27542