TodoKO

By John Kostaras


                                    D R A F T


Contents

Introduction

In this article we shall port the Todo PIM application, written in Swing, described in the article "A complete App using NetBeans 5" by Fernando Lozano, to a pure Javascript web application using Knockout.js.

That application has also been ported to other technologies:

  • 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
  • After reading this article and mastering Knockout JS (KO), it will be easier to understand the porting of the Todo Java Swing application to DukeScript in the TodoDS article. With DukeScript you can replace all KO JavaScript code with pure Java.

To follow this article you will need some knowledge on:

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 KO version of it.

Knockout.js

The Knockout.js JavaScript library provides a cleaner way to manage complex, data-driven interfaces. Knockout.js lets you create a direct connection between the underlying data and its presentation (automatic dependency tracking). 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 the Model-View-ViewModel (MVVM) design pattern which is a variant of the classic Model-View-Controller (MVC) design pattern. As in the MVC, the Model is the stored data (they can be loaded/stored using AJAX calls), and View is a visual representation of the data (e.g. in HTML5). But for the connection between the two, MVVM uses the ViewModel instead of a Controller. In KO, the ViewModel is a JavaScript representation of the model data, along with associated functions for manipulating the data. 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. Knockout.js includes several built-in bindings that determine how the observable appears in the user interface.

Let's see how all these are applied by building our TodoKO web application.

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

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:TodoKO-Fig1.png

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

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

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>5</span> |
	     <span>Finish TodoDS article!</span> |  
	     <span>true</span> |
	     <span>2017-03-10</span> 
	  </div>
	  <div>
	     <span>10</span> |
	     <span>Book conference room.</span> |  
	     <span>false</span> |
	     <span>2017-04-01</span> 
	  </div>
	</div>
    </body>
</html>

and here is the (not so attractive) output:

File:TodoKO-Fig2.png

In the following we build two UIs which you may choose from:

  • one uses <div>s and
  • the other <table>...</table>

Use of divs

Create a new index.css file inside a new folder resources/css inside Site Root, like so:

.rTable {
    display: table;
    width: 100%;
} 
.rTableRow {
    display: table-row;
} 
.rTableHead{
    display: table-cell;
    padding: 3px 10px;
    border: 1px solid #999999;
    background-color: #ddd;
    font-weight: bold;    
}
.rTableCell {
    display: table-cell;
    padding: 3px 10px;
    border: 1px solid #999999;
} 
.rTableHeading {
    display: table-header-group;
    background-color: #ddd;
    font-weight: bold;
} 
.rTableFoot {
    display: table-footer-group;
    font-weight: bold;
    background-color: #ddd;
} 
.rTableBody {
    display: table-row-group;
}

and update index.html like so (inside <body>):

<h2>Tasks</h2>
<div class="rTable"> 
   <div class="rTableRow"> 
      <div class="rTableHead">
          <strong>Priority</strong>
      </div> 
      <div class="rTableHead">
          <span style="font-weight: bold;">Description</span>
      </div> 
      <div class="rTableHead">
          <strong>Alert?</strong>
      </div>
      <div class="rTableHead">
          <strong>Due Date</strong>
      </div>
   </div>
   <div class="rTableRow"> 
       <div class="rTableCell">5</div> 
       <div class="rTableCell">Finish TodoDS article!</div> 
       <div class="rTableCell">true</div> 
       <div class="rTableCell">2017-03-10</div> 
   </div> 
   <div class="rTableRow"> 
       <div class="rTableCell">10</div> 
       <div class="rTableCell">Book conference room.</div> 
       <div class="rTableCell">false</div> 
       <div class="rTableCell">2017-04-01</div> 
   </div> 
</div>
<hr/>
<div class="rTableFoot">There are 1 task(s) with alerts today.</div>

and of course in the <head> don't forget to add:

<link rel="stylesheet" href="resources/css/index.css">

The output is much nicer:

