Maintaining and Organizing UI Dependencies with Knockout.JS: Part 2

Garry Smith's Headshot
Garry Smith

In Part 1 of Maintaining and Organizing UI Dependencies with Knockout.JS, we discussed the three types of observables that Knockout provides: observables, observable arrays and computed observables. We also built a small demo app and explained some of the library's features such as templating, using different bindings like "foreach" and "visible," adding input to an array and creating models.

The infamous JavaScript "this" conundrum

If you are experienced with JavaScript, you know about the ongoing war between scope, context and dealing with the this keyword. Previously, we used this on properties this.studentsName = ko.observable('') to refer to the ViewModel object itself. That was all well and good because we didn't have to worry about tracking what this meant in a nested function within studentsName.

The problem arises when our ViewModel starts becoming more complex. Say, for example, our computed observable function this.challengeCompletedCount uses a nested internal function. Internal functions create a new, separate context with a new this variable. The new this variable is completely different from the one outside of our internal function. If we were to leave the code like so:

function courseViewModel(notes, challenges) {
  this.studentsName = ko.observable('Garry Smith');
  //... etc.
  this.challengeCompletedCount = ko.computed(function () {
    return ((ko.utils.arrayFilter(this.challenges(), function (challenge) {
      return challenge.completed();
    }).length / this.challenges().length) * 100).toFixed(2) + ' %';
  });
}

It would not work, because this.challenges() is looking for a .challenges method belonging to a new this variable created by the inner function. Put another way, it is trying to find an observable array within the context of the computed's function scope, which doesn't exist. This is why we used the .bind(this) on this.challengeCompletedCount in the Part 1 code so that we could bind the ViewModel's value of this into the computed's function. You may feel like this is weird. It is, but stay calm and code on.

The popular convention of working with this is to save its value into a variable at the ViewModel's level so that you don't have to worry about it being redefined.

function courseViewModel(notes, challenges) {
  var self = this;

  self.studentsName = ko.observable('Garry Smith');
  //... etc.
  self.challengeCompletedCount = ko.computed(function () {
    return ((ko.utils.arrayFilter(self.challenges(), function (challenge) {
        return challenge.completed();
    }).length / self.challenges().length) * 100).toFixed(0) + ' %';
  });
}

Knockout.JS's Style and CSS binding.

Let's go into the more enjoyable and less quirky part of JavaScript. As mentioned in the previous post, Knockout provides many useful binding attributes. The Style and CSS bindings allow you to visually represent changes in the data to the user. Maybe you want to apply different CSS classes to the same element under different situations, perhaps changing the background color of a validation box to red when a user inputs an invalid password. In our case, to show a validation message when adding a "Note."

self.addNote = function () {
    var note = self.noteToAdd();
    if (note) {
        self.notes.push(new Note(note));
        self.noteToAdd("");
        self.validation("success", true, "Wicked good job!");
    }
    else {
         self.validation("fail", true, "Oh shux! There was no note text.");   
    }
};

self.validationStatus = ko.observable();                  
self.displayValidation = ko.observable();
self.validationText = ko.observable();

self.validation = function (status, displayValidation, text) {
    self.validationStatus(status);
    self.displayValidation(displayValidation);
    self.validationText(text);
};

This checks to see if the user inputted a value into the note box and displays a message accordingly. The code below sets the font color of the validation text based on the value of validationStatus(), green for successful and red for failure. This is a simple implementation, but you can use any CSS properties you want in the style binding.

<div data-bind="text: validationText, 
  style: { color: validationStatus() === 'success' ? 'green' : 'red' },
  visible: displayValidation">
</div>

Alternatively, you can bind CSS classes to an element using the css: binding. Below, we are applying the Bootstrap CSS class alert-success if the validation status is success, and alert-danger if not.

<div class="alert" data-bind="text: validationText, 
  css: { 
        'alert-success': validationStatus() === 'success',
        'alert-danger': !(validationStatus() === 'success')
       },
  visible: displayValidation">
</div>

Depending on where you feel comfortable putting code logic, you can also bind the observable to the CSS binding and put the logic in the JavaScript so that your HTML is simply css: validationStatus

Rejoice! Asynchronous Module Definition is here!

So far, our little app has been easy to maintain with only one ViewModel and a couple of models, but projects inevitably get larger and more complex. Code becomes quickly cumbersome and hard to maintain, not to mention prone to global variable conflictons. To truly leverage the perks of using an MVVM (model-view-viewmodel) or MVC pattern, you must separate your code into logical modules and decouple the layers. It may take some additional time at the onset of a project, but it will save you and your team headaches (and quite possibly an [Anchorman](http://www.youtube.com/watch?v=Wu7xiJHD6c)-_style street brawl) in the future. So unless tridents and hand grenades are your thing, I suggest using RequireJS to modularize your code.

Instead of having all of your Knockout code in your HTML file, you can break it down into separate JavaScript files, under their own respective folder paths such as ..\Models\ChallengeModel.js and ..\ViewModels\CourseViewModel.js and then tie their dependencies together using RequireJS's define() syntax. RequireJS will do all the heavy lifting, loading each file and their dependencies in correct order.

define(['knockout-2.3.0', 'challengeModel'], function(ko, challengeModel) {
  function courseViewModel(notes, challenges) {
    var self = this;

    self.studentsName = ko.observable('Garry Smith');
    self.studentsAge = ko.observable(25);
    //etc.
  }
  return courseViewModel;
});

We are wrapping the ViewModel code in a define() which tells RequireJS what dependencies to load for this module, in this case, the Knockout library and our challenge model.

For more information on how to set up RequireJS in your project, please visit their documentation.

That's it for now folks! We've covered some of the key aspects of KnockoutJS, including observable types, templating, built-in bindings and helper functions. We've also dabbled in project architecture, separating models and viewmodels, and tying together file dependencies with RequireJS, making code easier to maintain.

The code for this post can be viewed and run on jsFiddle, with exception to the RequireJS code.

Recent Comments

comments powered by Disqus