TodoDS

By John Kostaras


                                    D R A F T


Contents

Introduction

It all started in May 2006, when the first issue of the NetBeans magazine was published. There, the Todo PIM application, written in Swing, was described in the article "A complete App using NetBeans 5" by Fernando Lozano.

That application has been ported to various technologies, since then:

  • In the TodoRCP article, which has also been published in Java Magazine, the PIM Java Swing application was ported to its equivalent NetBeans Rich Client Platform (RCP).
  • An updated article (TodoRCP2) ported the TodoRCP application to Java 8 and NetBeans 8.
  • In the TodoFX article, it was ported to JavaFX
  • A follow-up TodoFX2 article, added JavaFX graphs and JPA support
  • The Swing Todo application was also ported to JavaScript using Knockout.js (TodoKO article) which is recommended to read before reading this article

In this article we shall, one more time, port the Todo Java Swing application to DukeScript. DukeScript is a new technology that allows you to write your logic in Java and render the result to a number of clients, which can be web browser, portable devices etc. It achieves this by transforming Java code to HTML 5 (Javascript) which can run in any device. It is a true Write Once Run Everywhere. DukeScript uses the following technologies which you (or your team) need to master if you wish to effectively work with DukeScript:

It is recommended to first read the TodoKO article for a short introduction to Knockout.js and in order to compare DukeScript with the pure JavaScript solution.

Please refer here for a short description of the requirements and the steps we followed in the previous articles to develop the PIM application. The same steps will be followed to build the DukeScript version of it.

Prerequisites and Setup

  • JDK 7 even though previous versions of JDK can be used
  • NetBeans 8.0 or later (8.2 was used for this article)
  • DukeScript

Before you start you need to install the DukeScript plugin. Go to Tools -> Plugins -> Available Plugins and select DukeScript Project Wizard and (optionally) Mine Sweeper (a game application written in DukeScript). Click on the Install button to install them. (Restart of the IDE maybe needed).

Step 1 - Build a static visual prototype of the UI

The purpose of this step is to build the initial user interface prototype to show end-users, i.e. the tasks list and the task-editing form, in order to discuss the dynamics of user interaction in the application and the basic business process involved. This functional prototype reacts to user input but won't persist any data.

DukeScript claims to have a clean separation of design and development. With DukeScript it is possible to completely outsource the UI design to a designer with no knowledge of DukeScript, or a specific set of tools. Because designing interfaces is not an easy job, for most UI technologies there are tools that allow us to design and test the User Interfaces via drag and drop. For example, for Swing there is the Matisse GUI Builder in NetBeans and the Window Builder in Eclipse which help a lot getting the layout of forms right, for JavaFX there is the SceneBuilder that helps us at least with FXML etc. Dukescript uses HTML for the framework's UI and there are plenty of tools to build HTML UIs with the help of CSS and it is a well known technology to UI designers. There are also plenty of ready-made designs in the web which you can download and use. Ref. 2 has such an example. We won't go for downloading a professional UI design in this article.

  1. Create a new Project (File -> New Project) and choose DukeScript under Categories and DukeScript Application under Projects. Click Next.

File:TodoDS-Fig1.png

2. In Step 2, specify the location where to create your project and the Maven coordinates. The Maven group id will automatically be used as the base package of your application. Click Next.

File:TodoDS-Fig2.png

3. The next wizard step asks you about your target platforms. If you have the Android SDK installed, you can choose Android, and/or iOS if you have Apple's SDK installed. In addition to what you selected, a Desktop Client will automatically be created for you by default that is used for testing and debugging. Click Next.

File:TodoDS-Fig3.png

4. In the last wizard step you can choose between the available project templates. Choose the simplest one Knockout 4 Java Example.

File:TodoDS-Fig4.png

Your project should look like the following figure:

File:TodoDS-Fig5.png

After the project has been created, the wizard will automatically run a priming build which downloads all the required artifacts. At the same time, the TodoDS General Client Code maven project opens by default; this is the Desktop Client test application that was mentioned in step 4 above. It contains two Java source files:

package ds.todo;

import ds.todo.js.PlatformServices;
import net.java.html.boot.BrowserBuilder;

public final class Main {
    private Main() {
    }

    public static void main(String... args) throws Exception {
        BrowserBuilder.newBrowser().
            loadPage("pages/index.html").
            loadClass(Main.class).
            invoke("onPageLoad", args).
            showAndWait();
        System.exit(0);
    }

    /**
     * Called when the page is ready.
     */
    public static void onPageLoad(PlatformServices services) throws Exception {
        DataModel.onPageLoad(services);
    }

    public static void onPageLoad() throws Exception {
        DataModel.onPageLoad(new DesktopServices());
    }

    private static final class DesktopServices extends PlatformServices {
    }
}
package ds.todo;

import net.java.html.json.Model;
import ds.todo.js.PlatformServices;
import net.java.html.json.ModelOperation;
import ds.todo.js.PlatformServices;

@Model(className = "Data", targetId="", instance=true, properties = {
})
final class DataModel {
    private PlatformServices services;

    @ModelOperation
    void initServices(Data model, PlatformServices services) {
        this.services = services;
    }
    private static Data ui;
    /**
     * Called when the page is ready.
     */
    static void onPageLoad(PlatformServices services) throws Exception {
        ui = new Data();
        ui.initServices(services);
        ui.applyBindings();
    }
}

and one index.html file:

<!DOCTYPE html>
<html>
    <head>
        <title></title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
<!-- ${browser.bootstrap} -->
    </body>
</html>

The View is the index.html HTML 5 file and we shall see how to bind it to the model in step 2, more accurately to the ViewModel. Running the TodoDS General Client Code you should see an empty window like the one in the following figure, with some buttons to try how you UI will look in different screen sizes.

File:TodoDS-Fig6.png

Since we are dealing with HTML and CSS, it should be easy to design our prototype. We could use a <table>...</table> to display our tasks list window, or follow the modern trends and use <div>s as described here.

Please follow TodoKO Step 1 on how to build the static prototype and update index.html the same way.

Step 2 - Build a dynamic prototype of the application