File:TodoKO-Fig3.png

We can easily add buttons to make our prototype tasks list look like our rough sketches

  1. Copy and paste the icons folder from the original Todo application inside resources inside Site Root.
  2. Modify your view like so (later we shall add actions):
<!DOCTYPE html>
<html>
    <head>
        <title>TodoKO</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">  
        <link rel="stylesheet" href="resources/css/index.css">
    </head>
    <body>
        <!-- ${browser.bootstrap} -->
        <h2>Tasks</h2>
        <div class="rTable">
            <div class="rTableRow">
                <div class="rTableCell">
                    <button>
                        <img src="resources/icons/add_obj.gif" 
                             alt="Add Task..." title="Add Task..."/>
                    </button>
                    <button>
                        <img src="resources/icons/tasks_tsk.gif" 
                             alt="Show Completed Tasks" title="Show Completed Tasks"/>
                    </button>
                    <button>
                        <img src="resources/icons/next_nav.gif" 
                             alt="Sort By Priority" title="Sort By Priority"/>
                    </button>
                    <button>
                        <img src="resources/icons/warning.gif" 
                             alt="Show Alerts..." title="Show Alerts..."/>
                    </button>
                </div>   
            </div>
        </div>        
        <div class="rTable"> 
            <div class="rTableHeading"> 
                <div class="rTableHead">
                    <strong>Priority</strong>
                </div> 
                <div class="rTableHead">
                    <span style="font-weight: bold;">Description</span>
                </div> 
                <div class="rTableHead">
                    <strong>Alert?</strong>
                </div>
                <div class="rTableHead">
                    <strong>Due Date</strong>
                </div>
            </div>
            <div class="rTableRow" > 
                <div class="rTableCell">5</div> 
                <div class="rTableCell">Finish TodoDS article!</div> 
                <div class="rTableCell">true</div> 
                <div class="rTableCell">2017-03-10</div> 
                <div class="rTableCell">
                    <button>
                        <img src="resources/icons/configs.gif" 
                             alt="Edit Task..." title="Edit Task..."/>
                    </button>
                    <button>
                        <img src="resources/icons/delete_edit.gif" 
                             alt="Remove Task..." title="Remove Task..."/>
                    </button>
                    <button>
                        <img src="resources/icons/complete_tsk.gif" 
                             alt="Mark As Completed" title="Mark As Completed"/>
                    </button>
                </div>
             </div> 
             <div class="rTableRow" >
                <div class="rTableCell">10</div> 
                <div class="rTableCell">Book conference room.</div> 
                <div class="rTableCell">false</div> 
                <div class="rTableCell">2017-04-01</div> 
                <div class="rTableCell">
                    <button>
                        <img src="resources/icons/configs.gif" 
                             alt="Edit Task..." title="Edit Task..."/>
                    </button>
                    <button>
                        <img src="resources/icons/delete_edit.gif" 
                             alt="Remove Task..." title="Remove Task..."/>
                    </button>
                    <button>
                        <img src="resources/icons/complete_tsk.gif" 
                             alt="Mark As Completed" title="Mark As Completed"/>
                    </button>
                </div> 
            </div>
        </div>
        <hr/>
        <div class="rTableFoot">There are 1 task(s) with alerts today.</div>
    </body>
</html>

The output is more satisfactory now.

File:TodoKO-Fig4.png

Of course the buttons don't do anything, yet. The <title> property of the <img> tag displays a tooltip but this doesn't appear on touch screens.

Use of <table> tags

Even though <table> tags are not used for styling anymore, pure CSS promises to provide modern UIs that run in any device. Download the library and install it inside e.g. resources/css. Modify index.html like so:

