Knock Me Out

Thoughts, ideas, and discussion about Knockout.js

Utility Functions in KnockoutJS

| Comments

When working with KnockoutJS, there seem to be many common operations that you need to perform on your view model objects. Internally, Knockout has a number of utility functions used by the library itself, but many of these functions might actually be handy for anyone using the library. I wanted to build a sample that highlights a few of my favorite utilities.

Handling data from the server

One of the first tasks that you typically encounter is converting data from the server to a suitable format for use in Knockout. Suppose, we are provided a JSON string from the server that was not automatically turned into a JavaScript object:

1
var JSONdataFromServer = '[{"name":"Peach","category":"Fruits","price":1},{"name":"Plum","category":"Fruits","price":0.75},{"name":"Donut","category":"Bread","price":1.5},{"name":"Milk","category":"Dairy","price":4.50}]';

Knockout has a utility function ko.utils.parseJson that will attempt to do a JSON.parse if it is available or fall back to evaluating it as a function string for older browsers. So, we can turn our JSON string into an object like:

1
var dataFromServer = ko.utils.parseJson(JSONdataFromServer);

Now we have a JavaScript object, but in order to be useful in Knockout, we might need to convert some properties to observables and possibly add some computed observables. One option for doing this is Knockout’s mapping plugin. By default, it will convert all arrays into observableArrays and all other properties into observables. It also has hooks to control creation in more advanced scenarios. However, for fairly simple scenarios, it is easy enough to do the mapping yourself like:

1
2
3
4
5
6
7
8
9
10
11
12
13
function Item(name, category, price) {
    this.name = ko.observable(name);
    this.category = ko.observable(category);
    this.price = ko.observable(price);
    this.priceWithTax = ko.dependentObservable(function() {
        return (this.price() * 1.05).toFixed(2);
    }, this);
}

//do some basic mapping (without mapping plugin)
var mappedData = ko.utils.arrayMap(dataFromServer, function(item) {
    return new Item(item.name, item.category, item.price);
});

So, we have a constructor function for an Item, which creates our observables and adds a computed observable for showing the price with tax. We generate a mapped array of objects by using ko.utils.arrayMap, which executes a function for each item in an array and pushes the result of the function to a new array that is returned.

Dealing with arrays in your view model

Now we have an array of items that is ready for use in Knockout. When dealing with an array though, there seem to be several common scenarios where utility functions come in handy.

Looping through an array

Suppose we want to create a computed observable at the root view model level to track the grand total for all items. We will need to sum the priceWithTax value for all items. We could easily write a for loop to do this (or if we are using jQuery, $.each), but Knockout provides a utility function ko.utils.arrayForEach for this purpose. We can use this function to get our total by doing something like:

1
2
3
4
5
6
7
8
9
10
viewModel.total = ko.computed(function() {
    var total = 0;
    ko.utils.arrayForEach(this.items(), function(item) {
        var value = parseFloat(item.priceWithTax());
        if (!isNaN(value)) {
            total += value;
        }
    });
    return total.toFixed(2);
}, viewModel);

Filtering an array

Next, say we want to allow a user to filter the list of items by name. We could create a computed observable that returns the matching subset of the original array of items. Knockout’s ko.utils.arrayFilter provides an implementation of this functionality that allows us to pass in an array and control which items are included based on the result of the function executed on each item. For example, suppose we bind a textbox to a filter observable and use it to get our filtered items:

1
2
3
4
5
6
7
8
9
10
11
//filter the items using the filter text
viewModel.filteredItems = ko.computed(function() {
    var filter = this.filter().toLowerCase();
    if (!filter) {
        return this.items();
    } else {
        return ko.utils.arrayFilter(this.items(), function(item) {
            return ko.utils.stringStartsWith(item.name().toLowerCase(), filter);
        });
    }
}, viewModel);

We pass our array of items into ko.utils.arrayFilter and return true only when the item’s name starts with the value of the filter observable (ko.utils.stringStartsWith provides an easy way to do this). Now we can bind our display to filteredItems and it will react to changes in the filter textbox. We would most likely want the display to update on each keystroke, so on our input field we can specify the binding like:

note: ko.utils.stringStartsWith is not exported in the minified KO file`. The code is simple enough though to replicate.

1
<p>Filter: <input data-bind="value: filter, valueUpdate: 'afterkeydown'" /></p>

Searching for an item in the array

Besides being able to filter the display, let’s say that we also want to be able to enter a search term and highlight the first matching entry by name. Knockout provides ko.utils.arrayFirst that will execute a function against each item in our array and return the first item where the function evaluates to true. Similar to the filteredItems computed observable, we can create one that returns the first match from our search field:

1
2
3
4
5
6
7
8
9
10
11
//identify the first matching item by name
viewModel.firstMatch = ko.computed(function() {
    var search = this.search().toLowerCase();
    if (!search) {
        return null;
    } else {
        return ko.utils.arrayFirst(this.filteredItems(), function(item) {
            return ko.utils.stringStartsWith(item.name().toLowerCase(), search);
        });
    }
}, viewModel);

Now we can use viewModel.firstMatch in our template to compare it against the item ($data) that we are sending through our template and style the matching row appropriately.

Flattening an array

Suppose we needed an array that contains all of the categories currently being used in our items. Knockout’s ko.utils.arrayMap that we used earlier is a nice way to take an array of objects and generate a flattened structure.

1
2
3
4
5
6
7
//get a list of used categories
viewModel.justCategories = ko.computed(function() {
    var categories = ko.utils.arrayMap(this.items(), function(item) {
        return item.category();
    });
    return categories.sort();
}, viewModel);

Our justCategories computed observable now contains an array of the used categories.

Getting just the unique values in an array

While we now have a list of categories in justCategories, what we might really want is a list of the unique categories represented in our items. Knockout’s ko.utils.arrayGetDistinctValues takes in an array and returns an array that contains only the unique values.

1
2
3
4
//get a unique list of used categories
viewModel.uniqueCategories = ko.dependentObservable(function() {
    return ko.utils.arrayGetDistinctValues(viewModel.justCategories()).sort();
}, viewModel);

Comparing two arrays

We have a list of the available categories and we have a list of the unique categories that are being used. Suppose that we want to provide a list of the categories that are missing from our data. Knockout’s ko.utils.compareArrays provides functionality to compare two arrays and indicate which items are different. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
//find any unused categories
viewModel.missingCategories = ko.dependentObservable(function() {
    //find out the categories that are missing from uniqueNames
    var differences = ko.utils.compareArrays(viewModel.categories, viewModel.uniqueCategories());
    //return a flat list of differences
    var results = [];
    ko.utils.arrayForEach(differences, function(difference) {
        if (difference.status === "deleted") {
            results.push(difference.value);
        }
    });
    return results;
}, viewModel)

The result of ko.utils.compareArrays is an array that contains items with a status property (added, deleted, or retained) and a value property holding the original item.

Sending data back to the server

At some point, most applications will need to post data back to the server to persist changes to storage. Our view model is likely not quite in a suitable format for use on the server.

Converting our view model using ko.toJS or ko.toJSON

I hesitate to call ko.toJS and ko.toJSON utilities, because they seem to be a necessity as soon as you need to package up some or all of your view model for transporting it back to the server. Usually a first attempt at doing this would involve calling something like JSON.stringify(viewModel). After seeing the result, you are immediately reminded that observables are actually functions and that JSON does not contain functions, so the observables are ignored by JSON serializers.

Luckily, Knockout includes these helper functions to facilitate transforming all of your observables and computed observables into normal properties on a JavaScript object.

ko.toJS – this function creates a copy of the object that you pass to it with all observables and computed observables converted into normal properties that are set to the current value.

ko.toJSON – this function first does ko.toJS on your object and then converts that object to a JSON string representation that is suitable for transferring back to the server. Note: this uses the browser’s native JSON.stringify() function, which is not available in some older browsers. One way to overcome this is by referencing a script from here.

Removing properties from our converted array

We can use ko.toJS to turn our observables into a plain object, but we may need to do some additional tweaking before it is ready to send to the server. A common scenario is that your view model contains various computed observables for display that are not expected by your server-side code. Again ko.utils.arrayMap is useful to trim the fat off of your objects:

1
2
3
4
5
var items = ko.toJS(this.items);
var mappedItems = ko.utils.arrayMap(items, function(item) {
    delete item.priceWithTax;
    return item;
});

Now our items are in a proper format for posting to our server.

Knockout contains a number of utility functions that are useful for manipulating your view model. These were the ones that I find most useful. Check out the Knockout source to see all of the available utility functions.

Here is a completed sample demonstrating these utility functions:

Link to full sample on jsFiddle.net

Are Your Templates Working Overtime?

| Comments

Update: much of this is obsolete, if using native templates in KO 2.0

When reading the KnockoutJS documentation regarding observableArrays, one feature that immediately stands out is the ability to have a template rendered only for the changed items in an array. This is accomplished by using the foreach option of the template binding, as opposed to the {{each}} syntax of the jQuery Templates plugin.

The Problem

Anyone using an observableArray that is frequently manipulated will certainly want to take advantage of the foreach feature. However, I find it easy to catch your fingers typing something like this:

1
2
3
4
5
6
7
8
9
<script id="main" type="text/html">
    {{if items() && items().length > 0}}
    <div class="items">
         <h3>Items</h3>
        <ul data-bind="template: { name: 'itemTmpl', foreach: items }"></ul>
     </div>
    {{/if}}
    <a href data-bind="click: addItem">Add Item</a>
</script>

Our objective is simple: If there is anything in our items array, then we want to display a section with a header and the name of each item. Whenever an item is added to our array, we want to just render that new item using a template named itemTmpl. The foreach option of the template binding should take care of this for us, but we have a problem. All of our items are actually getting re-rendered on each change. Try it here.

Dependencies in Templates

In Knockout, bindings are implemented using computed observables to keep track of any dependencies that should trigger the binding to run again. The template binding uses this concept as well. Any observables that are accessed in your template outside of data-bind attributes will create a dependency.

Our main template actually has a dependency on the items observableArray in the {{if}} statement that checks to see if the array exists and has a length greater than zero. Any change to items will cause this template to be re-rendered, which will re-render the ul element that contains our foreach.

Potential Solutions

To find a potential solution to this issue, we need to be very careful about which observables that we access outside of a data-bind attribute in our template. The easiest solution is to use the visible binding on our section container instead of wrapping it in an {{if}} statement. If we know that our observableArray will always be initailized to at least an empty array, then the we won’t need the null check. In many cases, this should be a suitable alternative.

1
2
3
4
5
6
7
<script id="main" type="text/html">
     <div data-bind="visible: items().length > 0" class="items">
         <h3>Items</h3>
         <ul data-bind="template: { name: 'itemTmpl', foreach: items }"></ul>
     </div>
     <a href data-bind="click: addItem">Add Item</a>
</script>

On each change of the items array, we will now only evaluate the visibility of the container div rather than re-render the entire template.

In some cases though, the {{if}} statement may really have been our preferred approach. Maybe we have to do a lot of unnecessary work in the template to generate some elements that will be hidden by the visible binding. What we would really like to do is bind to an observable that only changes when the array crosses between 0 and 1 items.

The easiest way to accomplish this is by using a manual subscription. A computed observable seems like a logical choice, but it would actually get re-evaluated on any change to its dependencies. We only want to trigger a change when the array moves between empty and having items. A manual subscription that controls a separate observable will give us the flexibility to control how it changes.

1
2
3
4
5
6
7
8
9
10
viewModel.hasItems = ko.observable(true);

//only change hasItems when we cross between 0 and 1 items
viewModel.items.subscribe(function() {
    var current = viewModel.hasItems();
    var actual = viewModel.items() && viewModel.items().length > 0;
    if (current != actual) {
        viewModel.hasItems(actual);
    }
}, viewModel);

Now, we can go back to using a template with an {{if}} statement, but this time it will be bound to the hasItems observable.

1
2
3
4
5
6
7
<script id="main" type="text/html">
     <div class="items">
         <h3>Items</h3>
         <ul data-bind="template: { name: 'itemTmpl', foreach: items }"></ul>
     </div>
     <a href data-bind="click: addItem">Add Item</a>
</script>

The template rendering is smart enough to recognize that hasItems by itself is a function and will call it appropriately to retrieve the value of our observable, so we do not need to write hasItems(). In this case we want our template to get re-rendered whenever hasItems changes. However, if our main template had more content, then this might not be so attractive. We might need to break our templates into smaller parts and pass either the data or foreach options to the template binding to render sub-templates.

Test your Templates

For any application that makes significant use of the foreac option of the template binding, it would be useful to at least do a small amount of testing to ensure that your templates are being rendered as you expect.

One easy way to do this testing is to print out the time that your template is being rendered. The way that I have been doing this is by extending the jQuery Template plugin tags with a now tag like this:

1
2
3
4
5
$.extend(jQuery.tmpl.tag, {
    now: {
        open: '__=__.concat((" -rendered at: " + (new Date()).toLocaleTimeString()));'
    },
});

You can use anywhere that you want this information included. Note: if you are using a version of the jQuery Templates plugin prior to 1.0.0pre, then you should use _ instead of __ in the open tag.

Sample:

Link to full sample on jsFiddle.net

Using External jQuery Template Files With KnockoutJS

| Comments

Update: another good solution for this topic is to use the external template engine found here.

A question recently came up on the Knockout forums about how to get better design time support in Visual Studio when working with jQuery templates and how to incorporate external template files into Knockout projects. Here are three different takes on solving both of these problems that each build on the previous idea.

Level One – Load individual template files

  1. Store each template in a .html file without the script tags. You could use a different extension, but using .html or .htm will ensure that both the IDE will treat it properly and that the web server will serve it up without further configuration.
  2. Make an AJAX request for each template file that you need and inject it into the document inside of a script tag.
  3. Use a convention that the filename without the extension is the ID of the template (id attribute of the script tag).
  4. Make sure that all of the template files have been loaded prior to calling ko.applyBindings

Example

First we store our templates in individual .html files. For example, suppose I have templates for a read-only view and for an editable view of an item. My read-only template might be stored in a file called Templates/itemTmpl.html and look like:

1
2
3
4
5
6
7
<tr>
    <td data-bind="text: name"></td>
    <td class="buttons">
        <button data-bind="click: function() { viewModel.editItem($data); }">Edit</button>
        <button data-bind="click: function() { viewModel.deleteItem($data); }">Delete</button>
    </td>
</tr>

On my page load, I would load my templates using something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$(function() {
    ensureTemplates(["itemTmpl", "editTmpl"]);
});

function ensureTemplates(list) {
    var loadedTemplates = [];
    ko.utils.arrayForEach(list, function(name) {
        $.get("Templates/" + name + ".html", function(template) {
            $("body").append("<script id=\"" + name + "\" type=\"text/html\">" + template + "<\/script>");
            loadedTemplates.push(name);
            if (list.length === loadedTemplates.length) {
                ko.applyBindings(viewModel);
            }
        });
    });
}

The ensureTemplates function will load each template that is required using our convention that the name of the file matches the id of the script. We inject the script tag into the document, as if it had been there the whole time. We keep track of how many have been loaded and in the success callback of the last one to load we call ko.applyBindings.

Note: We probably should consider some additional error handling here to make sure that we either retry loading any templates that fail or at least present the user with an error message if all of the templates fail to load.

Pros

  • Gives each page the flexibility to load only the templates that it needs
  • Gives good Intellisense and syntax highlighting in Visual Studio
  • No further configuration necessary

Cons

  • The requests are a bit chatty, as it makes individual requests for each file.
  • Have to coordinate waiting for all files to load.

Link to Level One jsFiddle

Level Two – Load a single file generated at build time

  1. As in Level One, store each template in a .html file.
  2. At build time, generate a single html file that includes all of your template files and wraps them in their script tags. Without being wrapped in their script tags or at least some identifiable container, we would not be able to easily understand where the templates start and end.
  3. Make an AJAX request for the single .html file and call ko.applyBindings in the success callback of that request.

Example

We would store our templates in the same manner, but then during our build we would produce a single file that contains all of our templates. Here is one way to produce such a file using MSBUILD for a .NET Web Application project.

Open your Web Application’s .csproj file. At the end of the file uncomment the AfterBuild task and make it look something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<Target Name="AfterBuild">
    <Delete Files="Templates\all.html" />
    <ItemGroup>
      <Templates Include="Templates\*.html" />
    </ItemGroup>
    <MSBuild Projects="$(MSBuildProjectFile)"
           Properties="Template=%(Templates.Identity);Name=%(Templates.FileName)"
           Targets="PrepareOneTemplate">
    </MSBuild>
  </Target>
  <Target Name="PrepareOneTemplate">
    <PropertyGroup>
        <Prefix><![CDATA[<script id="$(Name)" type="text/html">
]]>
</Prefix>
         <Suffix><!<![CDATA[
</script>]]></Suffix>
        <Contents>$(Prefix)$([System.IO.File]::ReadAllText($(Template)))$(Suffix)</Contents>
        <AllFile>Templates\all.html</AllFile>
    </PropertyGroup>
    <WriteLinesToFile File="$(AllFile)" Lines="$(Contents)" Overwrite="false" />
  </Target>

On each build, we first delete our composite file that I named all.html. Then, we call a target named PrepareOneTemplate for each of our template files. The PrepareOneTemplate target simply builds a string that wraps the template file contents in an appropriate script tag and writes it to the composite file. The end result is a single file that contains all of the script tags for our templates.

There certainly are many ways that this file could be generated. I’m sure that most people could find a way to generate it as part of their build process, even by just calling some shell commands.

Pros

  • A single AJAX request is made for the templates.
  • Gives good Intellisense and syntax highlighting in Visual Studio, as you would still be always editing the individual .html template files.
  • Final project does not even need to include the individual .html files. If someone accidentally browsed to the composite .html file they would not see anything visible.

Cons

  • If we need to load different subsets of templates on each page, then we might have to do some work in the build to properly generate a variety of composite files.

Link to Level Two jsFiddle

Level Three – Reference a script generated at build time

  1. As in Level One, store each template in a .html file.
  2. At build time, generate a single .js file. This file would include JavaScript to inject a string representation of each template into the document.
  3. Reference the script on your page prior to your code that calls ko.applyBindings.

Example

This time, we can use the same technique as Level Two in MSBUILD, we just need to generate lines that look like:

1
jQuery("body").append("<script id=\"yourid\">yourtemplate</script>");

This could even just do a document.write with the script tag and it would work or append it without the jQuery. I just like appending it to the body, as if it had been there all along.

The MSBUILD task would look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<Target Name="AfterBuild">
    <Delete Files="Templates\all.html;Templates\all.js" />
    <ItemGroup>
      <Templates Include="Templates\*.html" />
    </ItemGroup>
    <MSBuild Projects="$(MSBuildProjectFile)"
           Properties="Template=%(Templates.Identity);Name=%(Templates.FileName)"
           Targets="PrepareOneTemplate">
    </MSBuild>
  </Target>
  <Target Name="PrepareOneTemplate">
    <PropertyGroup>
        <Prefix><![CDATA[<script id="$(Name)" type="text/html">
 ]]></Prefix>
         <Suffix><![CDATA[
</script>]]></Suffix>
        <Contents>$(Prefix)$([System.IO.File]::ReadAllText($(Template)))$(Suffix)</Contents>
        <AllFileJS>Templates\all.js</AllFileJS>
        <ContentsJS>jQuery("body").append("$(Contents.Replace('%22','\%22').Replace('%0A','').Replace('%0D',''))%22)%3B</ContentsJS>
    </PropertyGroup>
    <WriteLinesToFile File="$(AllFileJS)" Lines="$(ContentsJS)"    Overwrite="false" />
  </Target>
</Project>

We again wrap the template file contents in a script tag. Then we replace all quotes with \” and strip any carriage returns or line feeds. Finally, we put in the JavaScript statement that injects the text into our document.

Again, I am sure that there are many ways that this file could be generated as well, possibly by processing the files with regular expressions.

Pros

  • No need to add code for making an AJAX request, as the script tag would handle it naturally.
  • Gives good Intellisense and syntax highlighting in Visual Studio, as you would still be always editing the individual .html template files.
  • Could pull templates cross-domain, although doubtful that it would be a normal scenario.
  • Final project does not even need to include the individual .html files.

Cons

  • If we need to load different subsets of templates on each page, then we might have to do some work in the build to properly generate several scripts.

Link to Level Three jsFiddle

Final Thoughts

So far, I am happiest with the Level Three method. All three methods give you a decent experience in the IDE, but the last one is easy to use on a page and the build process takes care of the work. Any other ideas/thoughts/methods for including external templates while getting a good design-time experience?

Reacting to Changes in KnockoutJS: Choosing the Right Tool for the Right Job

| Comments

When starting out with Knockout, it doesn’t take long before you encounter a situation where observables and the default bindings don’t quite solve your problem in an elegant way. At that point, it really helps to understand the various options available to you in Knockout. Should you create a computed observable? Is it time to write your first custom binding? These tools along with manual subscriptions and writable computed observables give you some powerful ways to react to changes on your view model.

To demonstrate these options, let’s say that we are creating a small application to track store inventory. Here are the requirements of our application:

  1. Track the quantity and retail price of items in the store
  2. Display a total on each line based on the current quantity and price. Visually indicate when the total has been updated after any edits.
  3. Format the price and total as currency.
  4. Default the price of each item based on the selected product’s wholesale price.

Let’s see how we can meet these requirements in Knockout.

computed observables

  • computed observables are great for read-only fields that are based on one or more observables.
  • Our total field fits this requirement. Here is how we would define our computed observable in our Item constructor:
1
2
3
this.total = ko.computed(function() {
    return this.quantity() * this.price();
}, this);
  • Any observables accessed in the function will trigger updates to this field. In this case we are accessing the quantity and price, so our field will be updated any time that the quantity or price of the item are edited.

Custom Bindings

  • Knockout allows you to create custom bindings, if the released options don’t meet your needs.
  • Bindings are actually implemented as computed observables, so any observables accessed in the binding code will create dependencies.
  • The code called by your binding has access to the element containing the binding along with the data passed to it. Additionally, the binding code can access the other bindings listed on the element and the overall object that was the context of the binding.
  • Bindings should be reserved for cases where you need to connect your UI and your view model. While you could write a total binding that updates a field based on the quantity and price, this would make your view model less portable and tie the total concept to this UI.
  • Animations or other functionality that manipulates DOM elements based on changes to the view model are good candidates for bindings.

In our sample, we need to add some animation to the total field whenever it is updated. We can write a wrapper to the text binding that looks like this:

1
2
3
4
5
6
7
ko.bindingHandlers.fadeInText = {
    'update': function(element, valueAccessor) {
        $(element).hide();
        ko.bindingHandlers.text.update(element, valueAccessor);
        $(element).fadeIn('slow');
    }
};

Then, we can specify the binding on our total field to cause it to fade in each time that it is modified.

1
<td data-bind="fadeInText: total"></td>

Manual Subscriptions

  • Manual subscriptions can be thought of like bindings for your view model. Bindings allow your UI to react to changes in your view model, while manual subscriptions allow your view model to react to changes to itself. This may mean making updates to other related objects in your view model.
  • A common example is triggering an update to an observable via AJAX when another observable changes, such as when a checkbox is checked or a dropdown value is changed. In this case, the manual subscription acts like an asynchronous computed observable.

In our sample, we can use manual subscriptions to fill in a default value for the price whenever the user selects a different product from the catalog. We would define our subscription in our Item constructor like:

1
2
3
4
5
6
this.productId.subscribe(function(newProductId) {
    var newProduct = ko.utils.arrayFirst(viewModel.productCatalog, function(product) {
        return product.id == newProductId;
    });
    this.price(newProduct.wholesale);
}.bind(this));

Whenever the user selects a different product, we will default the Item’s price to the product’s wholesale price.

Writable computed observables

  • writable computed observables are a powerful feature that were added after the 1.12 release, so they are currently available only in the latest code.
  • They are best used when the value displayed in a field is not the value that you want saved to your model.
  • This functionality can be useful in cases where you want to broker between objects and IDs like when your object stores a reference by ID to an item in another array.
  • A typical example is when you want to format an editable field and then parse any edits to the field, so that you can update the original observable.

In our sample, we can use this concept to display currency for the price and parse the input back into a number to update the price. We would define it like:

1
2
3
4
5
6
7
8
9
10
11
12
//writable computed observable to parse currency input
this.editPrice = ko.computed({
    //return a formatted price
    read: function() {
        return viewModel.formatCurrency(this.price());
    },
    //if the value changes, make sure that we store a number back to price
    write: function(newValue) {
        this.price(viewModel.parseCurrency(newValue));
    },
    owner: this
});

Now, we can bind our input field for price to the editPrice computed observable and we will ensure that our price observable always contains a numeric value.

Completed Sample:

Link to full sample on jsFiddle.net

Guard Your Model: Accept or Cancel Edits to Observables in KnockoutJS

| Comments

Two-way data-binding is one of the main benefits of using KnockoutJS. However, I have found some situations where it is inconvenient that the underlying model is immediately updated based on user input. In these scenarios, I would prefer to allow a user to accept or cancel their edits first before applying them to my view model.

I experimented with a few options for cleanly managing this type of interaction:

  • Temporary objects - One option that I tried was using a temporary copy of the real object for editing. If the user cancels, then I could simply throw away the object, while on an accept I would need to replace the original version with the edited copy. This works, but brokering the objects back and forth between the temporary and original objects can get messy, especially if your structures are complex.
  • Temporary properties – Another option that I explored was adding temporary properties on each of my objects and copying the temp values into the originals on an accept. This is pretty simple and works well, but now all of the objects require extra properties, which bloat the model and possibly need to be stripped out before being sent back to the server. Additionally, we must be careful to always bind against the temporary properties in edit scenarios.
  • Special Observable - The final option that I investigated was creating some type of augmented observable that manages this functionality internally. I settled on creating a simple protectedObservable that could be used like any other observable, but had the extra features to support the accept/cancel requirement.

Here is how I define the protectedObservable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//wrapper to an observable that requires accept/cancel
ko.protectedObservable = function(initialValue) {
    //private variables
    var _actualValue = ko.observable(initialValue),
        _tempValue = initialValue;

    //computed observable that we will return
    var result = ko.computed({
        //always return the actual value
        read: function() {
           return _actualValue();
        },
        //stored in a temporary spot until commit
        write: function(newValue) {
             _tempValue = newValue;
        }
    }).extend({ notify: "always" });

    //if different, commit temp value
    result.commit = function() {
        if (_tempValue !== _actualValue()) {
             _actualValue(_tempValue);
        }
    };

    //force subscribers to take original
    result.reset = function() {
        _actualValue.valueHasMutated();
        _tempValue = _actualValue();   //reset temp value
    };

    return result;
};

A few things to note:

  • A protectedObservable is an augmented computed observable
  • The computed observable is dependent on an observable (_actual) that is private (only accessible internally by the function).
  • It is defined as a writable computed observable, so we have control over the read and write methods. The read method always returns the value of our underlying observable The write method stores the edited value in a private variable called _temp.
  • The commit method is used to actually update the observable from the temp value, if it has changed.
  • The reset method simply tells subscribers to update their value again from the original and resets the temporary value (so it would not cause unexpected behavior if commit was called without any further edits).

We can define these in our view model similar to any other observable:

1
2
3
4
5
6
7
8
9
10
11
12
var viewModel = {
      name: ko.protectedObservable(Item A),
      quantity: ko.protectedObservable(10),
      commitAll: function() {
          this.name.commit();
          this.quantity.commit();
      },
      resetAll: function() {
          this.name.reset();
          this.quantity.reset();
      }
  };

We can data-bind to these with the normal syntax:

1
2
3
4
<input data-bind=“value: name />
<input data-bind=“value: quantity />
<button data-bind=“click: commitAll>Commit</button>
<button data-bind=“click: resetAll>Reset</button>

So, now we have a self-contained observable where values are only persisted to the model when explicitly committed.

Here is an expanded example with a list editor that uses protectedObservables:

Link to jsFiddle

I think that the idea of a computed observable that depends on its own private observable could have some possibilities for solving other issues in a clean way. For example, recently a user had issues with numeric information in input fields being saved as strings back to the model. In absence of an official option on the value binding or on observable creation to support this need, one solution could be to create a numericObservable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ko.numericObservable = function(initialValue) {
    var _actual = ko.observable(initialValue);

    var result = ko.dependentObservable({
        read: function() {
          return _actual();
        },
        write: function(newValue) {
           var parsed = parseFloat(newValue);
            _actual(isNaN(parsed) ? newValue: parsed);
        }
    });

    return result;
};

Now, if the value is numeric it would be stored in the model as a number (sample here). Basically, this technique allows us to create observables with getter/setter functions to have better control over how the data is set and retrieved.

As for something like a protectedObservable, anyone have other ideas for clean ways to deal with this scenario? I was also thinking about how validation or change tracking could be added to an observable in the same fashion. I did come across a post by Steve that could achieve similar results using the interceptor pattern that would be interesting to explore as well. Any other ideas for clever ways to use this technique or features that it could support?