The second step – building the "dynamic prototype" – aims to implement as much user interaction as possible without using a persistent storage or implementing complex business logic. The original article 1 uses two well-known design patterns in the Todo application: DAO (Data Access Object) and the MVC (Model-View Controller). DukeScript uses the Model-View-ViewModel (MVVM) design pattern and Knockout is used to separate the view and the viewmodel. Knockout supports declarative bindings, which allows a much cleaner separation of UI and business code. The View is the index.html HTML file, as we saw in the previous step, and we shall bind it to the model in this step, more accurately to the ViewModel. The dynamic parts of the view are bound to the ViewModel, so they display data from the ViewModel and update whenever the ViewModel changes. The View can also bind actions like mouse clicks to function calls.

The VO (Value Object) named Task is the ViewModel in MVVM and it is used for moving information between application tiers. Therefore the view classes (such as the TaskMain and TaskDetailsDialog in original Todo application which correspond to index.html and edit.html respectively in our TodoDS application) will receive and return either Task objects or collections of Task objects.

While the TodoKO article uses pure JavaScript to add interactions, DukeScript uses pure Java.

Now it's time to explain the Java code. The main() method loads the html page, then the Main class and then calls the onPageLoad() method which is responsible for loading the data. This is delegated to the DataModel class which is the Model of MVVM which contains the stored data and operations of our application, representing e.g. a device or data in a database. This initialises an instance of a Data class and calls applyBindings(). But where is this class defined? You will notice that the @Model annotation defines a class Data. This is the ViewModel of MVVM, i.e the model used by the View. With this annotation the DukeScript framework can take care of generating boilerplate code. You just declare your Properties and their type and the class is generated for you. You can find the generated source code inside Generated Sources (annotations).

But let's create the ViewModel for our Todo application. Rename the class DataModel to ViewModel (to be more consistent with the above) - don't forget to change it in Main, too - and modify it like so:

@Model(className = "Task", targetId="", instance=true, properties = {
    @Property(name = "id", type = int.class),
    @Property(name = "description", type = String.class),
    @Property(name = "priority", type = int.class),
    @Property(name = "dueDate", type = String.class),
    @Property(name = "alert", type = boolean.class),
    @Property(name = "daysBefore", type = int.class),
    @Property(name = "obs", type = String.class),
    @Property(name = "completed", type = boolean.class)
})
final class ViewModel {
    private PlatformServices services;

    @ModelOperation
    void initServices(Task model, PlatformServices services) {
        this.services = services;
    }
    private static Task task;
    /**
     * Called when the page is ready.
     */
    static void onPageLoad() throws Exception {
        task = new Task();
        task.initServices(services);
        task.applyBindings();
    }
}