<!DOCTYPE html>
<html>
    <head>
        <title>TodoKO</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <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"/>
    </head>
    <body>
        <!-- ${browser.bootstrap} -->
        <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">
                    <a class="pure-button">
                        <img src="resources/icons/tasks_tsk.gif" 
                             alt="Show Completed Tasks" title="Show Completed Tasks"/>
                    </a>
                </li>
                <li class="pure-menu-item pure-menu-selected">
                    <a class="pure-button">
                        <img src="resources/icons/next_nav.gif" 
                             alt="Sort By Priority" title="Sort By Priority"/>
                    </a>
                </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> 
                <tr>
                    <td>5</td> 
                    <td>Finish TodoDS article!</td> 
                    <td>true</td> 
                    <td>2017-03-10</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">
                                    <a class="pure-button">
                                        <img src="resources/icons/complete_tsk.gif" 
                                             alt="Mark As Completed" title="Mark As Completed"/>
                                    </a>
                                </li>
                            </ul>
                        </div>
                    </td>
                </tr> 
                <tr>    
                    <td>10</td> 
                    <td>Book conference room.</td> 
                    <td>false</td> 
                    <td>2017-04-01</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">
                                    <a class="pure-button">
                                        <img src="resources/icons/complete_tsk.gif" 
                                             alt="Mark As Completed" title="Mark As Completed"/>
                                    </a>
                                </li>
                            </ul> 
                        </div>
                    </td>
                </tr>
            </tbody>
        </table>
        <hr/>
        <div class="rTableFoot">There are 1 task(s) with alerts today.</div>     
    </body>
</html>

The result is similar:

File:TodoKO-Fig5.png

We can similarly create the edit.html page. Simply copy paste index.html and rename to edit.html or create a new HTML page (New -> Other -> HTML file).

<h2>Create/Edit Task</h2>
<form >
    <label for="description">Description:</label><input id="description" type="text"/><br/>
    <label for="priority">Priority:</label><input id="priority" type="number" min="0" max="10"/><br/>
    <label for="dueDate">Due Date:</label><input id="dueDate" type="text"/><br/>
    <input type="checkbox"/><label>Show alert:</label>
    <input id="daysBefore" type="number" min="0" max="365"/>
    <label for="daysBefore"> days before</label><hr>
    <label for="obs">Obs:</label><br/><textarea id="obs" rows="4" cols="50"></textarea><hr>
    <input id="completed" type="checkbox"/><label for="completed">Completed Task</label>
    <button type="submit">Save</button><button type="reset">Clear</button>
</form>

This is how the edit.html will look like in your browser:

File:TodoKO-Fig6.png

Or, if you use Pure CSS:

<!DOCTYPE html>
<html>
    <head>
        <title>TodoDS Create/Edit</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <link href="resources/css/pure-release-0.6.2/pure-min.css" rel="stylesheet"/>
    </head>
    <body>
        <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" required/>
                </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" required/>
                </div>
                <div class="pure-control-group">
                    <label for="dueDate">Due Date:</label>
                    <input id="dueDate" class="pure-input-1-2" type="date" required/>
                </div>
                <div class="pure-control-group">
                    <label for="alert" class="pure-checkbox">
                        <input id="alert" type="checkbox"/>Show alert:
                    </label>
                    <input class="pure-input-1-3" type="number" min="0" max="365"/>
                    <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"></textarea>
                </div>
                <div class="pure-control-group">
                    <label for="completed">
                        <input id="completed" type="checkbox"/>Completed Task
                    </label>
                </div>                
                <div class="pure-controls">
                    <button class="pure-button pure-button-primary" type="submit">Save</button>
                    <button class="pure-button" type="reset">Clear</button>
                    <button class="pure-button" type="submit">Cancel</button>
                </div>            
            </fieldset>
        </form>
    </body>
</html>

the output is much nicer:

File:TodoKO-Fig7.png

Notice that not all browers support type="date". As of this writing, Opera and Chrome seem to support it, but Firefox and Safari, not. (What about IE?). So it is safer to convert it to a pure text field:

<input id="dueDate" class="pure-input-1-2" type="text" placeholder="Due Date" required/>

