Book Image

Getting Started with Knockout.js for .NET Developers

By : Andrey Ankshin
Book Image

Getting Started with Knockout.js for .NET Developers

By: Andrey Ankshin

Overview of this book

Table of Contents (14 chapters)
Getting Started with Knockout.js for .NET Developers
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

Knockout.js fundamentals


In this section, you will learn how to create a very simple "Hello World" application with step-by-step instructions. Each step will describe one of the main Knockout.js concepts.

Creating a View

Let's learn about Knockout.js with a very simple example. We start work with the following code of an HTML page (HelloWorld-Example1.html):

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Hello world on Knockout.js</title>
  </head>
<body>
<span>Hello World!</span>

<script type='text/javascript' src='knockout-3.1.0.js'></script>
</body>
</html>

If we open this page in our favorite web browser, we will see the blank page with a single line, Hello World!. The body of this HTML page is the View layer of our MVVM pattern.

However, we did not use Knockout.js in this example. Let's change it.

Adding a ViewModel

Let's add some JavaScript code to our example. We will move information about the printed string from the View layer (HTML page) to the ViewModel layer (JavaScript object):

<script type='text/javascript'>
  var viewModel = {
    message: "Hello world!"
  };
  ko.applyBindings(viewModel);
</script>

In this example, the viewModel is a simple JavaScript object with a single message property. In the last line of the script, we activated Knockout.js by the applyBindings method of a global Knockout.js object called ko. The first parameter takes the ViewModel object that you want to use.

It's time to connect ViewModel with our HTML.

Adding a data binding

Let's change the inline Hello World! string to an empty span element with data binding (the full code of the page is placed in HelloWorld-Example2.html):

<span data-bind="text: message"></span>

The syntax of Knockout's declarative bindings provides a powerful way to link your data (the View) with your UI (the ViewModel). You can see an example of such syntax in the preceding HTML line; it consists of two parts (as a value of the data-bind property), the binding name and value, separated by a colon. In the example, we bind the text property of the span element to the user-defined message property of ViewModel.

Because of the message property, the content of the span element is Hello world!. The rendered HTML page is still represented by a single text line.

Adding an observable

Now our example has one thing lacking: changes of the ViewModel don't link to changes of the View. We can improve the situation with observables. Let's update the viewModel definition (see HelloWorld-Example3.html):

var viewModel = {
  message: ko.observable()
};
viewModel.message("Hello world!");
ko.applyBindings(viewModel);

In this example, message is an observable property. It means that after any changes to the message property, UI elements with corresponding bindings will be automatically updated.

You should understand that the ko.observable properties are actually functions. Therefore, you can use these functions to read and write. If you want to write some value in a property, you should call an observable function with a new value as the first parameter (such as in the preceding listing). If you want to read a value from a property, you should call the observable function without any parameters:

viewModel.message("Hello, world!");   // Write operation
currentMessage = viewModel.message(); // Read operation

The observables are the most important part of the Knockout.js MVVM structures. All of your ViewModel properties that are involved in the UI update process should be defined as an observable (such as in the preceding code via ko.observable). A non-observable property will work only for one-time read-only data binding. It may be useful for some special cases, but generally, you should describe all of your ViewModel properties as observables.

Also, you can set the initial observable value directly in the definition (for example, message: ko.observable("Hello world!")).

Subscribing to observables

Most applications don't need an explicit way to subscribe to observables (such operations are performed implicitly using the declarative style), but in some special scenarios, it may be useful. Now we consider the explicit way for a better understanding of the observables concept.

Explicitly subscribing means declaring a callback with the subscribe function, as shown in the following example (see HelloWorld-Example4.html):

viewModel.message.subscribe(function(newValue) {
  alert("New message is " + newValue);
})

After this subscription, any changes to the message property would entail an alert message about the changes.

In a real application, it may be useful to create an additional logic for the observable property change event that you can't make by the usual declarative bindings.

Updating View in a forced way

A data binding can have additional properties. Let's consider an example. By default, the View layer gets a notification about data changes only if the data was actually changed. However, we can modify this behavior and make ViewModel always send notifications. For this purpose, you will use the so-called notify extender to ensure that our subscribers are always notified on a write request, even if the new property value is the same:

viewModel.message.extend({ notify: 'always' });

In the preceding line of code (see HelloWorld-Example5.html), we call the extend function to update the notify property of message by the always value.

