Top

Todo MVC

In this guide you will learn how to develop a tipical Todo app using aria templates, taking advantage of his strong MVC orientation.

By referring to this guide, you will be able to:

  • Put in practice the MVC pattern with aria templates.
  • Use the aria templates DOM events (on keydown, on click, on blur).
  • Use sections with repeater.
  • Add and use listeners.
  • Play with the refresh mechanism.

Tutorial

Step 1

Create a file using your favourite editor and call it index.html. This will be the bootstrap for your app.

Save it inside the root of your project (e.g. /guides/todo/).

Step 2

Inside index.html you will have to load the aria templates framework, define the container div and load the template that you will create.

To load the framework:

<script type="text/javascript" src="/aria/ariatemplates-1.2.0.js"></script>
<script type="text/javascript" src="/aria/css/atskin-1.2.0.js"></script>

To define the div container:

<div id="output"></div>
To load the template:
<script type="text/javascript">
Aria.loadTemplate({
  classpath:'ariadoc.guides.todo.view.Todo',
  div:"output",
  moduleCtrl: {
    classpath : "ariadoc.guides.todo.TodoCtrl"
  }
});
</script>
Inside the bootstrap you are defining also the module controller.

Step 3

Create a file and call it Todo.tpl. This is the template that will be used to display the Todo user interface.

Save it inside the view folder inside your project (/guides/todo/view/).

Step 4

Define the classpath of your template and that it has a script:
{Template {
   $classpath: "ariadoc.guides.todo.view.Todo",
   $hasScript: true
}}
Define the html structure of the app with the call to the macro to manage the mark all tasks functionality, the section with a repeater to list the tasks and the call to the macro to manage the number of tasks left and the clear all completed tasks:
{macro main()}
   <div>
      <h1>Todos</h1>
     
      {call listControl("markall")/}
      {repeater {
         id: "tasklist",
         content: data.tasks,
         type: "ul",
         childSections: {
            id: "task",
            macro: "taskline",
            type: "li",
            bindRefreshTo: function(el) { return [ {inside:el.item} ] }
         }
      }/}
      {call listControl("status")/}
   </div>
{/macro}

Note: To get more info abour repeater take a look at this page.

Define the macro to manage the mark all tasks feature, the number of tasks left and the clear all completed tasks using a section with the refresh bound to the tasks inside the data model:
/**
 * listControl
 * --
 * outputs list controls
 * if type=markall the Mark All As bar is used
 * if type=status  the status bar is used
 */

 {macro listControl(type)}
   {section {
      id: type,
      type: "div",
      attributes: {
         classList: ["todo-"+type]
      },
         bindRefreshTo: [ {to:"tasks", inside:data} ]
      }}
         {if (data.tasks.length>0)}
           {var t=this.moduleCtrl.tasksLeft()/}
              {if (type=="markall")}
                 <label>
                    Mark all as completed
                 </label>
              {elseif (type=="status")/}
                 <strong>${t}</strong> ${t>1 ? 'tasks':'task'} left.
                 {var c=data.tasks.length-t/}
                 {if (c>0)}
                   <a class="todo-clear">
                      Clear ${c} completed ${c>1 ? 'tasks':'task'}
                   </a>
                 {/if}
              {/if}
         {/if}
   {/section}
{/macro}

Note: To get more info about sections take a look at this page.

Define the macro to manage the change of a task, displaying an input text to allow the user to update the task content and the icon to remove a task from the list:
 /**
  * taskLine
  * --
  * outputs a line in the list of tasks
  */

  {macro taskline(el)}
    {if (el.item.edit)}
       <input type="text" class="editBox" {id "editBox"/} {on blur {fn:"leaveEdit", scope:this, args:el}/} {on keydown {fn:"editTask", scope:this, args:el}/} />
    {else/}
      <span>
         <input type="checkbox" {if (el.item.done)}checked{/if} />
         ${el.item.desc}
         <a class="remove" title="Remove this task"></a>
      </span>
    {/if}
{/macro}

Step 5