Another thing that will come more apparent in the following is the toggle buttons or checkboxes. Three of the buttons in index.html need to be transformed to toggle buttons:

  • Show Completed Tasks
  • Sort By Priority
  • Mark As Completed

There are a number of resources in the Internet that offer solutions for checkboxes with images. E.g. edit index.css and add the following:

input[type=checkbox].showCompletedCheckbox:before {
    content:""; 
    display:inline-block; 
    width:16px;
    height:16px;  
    background-size: 100%;
    padding: 0 0 0 0px;
    background-color: lightgrey;
    background-image: url('../icons/tasks_tsk.gif') ;
    zoom: 1.5;
}

input[type=checkbox].showCompletedCheckbox:checked:before {
    display:inline-block;
    padding: 0 0 0 0px;
    content:"";     
    zoom: 1.5;
    border: 0.0625em solid rgb(192,192,192);
    background-color: lightgrey;
    background-image:url('../icons/tasks_tsk.gif');
}

input[type=checkbox].sortByCheckbox:before {
    content:""; 
    display:inline-block; 
    width:16px;
    height:16px;  
    background-size: 100%;
    padding: 0 0 0 0px;
    background-color: lightgrey;
    background-image: url('../icons/next_nav.gif');
    zoom: 1.5;
}

input[type=checkbox].sortByCheckbox:checked:before {
    display:none;
    content:""; 
    display:inline-block;     
    border: 0.0625em solid rgb(192,192,192);
    background-color: lightgrey;
    background-image:url('../icons/next_nav.gif');
}

index.html needs to be modified like so:

...
<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">
    <input id="tglMarkAsCompleted" type="checkbox" 
           name ="tglMarkAsCompleted" title="Mark As Completed..."/>
</li>
...

File:TodoKO-Fig8.png

The only thing that is missing is the link between the two pages. Update index.html's Add Task and Edit Task buttons by setting the link to <a class="pure-button" href="edit.html"> and update <button type="submit" formaction="index.html">Save</button> and <button class="pure-button" type="submit" formaction="index.html">Cancel</button> in edit.html to return to index.html when you click on the Save or Cancel buttons.

The prototype is now complete but we need to add some bindings to it...

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). As already mentioned, KO uses the Model-View-ViewModel (MVVM) design pattern to separate the view and the viewmodel. Knockout supports declarative bindings, which allow 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 the original Todo application which correspond to index.html and edit.html respectively in our TodoKO application) will receive and return either Task objects or collections of Task objects.

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

2. 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"> 
    <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>
</head>

Let's create our ViewModel in JavaScript:

<script type="text/javascript">
    var taskViewModel = {
        id: "1",
        description: "Finish TodoDS article!",
        priority: 5,
        dueDate: "2017-03-10",
        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. We shall introduce the actual model in Step 3.

The above JavaScript code creates a Task with id = 1, and the ko.applyBindings() method tells Knockout.js to use the object as the ViewModel for the page.

Next, bind HTML elements to the taskViewModel object. Knockout.js uses a special data-bind attribute to bind HTML elements to the ViewModel:

<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 (if you used the pure css):

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

It's worth pointing out that Knockout.js manages cross-browser issues behind the scenes. For IE, it uses the innerText property, and for Firefox and related browsers it uses textContent. The text binding will also escape HTML entities, so it can be used to safely display user-generated content.

But since our model consists of two tasks:

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

and we can update our view like so:

<!-- ko foreach: tasks -->
<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> 
    <div class="rTableCell" >
        <button>
            <img src="resources/icons/configs.gif" 
                 alt="Edit Task..." title="Edit Task..."/>
        </button>
        <button>
            <img src="resources/icons/delete_edit.gif" 
                 alt="Remove Task..." title="Remove Task..."/>
        </button>
        <input id="tglMarkAsCompleted" type="checkbox" 
               name ="tglMarkAsCompleted" title="Mark As Completed..."/>
    </div>
</div> 
<!-- /ko -->

or

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

The containerless knockout syntax <!-- ko foreach: tasks -->...<!-- /ko --> or the data-bind="foreach: tasks" 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.

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.

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.

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!", 5, "2017-03-10", true, 2, "", false),
        new Task(2, "Book conference room", 10, "2017-04-01", false, 2, "", false) 
    ]);
}
ko.applyBindings(new TasksViewModel());