In a real application, it may be useful if you want to notify a user about any change operation of data, regardless of a new value.

Delaying and suppressing change notifications

Let's consider another extender example. Normally, an observable notifies its subscribers immediately, as soon as it's changed. However, if an observable is changed repeatedly or triggers expensive updates, you may get better performance by limiting or delaying the observable's change notifications, as follows:

viewModel.message.extend({ rateLimit: 100 });

In the preceding line of code (see HelloWorld-Example6.html), we call the extend function to update the rateLimit property of message by 100. It means that ViewModel will notify the View about changes no more than once in every 100 milliseconds.

Adding dynamic behavior

It's time for a more interesting example. We will add some dynamic behavior. Let's insert a button to add an exclamation mark to our message. The new representation of the View layer will be as follows:

<span data-bind="text: message"></span><br />
<button data-bind="click: addExclamationMark">Add exclamation mark</button>

The representation of the ViewModel layer will be as follows:

var viewModel = {
  message: ko.observable(),
  addExclamationMark : function() {
    this.message(this.message() + "!")
  }
};
viewModel.message("Hello world!");
ko.applyBindings(viewModel);

In the View, we can see the button with the new declarative binding: click. This binding expression sets the click button event handler to addExclamationMark. We can find the declaration of this function in the ViewModel:

  addExclamationMark : function() {
    this.message(this.message() + "!")
  }

In the body of the function, we used the message property twice: once to read and once to write. More specifically, we took the current value of message, added an exclamation mark to the obtained string value, and set the composite string as the new message value.

Try to run the example (HelloWorld-Example7.html) and click on the button. You will see how the message is modified, as shown in the following screenshot:

A binding diversity

There are different ways to use Knockout.js declarative bindings. We will consider it in the following chapters, but for now, you can briefly look at the following binding example to understand the diversity of opportunities that you have with Knockout.js. You can find the full list of examples with comments in the BindingDiversity.html file.

Single and multiple bindings

An element can use a single binding (described by the name and value) or multiple bindings (related and unrelated). In the last case, each binding should be separated from the previous one by a comma:

<!-- Single -->
<span data-bind="text: message"></span>

<!-- Multiple related -->
<input data-bind="value: name, valueUpdate: 'afterkeydown'" />

<!-- Multiple unrelated -->
<input data-bind="value: name, enable: isNameEnable" />

Value representation

The value of a binding can be represented by a single value, variable, or literal. In addition, you can use some JavaScript expressions, including function calls, as shown in the following code:

<!-- Variable -->
<div data-bind="visible: shouldShowMessage">...</div>

<!-- Simple expression + value -->
<span data-bind="text: price() > 50 ? 'expensive' : 'cheap'"></span>

<!-- Functional call -->
<button data-bind="enable: parseAreaCode(cellphoneNumber()) !== '555'">...</button>

<!-- Function expression -->
<div data-bind="click: function (data) { myFunction('param1', data) }">...</div>

<!-- Object literal -->
<div data-bind="with: {emotion: 'happy', 'facial-expression': 'smile'}">...</div>

Note that such examples demonstrate a kind of bad practice because the good way encapsulates all the logic in the ViewModel layer.

White spaces

White spaces (spaces, tabs, and newlines) do not affect bindings. The following examples are all equivalent:

<!-- Without spaces -->
<select data-bind="options:availableCountries,optionsText:'countryName',value:selectedCountry,optionsCaption:'Choose...'"></select>

<!-- With spaces -->
<select data-bind="options : availableCountries, optionsText : 'countryName', value : selectedCountry, optionsCaption : 'Choose...'"></select>

<!-- With newlines -->
<select data-bind="
    options: availableCountries,
    optionsText: 'countryName',
    value: selectedCountry,
    optionsCaption: 'Choose...'"></select>

Skipping the value

If you use Knockout.js 3.0+, you can use the binding without a value, as shown in the following code. In this case, binding will have an undefined value. It can be useful with binding preprocessing (you will learn about this feature in future chapters):

<!-- Without a value -->
<span data-bind="text">Text that will be cleared when bindings are applied.</span>

Useful links

You can find more useful information about Knockout.js at the following links:

Information about Knockout.js developers can be found at the following links:

Tip

Downloading the example code

You can download the example code files from your account at http://www.packtpub.com for all the Packt Publishing books you have purchased. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.