Compare to the Task class of the original Todo application (since LocalDate is not supported per se, dueDate's data type is now String). Compare it to the Task object of the TodoKO article, too. Clean and build the TodoDS General Client Code maven module and you shall see the generated Task class inside Generated Sources (annotations) folder. Together with the setters and getters you will see a number of other methods together with the applyBindings() method mentioned before, which tells Knockout to bind the View to the ViewModel.

You now need to remove the sample data from index.html and bind the index.html to Task adding <data-bind> attributes like in the following code sample (see also TodoKO article):

<div class="rTableRow"> 
   <div class="rTableCell" data-bind="text: priority"></div> 
   <div class="rTableCell" data-bind="text: description"></div> 
   <div class="rTableCell" data-bind="text: alert"></div> 
   <div class="rTableCell" data-bind="text: dueDate"></div> 
...

or

<td data-bind="text: priority"></td> 
<td data-bind="text: description"></td> 
<td data-bind="text: alert"></td> 
<td data-bind="text: dueDate"></td> 

Let's add the same test data to our ViewModel:

static void onPageLoad() throws Exception {
    Task task = new Task();
    task.setPriority(5);
    task.setDescription("Finish TodoDS article!");
    task.setAlert(true);
    task.setDueDate("2017-03-10");
    task.applyBindings();
}

If you Clean and Build and then run the TodoDS General Client Code maven project you should see output similar to the following:

File:TodoDS-Fig12.png

Since we have a list of tasks we shall use an ObservableArray instead of a single Object (compare it to TasksViewModel of TodoKO article):

@Model(className = "TaskList", targetId = "", properties = {
    @Property(name = "tasks", type = Task.class, array = true)
})
final class ViewModel {

    /**
     * Called when the page is ready.
     */
    static void onPageLoad() throws Exception {
        Task task = new Task();
        task.setPriority(5);
        task.setDescription("Finish TodoDS article!");
        task.setAlert(true);
        task.setDueDate("2017-03-2017");
        TaskList taskList = new TaskList();
        taskList.getTasks().add(task);
        taskList.getTasks().add(
            new Task(2, "Book conference room!", 10, "2017-04-01", false, 2, "", false));
        taskList.applyBindings();
    }

    @Model(className = "Task", targetId = "", properties = {
        @Property(name = "id", type = int.class),
        @Property(name = "description", type = String.class),
        @Property(name = "priority", type = int.class),
        @Property(name = "dueDate", type = String.class),
        @Property(name = "alert", type = boolean.class),
        @Property(name = "daysBefore", type = int.class),
        @Property(name = "obs", type = String.class),
        @Property(name = "completed", type = boolean.class)
    })
    public static class TaskModel {  }
}

Pay attention to the array = true which defines the TaskList as an ObservableArray.

The View needs to be modified slightly in order to reference these tasks by making use of the foreach binding. The foreach binding copies a section of markup foreach object in an array and binds the markups attributes to the objects' Properties (be careful to add the binding to the second rTable):

<div class="rTable" data-bind="foreach: tasks"> 

The output is much nicer:

File:TodoDS-Fig13.png

but the header appears before each row! There is a fix: use containerless knockout syntax below the header <!-- ko foreach: tasks -->...<!-- /ko -->:

<div class="rTable"> 
   <div class="rTableHeading">
...
   </div>
<!-- ko foreach: tasks -->
   <div class="rTableRow" > 
...
   </div>
<!-- /ko -->

and the problem is gone. Now the output should be like in the original prototype.

File:TodoDS-Fig9.png

If you used the <table> tags instead of the <div>s then your job is easier:

<tbody data-bind="foreach: tasks"> 
  <tr>
    <td data-bind="text: priority"></td> 
    <td data-bind="text: description"></td> 
    <td data-bind="text: alert"></td> 
    <td data-bind="text: dueDate"></td>
 ... 

The application should behave as before. You can play with various @media blocks to add support for various browsers etc. but I 'll leave this to you.

Let's see how we can add user interaction to our index.html, e.g. delete a task from the task list. In the original Todo application, this was accomplished by the following method of the TaskManager:

    public void removeTask(final int id) {
        tasks.removeIf(task -> id == task.getId());
    }

This method is ported like so in our ViewModel (compare it to the remove() function in the TodoKO article):

    @Function
    public static void removeTask(TaskList tasks, Task data) {
        tasks.getTasks().remove(data);
    }  

The @Function annotation defines a function that returns void and allows it to be called by the UI. The arguments of this method define what will be passed to it. The Dukescript framework does its magic and injects the correct values into them. The Task parameter has to be named data. The UI binds to this method with the data-bind attribute:

<a class="pure-button" data-bind="click: $parent.removeTask">
   <img src="../resources/icons/delete_edit.gif" 
        alt="Remove Task..." title="Remove Task..."/>
</a>

What is $parent? Inside a table row you are inside a Task class and you want to access a method that is defined one level up (in TaskList), that is in the parent class.

Try it! Clean and build and run your application. Click on the X button to verify that the task disappears.

When we want to return a value from a function, we use a@ComputedProperty instead. ComputedProperties are Properties derived from other Properties. This also means that whenever one of the Properties it is depending on changes, the ComputedProperty changes as well.

Currently, the number of tasks with alert is hardcoded in the footer. Let's change that using ComputedProperties. Add the following in the ViewModel (compare it to the respective functions in the TodoKO article):

private static List<Task> listTasksWithAlert(List<Task> tasks) {
    List<Task> result = new ArrayList<>(tasks.size());
    for (Task task : tasks) {
        if (task.isAlert()) {
           result.add(task);
        }
    }  
    return result; 
}    

@ComputedProperty
public static int numberOfTasksWithAlert(List<Task> tasks) {
   return listTasksWithAlert(tasks).size();
}    

and update index.html like so:

<div class="footer">There are <label data-bind="text:
     $data.numberOfTasksWithAlert"/></label> task(s) with alerts today.</div>

The $data reference will let you access the context object directly. We don't need to access the parent, because this is the parent. We do this by the $data reference. Clean, build and run the application, remove a task with alert to verify that the number of tasks with alert are reduced. I know you are tempted to use Java 8 Streams here, however, bk2brwsr, the JVM engine that runs in the web browser, doesn't support lambdas yet.

Also, since our model (Task) is now auto-generated, we don't have much control about how the accessor methods are named, hence there is no hasAlert() but rather the not so convenient isAlert() method.

Of course, you may add extra methods to TaskModel:

public static class TaskModel {
    private static final SimpleDateFormat DATE_FORMATTER = 
        new SimpleDateFormat("yyyy-MM-dd");  // HSQLDB uses this format
    @ComputedProperty
    public static boolean isLate(String dueDate) {
        if (dueDate == null || dueDate.isEmpty()) {
            return false;
        }
        Date dateDue = null;
        try {
            dateDue = DATE_FORMATTER.parse(dueDate);
        } catch (ParseException ex) {
            Logger.getLogger(ViewModel.class.getName())
                  .log(Level.WARNING, null, ex);
        }
        return (dateDue == null) ? false : 
               dateDue.compareTo(Calendar.getInstance().getTime()) < 0;
    }

    @ComputedProperty
    static boolean hasAlert(String dueDate, boolean alert, int daysBefore) {
        if (dueDate == null || dueDate.isEmpty()) {
            return false;
        }
        Date dateDue = null;
        try {
            dateDue = DATE_FORMATTER.parse(dueDate);
        } catch (ParseException ex) {
            Logger.getLogger(ViewModel.class.getName())
                  .log(Level.SEVERE, null, ex);
        }
        if (!alert || dateDue == null) {
            return false;
        } else {
            Calendar cal = Calendar.getInstance();
            cal.setTime(dateDue);
            int dias = cal.get(Calendar.DAY_OF_YEAR) - 
                       Calendar.getInstance().get(Calendar.DAY_OF_YEAR);
            return dias <= daysBefore;
        }
    }
}  

Let's continue adding functionality. The functionality for the Show Completed... and Sort By Priority... toggle buttons can be summarized in a single ComputedProperty:

@ComputedProperty
public static List<Task> sortedAndFilteredTasks(List<Task> tasks, 
                            boolean sortByPriority, boolean showCompleted) {
    List<Task> result = new ArrayList<>();
    if (showCompleted) {
        for (Task task : tasks) {
            if (task.isCompleted()) {
                result.add(task);
            }
        }
    } else {
        result.addAll(tasks);
    }

    if (sortByPriority) {
        result.sort(new PriorityComparator());
    } else {
        result.sort(new DueDateComparator());            
    }
    return result;
}    
//...

private static class PriorityComparator implements Comparator<Task> {

    @Override
    public int compare(final Task t1, final Task t2) {
        if (t1.getPriority() == t2.getPriority()) {
            return 0;
        } else if (t1.getPriority() > t2.getPriority()) {
            return 1;
        } else {
            return -1;
        }
    }
}

private static class DueDateComparator implements Comparator<Task> {

    @Override
    public int compare(final Task t1, final Task t2) {
        try {
            Date t1DateDue = DATE_FORMATTER.parse(t1.getDueDate());
            Date t2DateDue = DATE_FORMATTER.parse(t2.getDueDate());
            return t1DateDue.compareTo(t2DateDue);
        } catch (ParseException ex) {
            Logger.getLogger(ViewModel.class.getName()).log(Level.WARNING, null, ex);
            return -1;
        }
    }
}    

Just move the declaration of DATE_FORMATTER to the ViewModel. How do we link to our UI? You should be aware of how by now:

<input id="tglShowCompleted" type="checkbox" title="Show Completed..."
       checked="showCompleted" name ="tglShowCompleted" 
       class="showCompletedCheckbox" data-bind="checked: showCompleted"/>
...
<input id="tglSortByPriority" type="checkbox" title="Sort By Priority..."
       checked="sortByPriority" name ="tglSortByPriority" 
       class="sortByCheckbox" data-bind="checked: sortByPriority"/>
...
<tbody>
    <!--<tbody data-bind="foreach: sortedAndFilteredTasks">--> 
    <!-- ko foreach: sortedAndFilteredTasks -->
...
    <!-- /ko -->
</tbody>

See how the foreach has now been replaced with a ko foreach that uses the new sortedAndFilteredTasks computed property. It is similar for the <div> version of index.html.

For the above to work, you need to define two new properties to the ViewModel:

@Model(className = "TaskList", targetId="", instance=true, properties = {
    @Property(name = "tasks", type = Task.class, array = true),
    @Property(name = "showCompleted", type = boolean.class),
    @Property(name = "sortByPriority", type = boolean.class)
})

The functionality for the Mark As Completed checkbox on the right toolbar of each task is easy to add, too.

<td data-bind="text: priority, 
    style: { 'background-color': completed() ? 'green' : 'white' }"></td> 
<td data-bind="text: description, 
    style: { 'background-color': completed() ? 'green' : 'white' }"></td> 
<td data-bind="text: alert, 
    style: { 'background-color': completed() ? 'green' : 'white' }"></td> 
<td data-bind="text: dueDate, 
    style: { 'background-color': completed() ? 'green' : 'white' }"></td> 
...
<input id="tglMarkAsCompleted" type="checkbox" 
       name ="tglMarkAsCompleted" title="Mark As Completed..." 
       checked="completed" data-bind="checked: completed"/>

From the first part of the above HTML code snippet you can see that you can also control the style and content, here directly adding an inline style to display the row of the completed task as green. Pay attention to completed(); the parentheses are important. For KO completed is a Javascript function, not a variable, calling it will either return true or false. KO understands completed, but that is just syntactic sugar.

There are many KO bindings that control the style and content (like text, style) or respond to input (like click, value).

For the Show Alerts..., we need to display dialog boxes for every task with alert. DukeScript provides a dedicated project JavaScript libraries that allows you to display JavaScript from Java and handle JavaScript code in Java. See here for more information.

Let's try to do it with CSS instead like in the TodoKO article. Update index.css adding the necessary overlay functionality for the popup:

.overlay {
    z-index: 10;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background: rgba(0, 0, 0, 0.7);
    transition: opacity 500ms;

}
.overlay:target {
    visibility: visible;
    opacity: 1;
}

.popup {
    margin: 70px auto;
    padding: 20px;
    background: #fff;
    border-radius: 5px;
    width: 30%;
    position: relative;
    transition: all 5s ease-in-out;
}

.popup h2 {
    margin-top: 0;
    color: #333;
    font-family: Tahoma, Arial, sans-serif;
}
.popup .close {
    position: absolute;
    top: 20px;
    right: 30px;
    transition: all 200ms;
    font-size: 30px;
    font-weight: bold;
    text-decoration: none;
    color: #333;
}
.popup .close:hover {
    color: #06D85F;
}
.popup .content {
    max-height: 30%;
    overflow: auto;
}

and index.html like so:

<div id="popup1" class="overlay" data-bind="visible: dialog, style: {opacity: dialog ? 1 : 0}">
    <div class="popup">
        <h2>Alert</h2>
        <a class="close" href="#" data-bind="click: hideDialog">×</a>
        <div class="content">
            <span data-bind="text: message"></span>
        </div>
    </div>
</div>  
...
<a class="pure-button" data-bind="click: expiredTasks">
     <img src="../resources/icons/warning.gif" 
          alt="Show Alerts..." title="Show Alerts..."/>
</a>

From the above bindings you can guess how we need to update our ViewModel. First, add two more properties to our TaskList model:

    @Property(name = "dialog", type = boolean.class),
    @Property(name = "message", type = String.class)

Then add the required functions:

@Function
static void expiredTasks(final TaskList tasks) {
    List<Task> listTasksWithAlert = listTasksWithAlert(tasks.getTasks());
    for (Task task : listTasksWithAlert) {
        showExpiredTask(tasks, task);
    }
}

private static void showExpiredTask(TaskList tasks, Task task) {
    tasks.setMessage("Task: " + task.getDescription() + "\nexpired on " + task.getDueDate());
    tasks.setDialog(true);     
}    

@Function
public static void hideDialog(final TaskList tasks){
    tasks.setDialog(false);
}    

File:TodoDS-Fig14.png

Cool, isn't it? Of course this solution cannot handle the case where there are more than one alerts. How do you fix this?

The remaining functionality is the Add Task... and Edit Task... buttons.

Applications written with DukeScript typically are single pages, and the scope of a Model is a single page. Still we need a way to mimic the behaviour that you typically get in a web application with several linked HTML-pages, like our index.html and edit.html. To overcome this problem we use Knockout templates.

The template binding has a name parameter. Knockout will look for a script tag with the same id as specified by the name parameter (in the following example name and id have the same value task):

<div data-bind="template: {name: 'task'}"></div>
<script type="text/html" id="task">
<h2>Tasks</h2>
  <div class="rTable">
...
  <div class="footer">There are 
       <label data-bind="text: $data.numberOfTasksWithAlert"/></label> 
       task(s) with alerts today.</div>
</script>

Make sure that the content of the script tag won't be executed as Javascript. For that we specify type='text/html'.

Let's see how can we apply this to switch between our index.html and edit.html. Edit index.html like so:

<!-- ${browser.bootstrap} -->
<div data-bind="template: {name: 'task', if: !edited()}"></div>
<div data-bind="template: {name: 'editor', if: edited(), data: edited()}"></div>
<script type="text/html" id="task">
    <h2>Tasks</h2>
   ...
    <div class="footer">There are 
         <label data-bind="text: $data.numberOfTasksWithAlert"/></label> 
         task(s) with alerts today.</div>
</script>
<script type="text/html" id="editor">
</script>

Here we define two KO templates, one with id="task" and one with id="editor". The first contains the list of tasks, and inside the second we will add the contents of edit.html also bound to properties of our model:

<script type="text/html" id="editor">
    <form id="edit-form" class="pure-form pure-form-aligned">
        <fieldset> 
            <legend><h2>Create/Edit Task</h2></legend>
            <div class="pure-control-group">
                <label for="description">Description:</label>
                <input id="description" class="pure-input-1-2" type="text" placeholder="Description" 
                       data-bind="textInput: description" required></input>
            </div>
            <div class="pure-control-group">
                <label for="priority">Priority:</label>
                <input id="priority" class="pure-input-1-2" type="number" placeholder="Priority" 
                       min="1" max="10" data-bind="textInput: priority" required></input>
            </div>
            <div class="pure-control-group">
                <label for="dueDate">Due Date:</label>
                <input id="dueDate" class="pure-input-1-2" type="text" placeholder="Due Date" 
                       data-bind="textInput: dueDate" required></input>
            </div>
            <div class="pure-control-group">
                <label for="alert" class="pure-checkbox">
                    <input id="alert" type="checkbox" data-bind="checked: alert"></input>Show alert:
                </label>
                <input class="pure-input-1-3" type="number" min="0" max="365" 
                       data-bind="textInput: daysBefore"></input>
                <span class="pure-form-message-inline">days before</span>
            </div>
            <div class="pure-control-group">
                <label for="obs">Obs:</label>
                <textarea id="obs" class="pure-input-1-2" rows="4" cols="50" placeholder="Obs" 
                          data-bind="textInput: obs"></textarea>
            </div>
            <div class="pure-control-group">
                <label for="completed">
                    <input id="completed" type="checkbox" 
                           data-bind="checked: completed"></input>Completed Task
                </label>
            </div>
            <div class="pure-controls">
                <button class="pure-button pure-button-primary" 
                        type="submit" data-bind="click: $root.commit">Save</button>
                <button class="pure-button" type="reset">Clear</button>
                <button class="pure-button" data-bind="click: $root.cancel" 
                        type="submit">Cancel</button>
            </div>
        </fieldset>
    </form>
</script>  

edit.html is not needed any more.

A number of bindings have been added. First we need to add two new properties to our ViewModel that represent the edited and the selected task:

    @Property(name = "selected", type = Task.class),
    @Property(name = "edited", type = Task.class)

The edited property is used by the KO to decide which one of the two templates to load. We also add an add() and an edit() method:

    @Function
    static void addNew(TaskList tasks) {
        tasks.setSelected(null);
        tasks.setEdited(new Task());
    }

    @Function
    static void edit(TaskList tasks, Task data) {
        tasks.setSelected(data);
        tasks.setEdited(data.clone());
    } 

which are bound to the respective buttons:

<a class="pure-button" data-bind="click: addNew">
   <img src="../resources/icons/add_obj.gif" 
        alt="Add Task..." title="Add Task..."/>
</a>
...
<a class="pure-button" data-bind="click: $root.edit">
  <img src="../resources/icons/configs.gif" 
       alt="Edit Task..." title="Edit Task..."/>
</a>
...

Don't forget to initialise the TaskList:

static void onPageLoad() throws Exception {
    TaskList taskList = new TaskList();
    taskList.setSelected(null);
    taskList.setEdited(null);
...

We are still missing the bindings of our edit form (compare them to the TodoKO article):

@Function
static void commit(TaskList tasks) {
    final Task task = tasks.getEdited();
    if (task == null) {
        return;
    }
    final Task selectedTask = tasks.getSelected();
    if (selectedTask != null) {
        tasks.getTasks().set(tasks.getTasks().indexOf(selectedTask), task);
    } else {
        tasks.getTasks().add(task);
    }
    tasks.setEdited(null);
}    

@Function
static void cancel(TaskList tasks) {
    tasks.setSelected(null);
    tasks.setEdited(null);
} 

The commit() function basically stops editing by setting the edited property to null again.

Clean and build and run again to now see a fully working application! Click on Add Task... and Edit Task... buttons to display the edit form and on Save, Clear or Cancel buttons to return back to the list of tasks.

But wait, there is still one thing that is missing. Validation! If you type an invalid priority (e.g. 12) or invalid date then, if there is no exception thrown, the application doesn't complain! This shouldn't be the case. In any case we don't trust our users, do we? So since, not all browsers fully support HTML 5, yet we need to enforce validation in our code.

First, let's add a line in our form to display the error message:

<div class="warning" data-bind="text: validate"></div>

and in index.css add:

.warning {
    color: red;
}

Define a method validate() in our ViewModel to be used when the user clicks on the Commit button:

private static boolean validate(Task task) {
    String invalid = null;
    if (task.getValidate() != null) {
        invalid = task.getValidate();
    }
    return invalid == null;
}
...
@Function
static void commit(TaskList tasks) {
    final Task task = tasks.getEdited();
    if (task == null || !validate(task)) {
        return;
    }
...   

which requires the definition of a validate() method in our TaskModel:

@ComputedProperty
static String validate(String description, int priority, String dueDate, int daysBefore) {
    String errorMsg = null;
    if (description == null || description.isEmpty()) {
        errorMsg = "Specify a description";
    }
    if (errorMsg == null && (priority < 1 || priority > 10)) {
        errorMsg = "Priority must be an integer in the range 1-10";
    }
    if (errorMsg == null) {
        if (dueDate == null) {
            errorMsg = "Specify a valid due date";
        } else {
            try {
                Date dateDue = DATE_FORMATTER.parse(dueDate);
                if (dateDue == null) {
                    errorMsg = "Specify a valid due date";
                }
            } catch (ParseException e) {
                errorMsg = "Specify a valid due date";
            }
        }
    }
    if (errorMsg == null && (daysBefore < 0 || daysBefore > 365)) {
        errorMsg = "Days before must be an integer in the range 0-365";
    }

    return errorMsg;
}

File:TodoDS-Fig15.png

Step 3 - Code the persistence logic

The Todo application uses HSQLDB, an embedded Java database. This allows the application to meet the ease of deployment requirements for a typical desktop application.

Follow the steps in the original article's Step 3 in order to setup HSQLDB with NetBeans. Add hsqldb.jar to General Client Code project's Runtime Dependencies by following these steps:

  1. Right-click on Runtime Dependencies
  2. Add Dependency
  3. Fill the form as shown in the following figure
  4. Click on Add

File:TodoDS-Fig16.png

Next time you clean and build (or Build with Dependencies) the project it will download hsqldb 2.3.4 to your local maven repository.

To finish the application, you need to refactor copy five more classes from the original to-do application to a new ds.todo.persistence package: TaskManager, Parameters, ModelException, ValidationException and DatabaseException.

You need to modify the classes a bit:

package ds.todo.persistence;

import java.io.File;
import java.util.prefs.*;

public class Parameters {
    
    private static final String jdbcDriver = "org.hsqldb.jdbcDriver";
    private static final String defaultUrl = "jdbc:hsqldb:";
    private static final String defaultOptions = ";shutdown=true";
    private static final String defaultDatabase = "db/todo";

    private final Preferences prefs = Preferences.userNodeForPackage(Parameters.class);
    
    public String getDatabase() {
        return prefs.get("database", System.getProperty("user.home")
                + File.separator + defaultDatabase);
    }

    public void setDatabase(String database) {
        prefs.put("database", database);
    }
    
    public String getJdbcUrl() {
        return defaultUrl + getDatabase() + defaultOptions;
    }
    
    public String getJdbcDriver() {
        return jdbcDriver;
    }
}

and convert TaskManager to be Singleton and use our Task class as well as some minor adaptations due to dueDate:

import ds.todo.Task;

public final class TaskManager {
...
    private static final SimpleDateFormat DATE_FORMATTER = 
            new SimpleDateFormat("yyyy-MM-dd");

    private TaskManager(Parameters params) {
        this.params = params;
        try {
            connect();
        } catch (DatabaseException ex) {
            Logger.getLogger(TaskManager.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    private final static class SingletonHolder {

        private final static TaskManager INSTANCE = new TaskManager(new Parameters());
    }
...
    private List<Task> query(String where, String orderBy) 
            throws DatabaseException {
    ...
        Date date = rs.getDate(5);
        task.setDueDate(date == null ? 
                        DATE_FORMATTER.format(Calendar.getInstance()) : 
                        DATE_FORMATTER.format(date));
    ...
    private void modify(String sql, Task task) throws DatabaseException {
    ...
       if (task.getDueDate() == null) {
           pst.setDate(4, null);
       } else {
           pst.setDate(4, Date.valueOf(task.getDueDate()));
       }
       pst.setBoolean(5, task.isAlert());
    ...
    public List<Task> listTasksWithAlert() throws ModelException {
        return query("alert = true AND "
                + "datediff('dd', CURRENT_TIMESTAMP, CAST(dueDate AS TIMESTAMP)) <= daysBefore",
                "dueDate, priority, description");
    }

In the last method shown above, curtime() has been changed to CURRENT_TIMESTAMP due to a change in version 2.0 and later of HSQLDB.

As a final step, the ViewModel needs to be adapted:

...
import ds.todo.persistence.DatabaseException;
import ds.todo.persistence.ValidationException;
import ds.todo.persistence.TaskManager;
...
    static void onPageLoad(PlatformServices services) throws Exception {
        TaskList taskList = new TaskList();
        taskList.setSelected(null);
        taskList.setEdited(null);
        List<Task> tasks = TaskManager.getInstance().listAllTasks(true);
        for (Task task : tasks) {
            taskList.getTasks().add(task);
        }
        taskList.applyBindings();
    }

    @Function
    public static void removeTask(TaskList tasks, Task data) {
        tasks.getTasks().remove(data);
        try {
            TaskManager.getInstance().removeTask(data.getId());
        } catch (DatabaseException ex) {
            Logger.getLogger(ViewModel.class.getName()).log(Level.SEVERE, null, ex);
        }        
    }  

    @Function
    static void commit(TaskList tasks) {
        final Task task = tasks.getEdited();
        if (task == null || !validate(task)) {
            return;
        }
        final Task selectedTask = tasks.getSelected();
        if (selectedTask != null) {
            tasks.getTasks().set(tasks.getTasks().indexOf(selectedTask), task);
            try {
                TaskManager.getInstance().updateTask(task);
            } catch (DatabaseException | ValidationException ex) {
                Logger.getLogger(ViewModel.class.getName()).log(Level.SEVERE, null, ex);
            }            
        } else {
            tasks.getTasks().add(task);
            try {
                TaskManager.getInstance().addTask(task);
            } catch (DatabaseException | ValidationException ex) {
                Logger.getLogger(ViewModel.class.getName()).log(Level.SEVERE, null, ex);
            }              
        }
        tasks.setEdited(null);
    }   

This is it! Clean and build your project and you are now able to persist your tasks to a database.

DukeScript and JPA

But what if you wish to use more modern technologies like the Java Persistence API (JPA)? With DukeScript it is possible. First, let's create a new module.

1. Right-click on Modules of TodoDS and select Create New Module....

2. Select Category: Maven and Project: Java Application and click Next.

3. Provide jpa as the Project Name and click Finish.

4. Right-click on the new module and select Properties. Give the Name: todo JPA Support and click OK.

5. Right-click on the ds.jpa package and select New --> Other --> Persistence --> Entity Classes from Database... and click Next

"Figure 17 - Start the JPA wizard"

2. Select your Database Connection, select the TODO table from the list of Available Tables and click on Add to add it to the list of Selected Tables. Click Next.

File:TodoDS-Fig18.png

Annex A - Development Environment for Web

In this article we saw the DukeScript plugin for NetBeans. However, you don't need NetBeans to try and play with DukeScript. DukeScript started as Development Environment for Web or DEW. DEW is a development environment for the Java programming language that runs entirely in a browser (using the Bck2Brwsr VM developed by NetBeans' architect Jaroslav Tulach). DEW is your ultimate fiddle environment that gives you an easy way to fork existing snippets and show your own creativity by saving your own code snippets as Github Gists.

File:TodoDS-Fig1A.png

Let's go through one of the existing examples. From the combo box on top, select e.g. the Greatest Divisor of two numbers example. As you can see, the UI (HTML 5) page is really simple; it contains two textfields and one span for the output. All these are bound to properties of the view model via data-binds.

a: <input data-bind="value:a"/>
b: <input data-bind="value:b"/>
Greatest divisitor of a and b: <span data-bind="text: nsd"></span>

The inputs are bound to the model properties a and b. The result is calculated via the computed property nsd(). The two required parameters for the calculations are passed magically via the DukeScript (or DEW) framework. The ViewModel is initialised in the static block.

@Model(className="GreatDiv", properties={
   @Property(name = "a", type=int.class),
   @Property(name = "b", type=int.class)
 })
class GreatestDivisor {
  @ComputedProperty
  static int nsd(int a, int b) {
    if (a < b) {
      return nsd(b - a, a);
    } else if (a > b) {
      return nsd(a - b, b);
    }
    return a;
  }
  
  static {
     GreatDiv pageModel = new GreatDiv(9,15);
     pageModel.applyBindings();
   }
}

You may read more about DEW here.

Annex B - Knockout.js

In this annex we provide a short introduction to Knockout.js (KO). This will help you compare it with DukeScript. KO is not required by DukeScript, as it can work without it (see e.g. the other two examples CRUD with Jersey Faces and Visual HTML/Java Example in the end of the New Project wizard). However, KO is a nice and easy to use frontend.

The Knockout.js JavaScript library provides a cleaner way to manage complex, data-driven interfaces. Instead of manually tracking which sections of an HTML page rely on the affected data, it lets you create a direct connection between the underlying data and its presentation. After linking an HTML element with a particular data object, any changes to that object are automatically reflected in the DOM. Knockout.js is a client-side library written entirely in JavaScript. IT connects the underlying data model to HTML elements by adding a single HTML attribute.

Knockout.js uses a Model-View-ViewModel (MVVM) design pattern, as we have already mentioned. The ViewModel is a JavaScript representation of the model data, along with associated functions for manipulating the data. DukeScript replaces the JS representation with pure Java. Knockout.js creates a direct connection between the ViewModel and the view, which is how it can detect changes to the underlying data and automatically update the relevant aspects of the user interface.

Knockout.js uses observables to track a ViewModel's properties; they observe their changes and automatically update the relevant parts of the view. To connect a user interface component in the view to a particular observable, you have to bind an HTML element to it. After binding an element to an observable, Knockout.js is ready to display changes to the ViewModel automatically.

Let's start with an example based on this article. We shall use NetBeans of course.

1. Create a new Project (File -> New Project) and choose HTML5/JavaScript under Categories and HTML5/JS Application under Projects. Click Next.

File:TodoDS-Fig1B.png

2. In the next step select No Site Template and click Next.

3. In the last step, uncheck all tools and click Finish.

4. Edit the main page like so:

index.html

<!DOCTYPE html>
<html>
    <head>
        <title>Tasks</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0"> 
    </head>
    <body>
	<h2>Tasks</h2>
	<div> 
	  <span>Priority</span> |
	  <span>Description</span> |  
	  <span>Alert?</span> |
	  <span>Due Date</span> 
	</div>  
	<div> 
	  <div>
	     <span>10</span> |
	     <span>Finish TodoDS article!</span> |  
	     <span>true</span> |
	     <span>10/03/2017</span> 
	  </div>
	</div>
    </body>
</html>

5. Download the latest Knockout.js and save it in a new folder resources/js inside Site Root.

6. Add a reference to it inside <head>:

<head>
    <title>Tasks</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">     
    <script src='resources/js/knockout-3.4.2.js'></script>
</head>

7. Let's create our ViewModel in JavaScript:

<script type="text/javascript">
    var taskViewModel = {
        id: "1",
        description: "Finish TodoDS article!",
        priority: 10,
        dueDate: "10/03/2017",
        alert: true,
        daysBefore: 2,
        obs: "",
        completed: false
    };
    ko.applyBindings(taskViewModel);
</script> 

It is important to place the above code inside <body> tags, otherwise the bindings of the next step won't work. Remember, a ViewModel is a pure JavaScript representation of your model data. This creates a Task with id = 1, and the ko.applyBindings() method tells Knockout.js to use the object as the ViewModel for the page. Compare it to the respective Java ViewModel or TaskModel in the main article.

8. Bind HTML elements to the taskViewModel object. As you already know, Knockout.js uses a special data-bind attribute to bind HTML elements to the ViewModel:

<div> 
    <div>
        <span data-bind='text: priority'></span> |
        <span data-bind='text: description'></span> |  
        <span data-bind='text: alert'></span> |
        <span data-bind='text: dueDate'></span> 
    </div>
</div>

The result looks like Fig 7 of the main article.

9. Time to move forward and copy all css from the main article (i.e. index.css and pure-release css) and paste them inside resources/css (modifying some paths if necessary). Update the ViewModel like so:

<script type="text/javascript">
   var task1 = {
       id: 1,
       description: "Finish TodoDS article!",
       priority: 10,
       dueDate: "10/03/2017",
       alert: true,
       daysBefore: 2,
       obs: "",
       completed: false
   };
   var task2 = {
       id: 2,
       description: "Book conference room",
       priority: 5,
       dueDate: "01/04/2017",
       alert: false,
       daysBefore: 2,
       obs: "",
       completed: false
   };
   var tasksViewModel = {
       tasks: [task1, task2]
   };
   ko.applyBindings(tasksViewModel);
</script>    

and the <body> like so:

<h2>Tasks</h2>
<div class="pure-menu pure-menu-horizontal">
    <ul class="pure-menu-list">
        <li class="pure-menu-item pure-menu-selected">
            <a class="pure-button">
                <img src="resources/icons/add_obj.gif" 
                     alt="Add Task..." title="Add Task..."/>
            </a>
        </li>
        <li class="pure-menu-item pure-menu-selected">
            <input id="tglShowCompleted" type="checkbox" title="Show Completed..."
                   name ="tglShowCompleted" class="showCompletedCheckbox"/>
            <label for="tglShowCompleted"></label>
         </li>
         <li class="pure-menu-item pure-menu-selected">
            <input id="tglSortByPriority" type="checkbox" title="Sort By Priority..." 
                   name ="tglSortByPriority" class="sortByCheckbox"/>
            <label for="tglSortByPriority"></label> 
        </li>
        <li class="pure-menu-item pure-menu-selected">
            <a class="pure-button">
                <img src="resources/icons/warning.gif" 
                     alt="Show Alerts..." title="Show Alerts..."/>
            </a>
        </li>
    </ul>   
</div>        
<table class="pure-table pure-table-bordered"> 
    <thead> 
        <tr>
            <th>Priority</th> 
            <th>Description</th> 
            <th>Alert?</th> 
            <th>Due Date</th> 
            <th></th>
        </tr> 
    </thead>
    <tbody data-bind="foreach: tasks"> 
        <tr>
            <td data-bind="text: priority"></td> 
            <td data-bind="text: description"></td> 
            <td data-bind="text: alert"></td> 
            <td data-bind="text: dueDate"></td>
            <td>
                <div class="pure-menu pure-menu-horizontal" >
                    <ul class="pure-menu-list">
                        <li class="pure-menu-item">
                            <a class="pure-button">
                                <img src="resources/icons/configs.gif" 
                                     alt="Edit Task..." title="Edit Task..."/>
                            </a>
                        </li>
                        <li class="pure-menu-item">
                            <a class="pure-button">
                                <img src="resources/icons/delete_edit.gif" 
                                     alt="Remove Task..." title="Remove Task..."/>
                            </a>
                        </li>
                        <li class="pure-menu-item">
                            <input id="tglMarkAsCompleted" type="checkbox" 
                                   name ="tglMarkAsCompleted" title="Mark As Completed..."/>
                        </li>
                    </ul>
                </div>
            </td>
        </tr> 
    </tbody>
</table>
<hr/>
<div class="warning">There are 1 task(s) with alerts today.</div>   

not forgetting to add the correct references:

<link rel="stylesheet" href="resources/css/index.css">
<link href="resources/css/pure-release-0.6.2/pure-min.css" rel="stylesheet"/>
<link href="resources/css/pure-release-0.6.2/tables-min.css" rel="stylesheet"/>        
<script src='resources/js/knockout-3.4.2.js'></script>

Compare the JavaScript ViewModel to the Java ViewModel of the main article.

However, Knockout.js won’t automatically update the view if you update e.g. a task's description after you have called ko.applyBindings(). This is because we haven't exposed the properties to Knockout.js. Any properties that you want Knockout.js to track must be observable, e.g.:

var task1 = {
    id: ko.observable(1),
    description: ko.observable("Finish TodoDS article!"),

You access observables like so:

task.description()                    // to access it
task.description("new description")   // to modify it

Be very careful not to accidentally assign a value to an observable property with the = operator. This will overwrite the observable, causing Knockout.js to stop automatically updating the view.

But what about functions? First, let's refactor our ViewModel to make it more Object Oriented:

function Task(id, description, priority, dueDate, alert, daysBefore, obs, completed) {
    this.id = ko.observable(id);
    this.description = ko.observable(description);
    this.priority = ko.observable(priority);
    this.dueDate = ko.observable(dueDate);
    this.alert = ko.observable(alert);
    this.daysBefore = ko.observable(daysBefore);
    this.obs = ko.observable(obs);
    this.completed = ko.observable(completed);
}
function TasksViewModel() {
    this.tasks = ko.observableArray([
        new Task(1, "Finish TodoDS article!", 10, "10/03/2017", true, 2, "", false),
        new Task(2, "Book conference room", 5, "01/04/2017", false, 2, "", false) 
    ]);
}
ko.applyBindings(new TasksViewModel());

Observable arrays let Knockout.js track lists of items. As we have already seen, when Knockout.js encounters foreach: tasks, it loops through each item in the ViewModel's tasks property and any markup inside of the loop is evaluated in the context of each item, so text: description actually refers to tasks[i].description.

The whole point of using observable arrays is to let Knockout.js synchronize the view whenever we add or remove items. Let's define a function to remove tasks:

this.removeTask = function (task) {
    this.tasks.remove(task);
};  

and bind it to the respective button:

<li class="pure-menu-item">
    <a class="pure-button">
        <img src="resources/icons/delete_edit.gif" 
             alt="Remove Task..." title="Remove Task..."
             data-bind="click: $root.removeTask"/>
    </a>
</li>

The above function, defined inside TasksViewModel corresponds to @Function annotation in the Java ViewModel of DukeScript.

The fact that we're inside a foreach loop sometimes messes up the this reference and you might get a TypeError when you click on the remove button. We can use a common JavaScript trick to resolve these kinds of scope issues. At the top of the TasksViewModel definition, assign this to a new variable called self:

function TasksViewModel() {
   self = this;

The functionality for the Mark As Completed checkbox on the right toolbar of each task is the same as mentioned in the main article, so no reason to repeat it here.

To display the tasks with alerts:

<div class="warning">There are <label data-bind="text: numberOfTasksWithAlert"/></label> 
task(s) with alerts today.

you need to define the following function and computed property inside TasksViewModel:

// list tasks with alert
self.listTasksWithAlert = function () {
    var tasksWithAlert = [];
    for (var i = 0; i < self.tasks.length; i++) {
        if (self.tasks[i].alert()) {
            tasksWithAlert.push(self.tasks[i]);
        }
    }
    return tasksWithAlert;
};
// number of tasks with alert
self.numberOfTasksWithAlert = ko.computed(function () {
    return self.listTasksWithAlert.length;
}, self);

References

  1. Lozano F. (2006), "A complete App using NetBeans 5", NetBeans Magazine, Issue 1, May.
  2. Epple A. (2016), Java everywhere: Write Once Run Everywhere with DukeScript, Leanpub.
  3. Epple A. (2016), "JPA and DukeScript"
  4. Kostaras J. (2015), Port Your Java Applets
  5. Hodson R. (2012), Knockout.js Succintly, Syncfusion.
Not logged in. Log in, Register

By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2012, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo