Object JavaScript – Dynamic UI With MVVM Using ObservableArray in Knockout.js

knockoutIn our previous post Dynamic UI Using Observables with MVVM Using Knockout.js, you learned how you get started with Knockout.js and how you can detect and respond to changes on one object using observables.

Knockout.js simplifies JavaScript UI by applying the Model-View-ViewModel pattern.

Now if you want to detect and respond to changes of a collection of things, you can use an observableArray. An observableArray tracks which objects are in the array, not the state of those objects.

An observableArray tracks which objects it holds, and notifies listeners when objects are added or removed.

You can make the items themselves observable if you wish, but we’ll start with a basic observableArray.

Let’s start with a simple list of products and make them an observableArray.


// This observable array initially contains four objects
var productArray = ko.observableArray([
{ product: "Widget", type: "Tool" },
{ product: "Bolt", type: "Fastener" },
{ product: "Washer", type: "Fastener" },
{ product: "Screwdriver", type: "Tool" }
]);

Using Control Flow Bindings

Now that we have an observableArray, we can use the foreach binding to display the data in a table.

The foreach binding duplicates a section of markup for each entry in an array, and binds each copy of that markup to the corresponding array item. This is especially useful for rendering lists or tables.

<tbody data-bind="foreach: productArray">
   <tr>
     <td data-bind="text: product"></td>
     <td data-bind="text: type"></td>
   </tr>
</tbody>

In the following example, you can see how an observable array is bound to a table using Knockout.


<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Bind Observable Array to Table</title>
</head>
<body>
<table border="0">
<thead>
<tr>
<th>Product</th>
<th>Type</th>
</tr>
</thead>
<tbody data-bind="foreach: productArray">
<tr>
<td data-bind="text: product"></td>
<td data-bind="text: type"></td>
</tr>
</tbody>
</table>
<script src="Scripts/knockout-2.2.1.debug.js"></script>
<script>
var productArray = ko.observableArray([
{ product: "Widget", type: "Tool" },
{ product: "Bolt", type: "Fastener" },
{ product: "Washer", type: "Fastener" },
{ product: "Screwdriver", type: "Tool" }
]);
ko.applyBindings(new productArray());
</script>
</body>
</html>

Manipulating the Array

To start with, the most basic thing you can do is display and add to an array. The following sample shows how to bind a list of items to a select box using an observableArray. You can then click the Add button to add items to the view model.


<!DOCTYPE html>
<html>
<head>
<title>Observable Array 1</title>
</head>
<body>
<form >
New item:
<button data-bind="click: addItem">Add</button>
<select multiple="multiple" data-bind="options: observableItems"></select>
</form>
<script src="Scripts/knockout-2.2.1.debug.js"></script>
<script>
var number = 0;
var listData = ["Alpha", "Beta", "Gamma"];
var listViewModel = function (items) {
this.observableItems = ko.observableArray(items);
this.count = this.observableItems().length;
this.addItem = function () {
this.observableItems.push("Item " + number++); // Adds a generic item.
}.bind(this); // Ensure that "this" is always this view model
};
ko.applyBindings(new listViewModel(listData));
</script>
</body>
</html>

When you add an item to the array, you can push it onto the array using one of the many standard JavaScript array functions.

Standard JavaScript Array Functions

You can use any of the native JavaScript array functions to operate on that underlying array, such as:

alert('The length of the array is ' + productArray().length);
alert('The first element is ' + productArray()[0]);

You can also use Knockout functions, such as indexOf that will work across browsers. (The JavaScript method indexOf does not work in IE 8 or earlier. But in Knockout it works everywhere.)

  • indexOf function returns the index of the first array item that equals your parameter. For example, myObservableArray.indexOf('Blah') will return the zero-based index of the first array entry that equals Blah, or the value -1 if no matching value was found.
  • slice function is the observableArray equivalent of the native JavaScript slice function. It returns the entries of your array from a given start index up to a given end index.
  • push('Some new value') adds a new item to the end of array.
  • pop() removes the last value from the array and returns it.
  • unshift('Some new value') inserts a new item at the beginning of the array.
  • shift() removes the first value from the array and returns it
  • reverse() reverses the order of the array.
  • sort() sorts the array contents. By default, it sorts alphabetically (for strings) or numerically (for numbers). Optionally, you can pass a function to control how the array should be sorted.
  • splice() removes and returns a given number of elements starting from a given index. For example, myObservableArray.splice(1, 3) removes three elements starting from index position 1 (i.e., the 2nd, 3rd, and 4th elements) and returns them as an array.

For more details about these observableArray functions, see the equivalent documentation of the standard JavaScript array functions.

Helpful Methods Not Found in JavaScript

observableArray adds some more useful methods that aren’t found on JavaScript arrays by default:

  • remove(someItem) removes all values that equal someItem and returns them as an array
  • remove(function(item) { return item.age < 18 }) removes all values whose age property is less than 18, and returns them as an array
  • removeAll(['Chad', 132, undefined]) removes all values that equal 'Chad', 123, or undefined and returns them as an array
  • removeAll() removes all values and returns them as an array

In addition, destroy and destroyAll functions are mainly intended as a convenience for developers using Ruby on Rails.

References

Getting Started with Knockout