James Thomas

Notes on JavaScript

Creating Todo MVC in Dojo - Part 3: Controllers

In the final article of this series, we’ll be looking at creating an MVC Controller for our sample todo application.

We’ve already shown how to define our application model, creating a domain-specific todo model backed by localStorage, along with our view template, using widget templating to render our tasks into the page.

The controller translates user input into operations on the model.

For our application, we need to handle the user actions to allow adding, removing and completing tasks. We already have a binding between a task’s completed state and our View, using the todo.form.CheckBox, allowing changes to flow back to the model without explicitly needing logic in the controller.

Let’s look more closely at the remaining tasks…

Adding Tasks

The View template, discussed in the second article, renders the HTML elements needed to allow the user to any new tasks. Once a user has finished typing in their new task, signalled by pressing the enter key, we need to retrieve their text and add it to the model.

Using Dojo’s declarative programming model, the View template includes the custom element attribute needed to connect the “onkeypress” DOM event to an event handler within our controller. When our widget is rendered in the page, by the Dojo parser, those connections are created automatically.

Declarative event handlingSource Link
1
2
<input id="new-todo" data-dojo-attach-event="onkeypress:onKeyPress"
 placeholder="What needs to be done?" type="text" autofocus>

Inside our controller, each time an event is fired, the following function is executed:

Controller event handlerSource Link
1
2
3
4
5
6
7
onKeyPress: function (event) {
    if (event.keyCode !== keys.ENTER) return;

    this.addToModel(event.target.value, false);
    event.target.value = "";
  dojo_event.stop(event);
}

Unless the user has pressed the enter key, we ignore the normal user input event. Once this happens, we extract the new task text from the event argument and call the following convenience function to create the new task in the model.

New model taskSource Link
1
2
3
4
5
6
7
addToModel: function (content, isDone) {
    var insert = mvc.newStatefulModel({
        data: {todo_text: content, isDone: isDone}
    });

    this.model.todos.add(this.model.todos.length, insert);
}

This function creates a new Model node, containing the task text and its completed state, adding the result to the list of tasks. When inserting new entries into the DojoX MVC Model array, the insertion position must be specified explicitly. By using the current length of the list as the position, we always add new items at the end.

Once the model has been modified, the View will automatically update to display the new item. We don’t need to manually update the rendered HTML template or even trigger re-loading of the View. By using widgets from the DojoX MVC package, changes to the Model are always reflected in our View in real-time.

Removing Tasks

Removing tasks begins with the user clicking the destroy icon, displayed on the right-hand side of each task. Once this happens, the Controller needs to trap the event, figure out which task to remove and update the Model. As the tasks’ list can be updated during the application, having an individual event connection for each task would require handling the setting up and tearing down every time the list changed.

Instead, we can use “Event Delegation”, introduced by the new Dojo event module, dojo/on, to listen for any remove events with a single connection.

Once the widget has been rendered, signalled by the “postCreate” function being called, we start listening for all click events on the destroy icons. Any events captured are passed through to our event hander, “onRemove”, to delete the associated task from the Model.

Event DelegationSource Link
1
2
3
4
5
6
7
8
postCreate: function () {
    on(this.domNode, ".destroy:click", lang.hitch(this, "onRemove"));
    this.onItemStatusUpdate();
},

onRemove: function (event) {
    this.model.todos.remove(domAttr.get(event.target, "data-model-id"));
}

With one event handler for all the remove events, the Controller won’t directly know which task within the Model the user has chosen to remove. To overcome this, the repeating View template for the task uses a HTML5 data attribute to store the unique task index on the rendered DOM element for the remove icon.

During rendering of a DojoX MVC Repeat widget, the “index” attribute on the instance refers to the current position within the bound list. This index value can then easily be retrieved from the event generated and used to remove the correct task from the Model.

1
2
<button class="destroy" data-model-id="#{this.index}">
</button>

Once again, when the Model is changed, the View automatically updates. There’s no action needed from the Controller in modifying the rendering HTML template.

Clearing Completed Tasks

Once a user has completed a series of tasks, they will eventually want to remove them. Rather than having to remove each task individually, the application provides the ability to clear all completed tasks from the list.

Again, we’ve used declarative programming in the View template to connect our event hander, removeCompletedItems, to the DOM event triggered when the user clicks the “Clear Completed” button.

1
2
<button id="clear-completed" data-dojo-attach-event="onclick:removeCompletedItems">
</button>

When our handler is fired, we need to iterate over the existing tasks’ list, removing any with the correct completed state. Removing items from the Model will left-shift the remaining items, so we need to take care to iterate correctly over the remaining items.

Clearing Completed ItemsSource Link
1
2
3
4
5
6
7
8
9
10
11
12
removeCompletedItems: function () {
    var len = this.model.todos.length, idx = 0;

    while (idx < len) {
        if (this.model.todos[idx].isDone.value) {
            this.model.todos.remove(idx);
            len--;
            continue;
        }
        idx++;
    }
}

When the event handler has finished executing, the View will be updated to clear out those completed tasks.

Conclusion

In the final part of this series, we’ve looked at how to define an MVC Controller, responsible for mediating between user actions and model operations. Using declarative Dojo programming in our View template, we set up bindings between DOM events and event handlers in our Controller.

When user actions triggered those events, our handlers were responsible for adding and removing todo tasks from the MVC Model class, StatefulModel, we’ve been using to store our application data. These changes then flowed back to the View, which automatically re-renders when it detects an updated Model.

Dojo’s new MVC package, dojox.mvc, offers great capabilities for building dynamic JavaScript applications using the MVC programming pattern. Although it’s still maturing, hopefully this series has been able to demonstrate that for most applications it’s more than capable of providing the features developers expect in a modern JavaScript MVC library.

If you have any further questions, feel free to leave comments below, send me an email or a tweet. The source code for the application is available on Github, allowing you to run the examples above and compare it against other frameworks.

What’s Next?

This series of articles was based upon the version of DojoX MVC present in the 1.7.0 release of The Dojo Toolkit. My experiences, good and bad, building this application were fed back into the community to help improve the package in the future. With the upcoming 1.8 release of The Dojo Toolkit, there has been some major improvements to the MVC package, resolving many of the issues I raised.

When that version of the toolkit is available, I’ll re-visit the application and show how those changes would make writing this application even simpler.

Comments