Observable arrays let Knockout.js track lists of items. Knockout.js automatically updates any associated HTML elements whenever items are added to or removed from an observable array. 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.

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 following properties are available in View:

  • $root: refers to the top-level ViewModel (in our case the TasksViewModel
  • $data: refers to the ViewModel object of the current context (can be omitted)
  • $parent: refers to the parent ViewModel object (useful for nested loops)
  • $index: contains the current item’s index in the array

In our case, since we are inside the foreach loop (i.e. inside the Task ViewModel), we need to access the method remove() which is defined in the parent ViewModel, hence either $root or $parent would help KO to locate this method.

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;
    self.tasks = ko.observableArray([
        new Task(1, "Finish TodoDS article!", 5, "2017-03-10", true, 2, "", false),
        new Task(2, "Book conference room", 10, "2017-04-01", false, 2, "", false)
    ]);
    self.removeTask = function (task) {
        self.tasks.remove(task);
    };

Now, clicking on the Remove button on the UI will work as expected removing the task from the TasksViewModel.

To display the number of 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);

Remember that to access an observable you must call it without any arguments (e.g. self.tasks()[i]) which can be somewhat counter-intuitive for beginners to Knockout.js.

Computed observables let you create properties that are dynamically generated. Computed properties are properties derived from other properties. This means you can combine several normal observables into a single property, and Knockout.js will still keep the view up-to-date whenever any of the underlying values change. This also means that whenever one of the properties it is depending on changes, the computed property changes as well. See again how we defined numberOfTasksWithAlert as a computed property above.

The functionality for the Mark As Completed checkbox on the right toolbar of each task is easy to implement, too (depends on the observable property completed of the Task ViewModel):

<!-- ko foreach: tasks -->
<div class="rTableRow" > 
    <div class="rTableCell" data-bind="text: priority, 
         style: { 'background-color': completed() ? 'green' : 'white' }"></div> 
    <div class="rTableCell" data-bind="text: description, 
         style: { 'background-color': completed() ? 'green' : 'white' }"></div> 
    <div class="rTableCell" data-bind="text: alert, 
         style: { 'background-color': completed() ? 'green' : 'white' }"></div> 
    <div class="rTableCell" data-bind="text: dueDate, 
         style: { 'background-color': completed() ? 'green' : 'white' }"></div> 
    <div class="rTableCell" >
        <button>
            <img src="resources/icons/configs.gif" 
                 alt="Edit Task..." title="Edit Task..."/>
        </button>
        <button data-bind="click: $root.removeTask">
            <img src="resources/icons/delete_edit.gif" 
                 alt="Remove Task..." title="Remove Task..."/>
        </button>
        <input id="tglMarkAsCompletedcli" type="checkbox" 
               name ="tglMarkAsCompleted" title="Mark As Completed..."
               checked="completed" data-bind="checked: completed"/>
    </div>
</div> 
<!-- /ko -->

or

  <!-- ko foreach: tasks -->
   <tr>
       <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> 
       <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" data-bind="click: $root.edit"
                                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..."
                                data-bind="click: $root.removeTask"/>
                       </a>
                   </li>
                   <li class="pure-menu-item">
                       <input id="tglMarkAsCompleted" type="checkbox" 
                              name ="tglMarkAsCompleted" title="Mark As Completed..."
                              checked="completed" data-bind="checked: completed" />
                   </li>
               </ul>
           </div>
       </td>
   </tr> 
   <!-- /ko -->