Create a file and call it TodoScript.js.

Save it inside the view folder inside your project (/guides/todo/view/).

Step 6

Define the classpath and the function scripts to manage the DOM events.
Basically:
  • addTask: function(e) calls this.moduleCtrl.addTask(v) inside the module controller;
  • clearTask: function(e) calls this.moduleCtrl.deleteTask(v) inside the module controller;
  • markAll: function(e) calls this.moduleCtrl.flagTask(i, checked) inside the module controller for each task;

The clickTask() function marks a task or allows the user to change the text of the task clicked:
clickTask: function(e, el) {
  if (e.target.tagName.toUpperCase()=="INPUT") {
     this.moduleCtrl.flagTask(el.index, !el.item.done);
  } else {
       aria.utils.Json.inject({edit:true}, el.item);
       // the following is a tiny hack to put focus on the field and cursor at the end
       var tf = this.$getElementById("editBox");
       tf.focus();
       tf.setValue(el.item.desc);
  }
}
The leaveEdit() function calls this.moduleCtrl.updateTask() function inside module controller to stop and resume the refresh to avoid extra refresh when the value changes:
leaveEdit: function(e, el) {
    // We use the RefreshManager stop() and resume() to avoid an extra refresh when the value changes
    aria.templates.RefreshManager.stop();
    this.moduleCtrl.updateTask(this.$getElementById("editBox").getValue(), el.index);
    aria.utils.Json.deleteKey(el.item, "edit");
    aria.templates.RefreshManager.resume();
}

Note: To get more info about the refresh manager take a look at this page.

Step 7

Create a file and call it ITodoCtrl.js.

Save it inside the root folder inside your project (/guides/todo/).

Step 8

Define the classpath, which class the controller interface extends, that is always aria.templates.IModuleCtrl and write the signature of all the methods of your controller:
Aria.interfaceDefinition({
  $classpath: "ariadoc.guides.todo.ITodoCtrl",
  $extends: "aria.templates.IModuleCtrl",
  $interface: {
     tasksLeft:  function() {},
     addTask:    function(desc) {},
     updateTask: function(desc, idx) {},
     deleteTask: function(idx) {},
     flagTask:   function(idx, done) {}
  }
});

Step 9

Create a file and call it TodoCtrl.js.

Save it inside the root folder inside your project (/guides/todo/).

Step 10

Define the classpath, which class the controller extends, that is always aria.templates.ModuleCtrl, which controller interface implements, in our case ariadoc.guides.todo.ITodoCtrl (that's the controller interface defined in the previous step) and define the constructor:
Aria.classDefinition({
  $classpath: "ariadoc.guides.todo.TodoCtrl",
  $extends: "aria.templates.ModuleCtrl",
  $implements: ["ariadoc.guides.todo.ITodoCtrl"],

  $constructor: function () {
     this.$ModuleCtrl.constructor.call(this);
     this.setData({
          tasks: (localStorage.tasklist ? JSON.parse(localStorage.tasklist) : [])
     });
     this.json.addListener(this._data, "tasks", {fn:this.__saveTasks, scope:this}, false, true)
  }
Inside the constructor we check the localStorage to load an existing tasklist and we add a listener to the data model to automathically save, inside the localStorage, the task that the user add.

Note: To read more about listeners take a look at this page.


Than the other functions:

  • tasksLeft: function() that just count the number of task left;
  • addTask: function(desc) that calls this.json.add to add a task to the todo list;
  • updateTask: function(desc, idx) that calls this.json.inject to update a task;
  • deleteTask: function(idx) that calls this.json.removeAt to remove a task from the todo list;
  • flagTask: function(idx, done) that calls this.json.setValue to mark a task as completed;
  • __saveTasks: function() that saves the tasklisk inside the localStorage;

Step 11

Create a file and call it style.css.

Save it inside the root folder inside your project (/guides/todo/).

Step 12

Give some style to your todo app.

Step 13

That's it! Open the index.html with your favourite browser (e.g. Chrome) and enjoy.