</tbody>

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

  • Control-flow bindings: if, ifnot, foreach, with
  • Appearance bindings: text, html, visible, css, style, attr
  • Interactive bindings: click, value, event, submit, enable, disable, , checked, options, selectedOptions, hasfocus

The interested reader may check the above link for more information on the above or check the references at the end of this article.

An example use of the if control-flow binding could be:

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

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

function TasksViewModel(sortByPriority, showCompleted) {
    self = this;
    self.tasks = ko.observableArray([
        new Task(1, "Finish TodoDS article!", 5, "2017-03-10", true, 2, "", false),
        new Task(2, "Book conference room", 10, "2017-04-01", false, 2, "", false)
    ]);
    self.sortByPriority = ko.observable(sortByPriority);
    self.showCompleted = ko.observable(showCompleted); 
    ...
    // tasks sorted and filtered
    self.sortedAndFilteredTasks = ko.computed(function () {
        var filteredTasks = ko.observableArray([]);
        if (self.showCompleted()) {
            for (var i = 0; i < self.tasks().length; i++) {
                if (self.tasks()[i].completed()) {
                    filteredTasks.push(self.tasks()[i]);
                }
            }
        } else {
            filteredTasks(self.tasks());
        }
        if (self.sortByPriority()) {
            filteredTasks.sort(function (t1, t2) {
                return (t1.priority() - t2.priority());
            });
        } else {
            filteredTasks.sort(function (t1, t2) {
                var dt1 = self.convertToDate(t1.dueDate());
                var dt2 = self.convertToDate(t2.dueDate());
                if (dt1 === dt2) {
                    return 0;
                } else if (dt1 > dt2) {
                    return 1;
                } else {
                    return -1;
                }
            });
        }
        return filteredTasks();
    }, self); 
};

It uses an utility function which we define outside any object declaration:

// convert string to date
function convertToDate(strDate) {
    if (strDate == null) return null;
    var pattern = /^(\d{4})\-(\d{1,2})\-(\d{1,2})$/; // YYYY-MMM-dd
    var arrayDate = strDate.match(pattern);
    return new Date(arrayDate[1], arrayDate[2] - 1, arrayDate[3]);
};

The initial check tests if strDate is undefined or not.

Two new observable properties sortByPriority, showCompleted have been added to the TasksViewModel. These are bound to the corresponding check boxes in the UI:

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

or

<li class="pure-menu-item pure-menu-selected">
    <input id="tglShowCompleted" type="checkbox" title="Show Completed..."
           name ="tglShowCompleted" class="showCompletedCheckbox"
           checked="showCompleted" data-bind="checked: showCompleted" />
    <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"
           checked="sortByPriority" data-bind="checked: sortByPriority"/>
    <label for="tglSortByPriority"></label> 
</li>

if you use pure css.

The resulted filteredTasks observable array contains only the tasks that satisfy the criteria of being completed and/or sorted by priority or due date. The helper function convertToDate() converts a Date represented by a String to a Date.

And in order for the trick to work you need to do one more modification to your UI:

<!-- ko foreach: sortedAndFilteredTasks() -->

or

<tbody data-bind="foreach: sortedAndFilteredTasks()">

Let's implement the Show Alerts... button. First, 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 update index.html adding a new <div> on top and binding the button to the expiredTasks() function:

<h2>Tasks</h2>
<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>

or

<button>
     <img src="resources/icons/warning.gif" data-bind="click: expiredTasks"
          alt="Show Alerts..." title="Show Alerts..."/>
</button>

From the above bindings you can guess how we need to update our TasksViewModel. Add two more properties dialog, message to our view model along with the respective functions:

function TasksViewModel(sortByPriority, showCompleted, dialog, message) {
    self = this;
    self.tasks = ko.observableArray([
        new Task(1, "Finish TodoDS article!", 5, "2017-03-10", true, 2, "", true),
        new Task(2, "Book conference room", 10, "2017-04-01", false, 2, "", false)
    ]);
    self.sortByPriority = ko.observable(sortByPriority);
    self.showCompleted = ko.observable(showCompleted); 
    self.message = ko.observable(message);
    self.dialog = ko.observable(dialog);   
    ...
    // hide alert dialog
    self.hideDialog = function () {
        self.dialog(false);
    };
    // display alerts of expired tasks
    self.expiredTasks = function () {
        var tasksWithAlert = self.listTasksWithAlert();
        for (var i = 0; i < tasksWithAlert.length; i++) {
          if (tasksWithAlert[i].isLate()) { 
            self.message('Task:' + tasksWithAlert[i].description() 
                    + ' expired on ' + tasksWithAlert[i].dueDate());
            self.dialog(true);
          }
        }
    };
};
ko.applyBindings(new TasksViewModel(false, false, false, ""));

where isLate() needs to be defined inside Task:

// isLate
this.isLate = function () {
    if (this.dueDate() === null || this.dueDate() === '') {
        return false;
    }
    var dateDue = convertToDate(this.dueDate());
    var now = new Date();
    return (dateDue === null) ? false : dateDue - now < 0;                
};            
// hasAlert
this.hasAlert = function () {
    if (this.dueDate() === null || this.dueDate() === '') {
        return false;
    }
    var dateDue = convertToDate(this.dueDate());
    if (!this.alert() || dateDue == null) {
        return false;
    } else {
        var now = new Date();
        var timeDiff = dateDue.getTime() - now.getTime();
        var diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24));                     
        return diffDays <= this.daysBefore();
    }                
};  

and you may replace if (self.tasks()[i].hasAlert()) { with if (self.tasks()[i].hasAlert()) { in listTasksWithAlert() function.

Here is the result:

File:TodoKO-Fig9.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 KO typically are single pages, and the scope of a ViewModel 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:

<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>
<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:

function TasksViewModel(sortByPriority, showCompleted, dialog, message, selected, edited) {
     self = this;
     self.tasks = ko.observableArray([
             new Task(1, "Finish TodoDS article!", 5, "2017-03-10", true, 2, "", true),
             new Task(2, "Book conference room", 10, "2017-04-01", false, 2, "", false)
     ]);
     self.sortByPriority = ko.observable(sortByPriority);
     self.showCompleted = ko.observable(showCompleted);
     self.message = ko.observable(message);
     self.dialog = ko.observable(dialog);
     self.edited = ko.observable(edited);
     self.selected = ko.observable(selected);
     ...
};
ko.applyBindings(new TasksViewModel(false, false, false, "", null, null));

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

    // add task
    self.addNew = function () {
        self.selected(null);
        self.edited(new Task());
    };
    // edit task
    self.edit = function (task) {
        self.selected(task);
        self.edited(new Task(task.id(), task.description(), task.priority(), task.dueDate(), 
                             task.alert(), task.daysBefore(), task.obs(), task.completed()));
    };

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

or

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

We are still missing the bindings of our edit form:

// commit button
self.commit = function() {
    var task = self.edited();
    if (!task) return;
    var selectedTask = self.selected();
    if (selectedTask != null) {
        self.tasks()[self.tasks().indexOf(selectedTask)] = task;
    } else {
        self.tasks().push(task);
    }
     self.edited(null);
};
// cancel button
self.cancel = function() {
    self.selected(null);
    self.edited(null);
}; 

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

You now have an almost 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:

self.validate = function (task) {
    var 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:

// validate task
this.validate = ko.computed(function (description, priority, dueDate, daysBefore) {
    var 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 {
            var dateDue = convertToDate(dueDate);
            if (dateDue == null) {
                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;
}, this);   

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.

References

  1. Lozano F. (2006), "A complete App using NetBeans 5", NetBeans Magazine, Issue 1, May.
  2. Knockout.js.
  3. 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