TIL Hướng dẫn KnockoutJS cho người mới
TIL
496
White

Ngọc Tú viết ngày 05/05/2016

Beginers Guide to KnockoutJS

resource:
MVVM

Model (Server / DB)
View Model (JavaScript)
View (Html)

Knockout JS is Focused (light)
Compatible (IE6)
Reactive (automationally)

A Beginners guide to KO: Basics and Observables

Ko implements Model-View-View-Model (MVVM) design pattern for JavaScript. In this pattern your applidation is split into three parts:

  • A model holds yours application's data. This can be data entered by users or JSON data fetched from a web server.

  • A view that serves as a connector and comminication layer between Model and View. It holds data and operations for manipulating this data and display it in the UI. Every time when data model is changed corresponded UI parts updates, reflecting these changes. View Model in your applications is represented by JavaScript code.

  • A view that refers to all UI elements in your application. It is a representation of the structure and appearance for given UI. The view is responsible for displaying data and accepting user input. View is represented by HTML/CSS code in your application.

They are three core concepts upon KO is built:

  1. Declarative Bindings: These allow you to connect parts of your UI to your data model in a simple and convenient way. When use JavaScript directly to manipulates DOM this may cause broken code if you later change the DOM hierarchy or element IDs. With declarative bindings event if you change DOM all bound pieces stay connected. You can bind data to a DOM by simply including a "data-bind" attribute to any DOM element.

  2. Dependency Tracking: Thankfully to the binddings and special type of variables called observables every time when your model data has changed all parts associated with it automatically being updated. No need to worry about adding event handlers and listeners. All that extra work is performed internally by KO and observables, which notify listeners when underlying values have changed.

  3. Templating: This comes in handy when your application becomes more complex and you need a way to display a rich structure of view model data, thus keeping your code simple. KO has a native, built-in template engine which you can use right away. But if you want, a third-party template engine, like jquery.tmpl or Underscore, also can be used.

Don’t worry if all this theory sounds obscure to you. When we go through the tutorial and the code examples everything will become clearer.

Getting Started

  • Download and reference the library in your HTML document
<script type='text/javascript' src='jquery-1.7.1.min.js'></script>
<script type='text/javascript' src='knockout-2.0.0.js'></script>
<script type='text/javascript' src='application.js'></script>
  • Now, to create a view model just declare any JavaScript object like this:
function viewModel() {

// Your code here

};
  • The data-bind attribute isn't native to HTML, and browser doesn't know what it means. So in order to take effect Ko has to be actived by inserting ko.applyBindings() function at the end of the script.
$(document).ready(function(){

  function viewModel() {

   // Your code here

  };

  ko.applyBindings(new viewModel());

});
  • Calling the ko.applyBindings() method and passing in our view model tells Ko to bind the specified model to our UI. You can even provide a DOM element if you only want to bind this view model to one part of your overall UI. ko.applyBindings() takes two parameters. The first parametar says what view model object you want to use with the declarative bindings it activates. The second parameter is optional and defines which part of the document you want to search for data-bind attributes. For example, ko.applyBindins(viewModel, document.getElementById('container')) will restrict the activation to the element with ID container and its descendants. This is useful if you want to have multiple view models and associate each with a different region of the page.

How It Work

  • With Ko, you can bind data to a DOM element by including a data-bind in the markup which specifies tha data-binding to perform. The code never has any reference to the DOM structure so you can freely change the HTML without breaking your bindings. In the following example we add text data-bind attribute to span element like this:
// syntax: data-bind="bindingName: bindingValue"
<p>The day of the week is <span data-bind="text: dayOfWeek"></span>. It's time for <span data-bind="text: activity"></span></p>
  • Then if we want to make the value of text to updates dynamically then we have to declare it in our view model as an observable.
function viewModel() {
  this.dayOfWeek = ko.observable('Sunday');
  this.activity = ko.observable('rest');
};

ko.applyBindings(new viewModel());

-This will output "The day of the week is Sun day. It's time for rest."

* Observables

  • Ko implements to observable properties by wrapping object properties with a custom function named ko.observable().
this.property = ko.observable('value')
  • Observables are set as functions. As such you can use them in the following manner:
// To read the observable's current value, just call the observable with no parameters
// The following will return Sunday
this.dayOfWeek()

// To write a new value to the observable, call the observable and pass the new value as a parameter.
// The following will change the day of week to Monday
this.dayOfWeek('Monday')

// To write values to multiple observable properties on a model object, you can use chaining syntax.
this.dayOfWeek('Monday').activity('work')
  • Ko doesn't require you to use observable properties. If you want DOM elemnt to receive values once but then not be updated when the values in the source object change, simple objects will be sufficient. However, if you want your cource object and target DOM elements to stay in sync - two-way binding - then you'll want to consider using objservable properties.

  • In some cases you may need to combine the values of two or more observables into one new value. This can be done with so-called computed observables. Computed observables are functions that are dependent on one or more other observables, and will automatically update whenever any of these dependencies change. The computed property automatically update when any of the observables it depends on for its evaluation change. In the following example the computed observable named fullDate will updates every time when one or more of the day, month and year observables change.

<p>Day: <input data-bind="value: day" /></p>
<p>Month: <input data-bind="value: month" /></p>
<p>Year: <input data-bind="value: year" /></p>
<p>The current date is <span data-bind="text: fullDate"></span></p>
function viewModel() {
  this.day = ko.observable('24');
  this.month = ko.observable('02');
  this.year = ko.observable('2012');

  this.fullDate = ko.computed(function() {
   return this.day() + "/" + this.month() + "/" + this.year();
  },this);
};

ko.applyBindings(new viewModel());
  • The ko.computed takes a second parameter this. Without passing it in, it would not have been possible to refer to this.day(), this.month() or this.year(). In order to simply things you can create a variable self, thus avoiding the addition of the second parameter. From now on we will use this approach in the code examples.
function viewModel() {
  var self = this;
  self.day = ko.observable('24');
  self.month = ko.observable('02');
  self.year = ko.observable('2012');

  self.fullDate = ko.computed(function() {
   return self.day() + "/" + self.month() + "/" + self.year();
  });
};

ko.applyBindings(new viewModel());
  • When you dealing with one object you can easily track any changes to it by turn it into an observable. But what if you have multiple objects? In such cases KO has a special object called ko.observableArray(), which can detect and respond to changes of a collection of things. This makes possible to display and/or edit multiple values, for example, when you need repeated sections of UI to appear and disappear as items are added and removed.
this.property = ko.observableArray();
  • When you create an observable array you can leave it empty or populate it with some initial values. In the following example we create an observable array populated with the days of the week:

<p>Today is <span data-bind="text: daysOfWeek()[0]"></span></p>

function viewModel() {
  var self = this;
  self.daysOfWeek = ko.observableArray([
  'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'
  ]);
  alert("The week has " + self.daysOfWeek().length + " days");
};

ko.applyBindings(new viewModel());
  • As you can see, to read and write Ko array you can use any native JavaScript functions. But ko has its own equivalent functions which syntax is a little bit more convenient:
array().push('some value'); // native JavaScript
array.push('some value'); // knockout

* Bindings

  • Ko provides a whole set of useful built-in bindings for the most common task and scenarios. Each of these bindings allows you to bind simple data values or use JavaScript expressions to calculate the appropriate value. This provides a lot of flexibility and makes it easy to create very dynamic UIs with minimum effort.

  • Syntax for using the built-in bindings is to include the ko binding name and the view model property pairs of the data-bind attribute of an HTML element.

// syntax: data-bind="bindingName: bindingProperty"
<span data-bind="text: msg"></span>
  • If you want to data bind to more than one property in the HTML element, simply separate the bindings by a comma using this syntax:

<span data-bind="text: msg, visible: toggle"></span>

  • You should bear in mind that the most of the bindings attempt to convert any parameter to a boolean value. If you give a value that isn't actually boolean, it will be interpreted loosely. This means that nonzero numbers and non-null objects and non-empty strings will all be interpreted as true, whereas zero, null, undefined, and empty strings will be interpreted as false.

* Simple Bindings

  • We've already seen text binding when dealing with observables in the previous tutorial. It sets the text of associated element to the value of your parameter. This is equivalent of setting the innerText (for IE) or textContent (for other browsers) property of the DOM element. If your parameter is something other than a number or string then the binding will assign the results of toString() to the element.

  • If this parameter is an observable value, the binding will update the element's ext whenever the value changes. If the parameter isn't observable, it will only set the element's text once and will not update it again later. This is valid for all bindings.

  • The text binding is often used to display values in a span or div element. When it is used, any previous text will be overwritten.

<p>The tip of the day is: <span data-bind="text: tipOfTheDay"></span></p>

function viewModel() {
 var self = this;
 self.tipOfTheDay = ko.observable('Relax.Take it easy!')
};
ko.applyBindings(new viewModel());
  • value binding sets value of the associated element to the value of your parameter. This is typically used for form element like input, select and textarea. When the user edits the value in the associated form control, it updates the value on your view model. Likewise, when you update the value in your view model, this updates the value of the form control on screen. This is known as two-way bindding. If your parameter is something other than a number or string then the binding will assign the results of toString() to the element.

  • By default, Ko updates your view model when user transfers focus to another Dom node, on the change event, but you can control when the value is updated using the valueUpdate parameter described below. If your binding also includes a parameter called valueUpdate, this defines which browser event Ko should use to detect changes.

  • "change" is the default event and it updates your view model when user moves the focus to a different control, or in the case of <select> elements, immediaty after any change.

  • "afterkeydown" - updates your view model as soon as the user begins typing a character. This works by catching the browser's keydown event and handing the event asynchronously. If you want to keep your view model updated in real-time using "afterkeydown" will be the best choise.

  • Other event: keyup, keypress,...

<input data-bind="value: name, valueUpdate: 'afterkeydown'"></input>
<p data-bind="text: name"></p>
function viewModel() {
 var self = this;
 self.name = ko.observable()
};
ko.applyBindings(new viewModel());

* Control Mark Up

  • The html binding isn't used as often, but it's very handy for rendering HTML content in your view model. This binding sets the HTML of the associated element to the view of your parameter and is the equivalent of setting the innerHTML property on the DOM element. If your parameter is something other than a number or string then the binding will assign the results of toString() to the element.

  • Since this binding sets your element's content using innerHTML, you should be careful not to use it with untrusted model values, because that might open the possibility of a script of a script injection attack. If you cannot guarantee that the content is safe to display, then you can use text bindding instead.

<div data-bind="html: markup"></div>

function viewModel() {
 var self = this;
 self.markup = ko.observable('<p><strong>Knockout</strong> is so <em>cool</em>!</p>')
};
ko.applyBindings(new viewModel());
  • While Ko has many built-in bindings, you will surely encounter some situations for which none exist. For those, Ko ofters the attr binding, which allow you to data bind any attribute to a view model property. The parameter should be a JavaScript object where the property names are the attributes and the property values are the value that will be bound to the attribute. This is verry useful in many common scenarios, such as binding the href and title of the a element or the src and alt of ther img element.

<img data-bind="attr: {src: url, alt: details}" />

* Add Styling

  • You can bind styles with Ko using the css and the style built-in binddings.
  • css binding sets one or more CSS classes for the associated element. The parameter should be a JavaScript object where the property names correspond to the desired CSS classes and the property value evalute to true or false indicating whether that calss should be applied. You can set multiple CSS classes at once.
<style>
.colorize {color: red}
</style>

<p data-bind="css: { colorize: on }">Text</p>
function viewModel() {
 var self = this;
 self.on = ko.observable(true)
};
ko.applyBindings(new viewModel());
  • You can use an expression to determine when the class will be applied.

<p data-bind="css: { colorize: on() > 3 }">Text</p>

  • While it is better to use css classes whenever posible, at times you might want to set a specific style as well. Ko supports this with style built-in binding which sets one or more style values for the associated element. The parameter should be an object whose properties corresoind to css style names, and the values correspond to the style values you wish to apply. Typically this parameter value is declared using JSON.

<p data-bind="style: {color: on() > 3 ? 'red' : 'black'}">Text</p>

function viewModel() {
 var self = this;
 self.on = ko.observable(5)
};
ko.applyBindings(new viewModel());
  • Note: When you have an attribute or CSS class whose name is not legal JavaScript variable name then you should wrap the identifier name in quotes so that it becomes a string literal. And if you want to apply style whose name isn't a legal JavaScript identifer, you must use the JavaScript name for that style.
//incorrect:
<div data-bind="attr: { data-something: someValue }">...</div>
<div data-bind="css: { my-class: someValue }">...</div>
<div data-bind="style: { font-weight: someValue }">...</div>

//correct:
<div data-bind="attr: { 'data-something': someValue }">...</div>
<div data-bind="css: { 'my-class': someValue }">...</div>
<div data-bind="style: { fontWeight: someValue }">...</div>

* Handling Events

  • Ko supports binding to any event through its event built-in binding. It adds event handlers for the specified events to the associated DOM element. You can use this to bind to any defined HTML events. Within your event handler you can access the current view model data item, the event object, or custom parameters passed as part of the event binding. To use event binding, you pass an object literal containing name value pairs for the event name and the view model method, separeted by commas.
<p data-bind="event: { mouseover: hello, mouseout: goodbye }"> Mouse over me! </p>
<p data-bind="text: helloEnabled"></p>
<p data-bind="text: goodbyeEnabled"></p>
function viewModel() {
var self = this;
self.helloEnabled = ko.observable()
self.goodbyeEnabled = ko.observable()
 self.hello = function() {
 self.helloEnabled('Hello!');
 self.goodbyeEnabled('');
 }
 self.goodbye = function() {
 self.goodbyeEnabled('Goodbye!');
 self.helloEnabled('');
 }
};
ko.applyBindings(new viewModel());
  • click binding, as you may guess, is handling the click event. Because it is the most-used binding for events, it's simply a shortcut to the event binding.
<button data-bind="click: writeMSG">Show</button>
<p data-bind="text: msg"></p>
function viewModel() {
 var self = this;
 self.msg = ko.observable()
 self.writeMSG = function() {
 self.msg('Hello!')
 }
};
ko.applyBindings(new viewModel());
  • submit binding is a shortcut for handling the submit event for the form element. When you use the submit bindling on a form, Ko will prevent the browser's default submit action for that form. In other words, the browser will call your handler function but will not submit the form to the server. This is a useful default because when you use the submit binding, it's normally because you're using the form as an interface to your view model, not as a regular HTML form. If you do want to let the form submit like a normal HTML form, just return true from your submit handler.

  • Instead of using submit on the form, you could use click on the submit button. But using submit binding gives you the benefits of using alternative ways to submit the form, such as pressing the enter key while typing into a text box.

* Controling UI

  • visible bindings set visibility of the associated element based on the binding parameter value. The binding attempts to convert any parameter to a boolean value. Ko's visible bindling should be bound to a property that evaluates to true or false. Thsi takes priority over any display you've defined using css.
<button data-bind="click: show">Show Message</button>
<button data-bind="click: hide">Hide Message</button>
<p data-bind="visible: msg">Hello, Knockout!</p>
function viewModel() {
 var self = this;
 self.msg = ko.observable()
 self.show = function() {
 self.msg(true)
 }
 self.hide = function() {
 self.msg(false)
 }
};
ko.applyBindings(new viewModel());
  • enable/disable binding sets the disabled attribute on the associated element based on the supplied value. This is typically used for form elements like the input, select, textarea. Ko provides built-in bindings to enable and disable input elements. The enale binding will enable the input element if the property it's bound to eveluates to true, and will disable the element if it evaluates to false. The disable binding does the exact opposite.

  • Ko has a built-in binding names hasfocus that determines and sets which element has the focus. The *hasfocus binding * is handy when you want the focus to be set to a specific element on a form for example search form when visitor open the page.

<input data-bind="value: val, hasfocus: on">
<button data-bind="enable: on">Send</button>
function viewModel() {
 var self = this;
 self.val = ko.observable()
 self.on = ko.observable(false)
};
ko.applyBindings(new viewModel());

* Dealing with Checkboxes and Radio Buttons

  • Checkboxes can be data bound to Ko's checked binding. The checked binding should be bound to a property or expression that evaluates to true or false. Because the view model properties are defiend as observables, the checkbox is updated when the source property changes. Likewise, when a user checks or unchecks the checkbox, the value is updated in the view model property. This binding sets the checks state or unchecks the checkbox, the value is updated in the view model property. This binding sets the checked state of radio buttons and checkbox. For checkboxes, the binding attempts to convert any parameter into a boolean value. For radio buttons, the binding compares the buttons value attribute to the binding paramets.

* Create Dropdown Lists

  • Dropdown lists have several important properties to load a list of items, display a value, use a different key value and store the user's selection. Ko provides a built-in binding for each of these. options binding sets the options which will appear in a drop-down list element. The value should be an array. This binding cannot be used with anything other than <select> elements. For a multi-select list, to set which of the options are selected, or to read which of the options are selected, use the selectedOptions binding. For a single-select list, you can also read and write the selected option using value binding.

  • The options binding identifies a list of values to display, usually from an array property on the view model.

<p>Choose your destiny: <select data-bind="options: availableRoles"></select></p>

function viewModel() {
 var self = this;
 self.availableRoles = ko.observableArray(['an artist', 'an actor', 'an author'])
};
ko.applyBindings(new viewModel());
  • The selectedOptions binding controls which elements in a multi-select list are currently selected. When the user selects or de-selects an item in the multi-select list, this adds or removes the corresonding value to an array on your view model.

<p>Choose your destiny: <select data-bind="options: availableRoles, selectedOptions: selected" multiple="true"></select></p>

function viewModel() {
 var self = this;
 self.availableRoles = ko.observableArray(['an artist', 'an actor', 'an author'])
 self.selected = ko.observableArray(['an author'])
};
ko.applyBindings(new viewModel());
  • Sometimes you want to display one value in the dropdown list but use another value when a user selects an item from the list. Ko's built-in optionsText and optionsValue bindings help. The optionsText binding is sets to the string name of the property to display in the dropdown list, from options binding. The optionsValue binding is set to the string name of the property to bind to for the selected value of the item in the dropdown list. optionsCaption is useful when you don't want to have any particular option selected by default. This parameter set a text such as 'Select an item...' on top of the options list and show it when there isn't any particular item selected.
<p>Locales: <select data-bind="options: locales, selectedOptions: selected, optionsCaption: 'Select your locale...', optionsText: 'country', optionsValue: 'code'"></select></p>
<p data-bind="text: selected"></p>
function viewModel() {
  var self = this;
  self.locales = [
   { country: 'USA', code: 'en_US' },
   { country: 'Spain', code: 'es_ES' },
   { country: 'French', code: 'fr_FR' }
  ]
  self.selected = ko.observableArray();
 }
ko.applyBindings(new viewModel());

* Templating and More

  • There are four control-flow bindings: foreach, if, ifnot and with. These control bindings allow you to declaratively define the control-flow logic without createing a named template as you will see below.

  • 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 suitable for rendering lists or tables. If your array is an observable array, whenever you later add or remove array entries, the binding will update the UI to match by inserting or removing more copies of the list items or table rows, without affecting any other DOM elements. See the following example:

<table>
 <thead>
  <tr><th>Title</th><th>Author</th></tr>
 </thead>
 <tbody data-bind="foreach: books">
  <tr>
   <td data-bind="text: title"></td>
   <td data-bind="text: author"></td>
  </tr>
 </tbody>
</table>

<script type="text/javascript">
  function viewModel() {
   var self = this;
   self.books = ko.observableArray([
     { title: 'The Secret', author: 'Rhonda Byrne' },
     { title: 'The Power', author: 'Rhonda Byrne' },
     { title: 'The Magic', author: 'Rhonda Byrne' }
   ]);
  }
  ko.applyBindings(new viewModel());
</script>
  • Here, a table row will be created automatically for each array entry in the books array.
  • Sometimes you may need to refer to the array entry itself rather than just one of its properties. In that case, you can use the pseudovariable $data. It means "the current item", when is used within a foreach block.
<ul data-bind="foreach: daysOfWeek">
 <li>
 <span data-bind="text: $data"></span>
 </li>
</ul>

<script type="text/javascript">
function viewModel() {
  var self = this;
  self.daysOfWeek = ko.observableArray([
   'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'
  ]);
};

ko.applyBindings(new viewModel());
</script>
  • This will list all days of the week without need to repeat the code for each item separately.

  • In Ko you can nest as many control-flow bindings as you wish. And when you do that, it's often desiable to reach backup the hierarchy and access data or functions from parent contexts. In such cacses you can use the following pseudovariables:

$parent - represents the data item outside the current foreach block
$parents - is an array representing data items from all outer control-flow scopes. $parents[0] is the same as $parent.$parents[1] represents the item from the grandparent control-flow scope, and so on.
$root - represents the item from the outer-most control-flow scope. Typically this is your top-level view model object.

  • In the following example we use the $parent pseudovariable in order to remove property a book item from the books array:
<table>
 <thead>
  <tr><th>Title</th><th>Author</th></tr>
 </thead>
 <tbody data-bind="foreach: books">
  <tr>
   <td data-bind="text: title"></td>
   <td data-bind="text: author"></td>
   <td><a href="#" data-bind="click: $parent.removeBook">Remove</a></td>
  </tr>
 </tbody>
</table>

<script type="text/javascript">
  function viewModel() {
   var self = this;
   self.books = ko.observableArray([
     { title: 'The Secret', author: 'Rhonda Byrne' },
     { title: 'The Power', author: 'Rhonda Byrne' },
     { title: 'The Magic', author: 'Rhonda Byrne' }
   ]);

  self.removeBook = function() {
   self.books.remove(this);
  }
  }
  ko.applyBindings(new viewModel());
</script>
  • In some cases, you might want to duplicate a section of markup, but you don't have any container element on which to put a foreach binding, Then you can use following syntax:
<ul>
<li><strong>Days of week:</strong></li>
 <!-- ko foreach: daysOfWeek -->
 <li>
  <span data-bind="text: $data"></span>
 </li>
 <!-- /ko -->
</ul>

<script type="text/javascript">
function viewModel() {
  var self = this;
  self.daysOfWeek = ko.observableArray([
   'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'
  ]);
};

ko.applyBindings(new viewModel());
</script>
  • In this example, you can't use a normal foreach binding. If you put it on the

      this will duplicate the header item, and if you want to put a a further container inside
        you can't bescause only
      • elements are allowed inside
          s. The solution is to use the containerless control-flow syntax where the <!-- ko --> and <!-- /ko --> comments define a "virtual element" that contains the markup inside, which syntax Ko understands and binds this virtual element as if you had a real container element.
      • This type of syntax is also valid for if and with bindings.

      • The if binding causes a section of markup to appear in your document, only if a specified expression evaluates to true. Then the contained markup will be present in the document, and any data-bind attributes on it will be applied. On the other hand, if your expression evaluates to false, the contained markup will be removed from your document without first applying any bindings to it.

    <label><input type="checkbox" data-bind="checked: showList" />Show me list</label>
    <ul data-bind="if: showList">
      <li>Item</li>
      <li>Item</li>
      <li>Item</li>
    </ul>
    
    <script type="text/javascript">
       function viewModel() {
        var self = this;
        self.showList = ko.observable(false);
       }
      ko.applyBindings(new viewModel());
    </script>
    
    • The with binding creates a new binding context, so that descendant elements are bound in the context of a specified object. The object that you want to use as the context for binding descendant elements. If the expression you supply evaluates to null or undefined, descendant elements will not be bound at all, but will instead be removed from the document. The with binding changes the data context to whatever object you specify. This is especially usefull when dealing with object graph with multiple parent/child relationships.
    <p data-bind="text: book"> </p>
    <ul data-bind="with: details">
     <li>Category: <span data-bind="text: category"> </span></li>
     <li>Author: <span data-bind="text: author"> </span></li>
     <li>Publisher: <span data-bind="text: publisher"> </span></li>
    </ul>
    
    <script type="text/javascript">
      function viewModel() {
       var self = this;
       self.book = ko.observable('The Secret');
       self.details = ko.observable({category: 'Psychology', author: 'Rhonda Byrne', publisher: 'Simon & Schuster Ltd'});
      }
     ko.applyBindings(new viewModel());
    </script>
    

    * Templating

    • The template binding populates the associated DOM element with the results of rendering a template. Templates are a simple and convenient way to build sophisticated UI structures - possibly with repeating or nested block - as a function of your view model data. There are two main ways of using templates.
    • The first one, native templating, is the mechanism that underpins foreach, if, with, and other control-flow bindings. Internally, those control-flow bindings capture the HTML markup contained in your element, and use it as a template to render against an arbitrary data item. This feature is build into Ko and doesn't require any external library. You can see basic scheme for creating a template here:
    <div data-bind="template: 'myTemplate'"></div>
    
    <script type="text/html" id="myTemplate">
    // template code here
    </script>
    
    • In the following example you can see how to use it in action:
    <div data-bind="template: 'book-template'"></div>
    
    <script type="text/html" id="book-template">
      <h3 data-bind="text: title"></h3>
      <p>Written by: <span data-bind="text: author"></span></p>
    </script>
    
    <script type="text/javascript">
       function viewModel() {
        var self = this;
        self.title = ko.observable('The Secret')
        self.author = ko.observable('Rhonda Byrne')
       }
     ko.applyBindings(new viewModel());
    </script>
    
    • Here, we must use an id equals to the template name in order to bound the template to the rest of our markup. In this case it is ‘book-template’.

    • Instead of using the short syntax described above, we can pass more parameters to the template binding, which will gives us more precise control over the final output.

    //syntax: <div data-bind="template: { name: 'myTemplate', data: myData, afterRender: myLogic }"></div>
    <div data-bind="template: { name: 'book-template', data: bestseller, afterRender: msg }"></div>
    
    //template here
    <script type="text/javascript">
       function MyViewModel() {
        var self = this;
        self.bestseller = { title: 'The Secret', author: 'Rhonda Byrne' };
        self.ordinary = {title: 'Some Name', author: 'Some Author'};
        self.msg = function(elements) {
          alert('Hip Hip Hooray!!! :)');
        }
       }
     ko.applyBindings(new MyViewModel());
    </script>
    
    • Here, the name is the id of the element that contains the template you wish to render; the data is an object to supply as the data for the template to render; and the afterRender is a callback function to be invoked against the rendered DOM elements.

    • The following example is an equivalent of a foreach binding. Here, foreach is passed as a parameter to the template binding.

    //syntax: <div data-bind="template: { name: 'myTemplate', foreach: myArray }"></div>
    
    <div data-bind="template: { name: 'book-template', foreach: books }"></div>
    
    //template here
    
    <script type="text/javascript">
       function MyViewModel() {
        var self = this;
        self.books = [
        { title: 'The Secret', author: 'Rhonda Byrne' },
        { title: 'The Power', author: 'Rhonda Byrne' }
        ]
       }
     ko.applyBindings(new MyViewModel());
    </script>
    
    • You can get exactly the same result by embedding an anonymous template directly inside the element to which you use foreach binding:
    <div data-bind="foreach: books">
      <h3 data-bind="text: title"></h3>
      <p>Written by: <span data-bind="text: author"></span></p>
    </div>
    
    • The second way of using templates is to connect Knockout to a third-party template engine. Knockout will pass your model values to the external template engine and inject the resulting markup string into your document. For examples that use the jquery.tmpl and Underscore template engines check the documentation.

    * Extending Observables

    • Ko observables provide the basic features necessary to support reading/writing values and notifying subcribers when that value changes. In some cases, though, you may wish to add additional functionality to an observable to an observable like adding additional properties to the observable. Ko extenders provide an easy and flexible way to do just that.

    • Creating an extender involves adding a function to the ko.extenders object. The function takes in the observable itselft as the first argument and any options in the second argument. It can then either return the observable or return something new like a computed observable that uses the original observable in some way.

    • Now we’ll create an observable extender which will add the ability to show a hint message.

    <input data-bind='value: name, hasfocus: name.on' />
    <span data-bind="visible: name.on, text: name.hint"></span>
    <br />
    <input data-bind='value: pass, hasfocus: pass.on' />
    <span data-bind="visible: pass.on, text: pass.hint"></span>
    
    <script type="text/javascript">
    
    // begin observable extender
    ko.extenders.hints = function(target, hint) {
     target.on = ko.observable()
     target.hint = ko.observable()
    
     function showHint(value){
      target.on(value ? false : true);
      target.hint(value ? "" : hint);
     }
    
     showHint(target());
    
     return target;
    };
    // end observable extender
    
     function viewModel() {
      var self = this;
      self.name = ko.observable().extend({hints: 'Type your name here'})
      self.pass = ko.observable().extend({hints: 'Type your password here'})
     };
    ko.applyBindings(new viewModel());
    </script>
    

    * Custom Bindings

    • Ko's built-in bindings allow you to handle most binding scenarios, but if you encounter a specialized binding scenario that isn't covered, you can create custom bindings with Ko which gives you a lot of flexibility to encapsulate sophisticated behaviors in an easy-to-reuse way. For example, you can create interactive components like grids, tabsets, and so on, in the form of custom bindings.

    • Ko bindings consist of two methods: init and update. Creating a binding is as simple as creating an object with these two methods and registering that object with Ko using ko.bindingHandlers as shown below.

    ko.bindingHandlers.yourBindingName = {
      init: function(element, valueAccessor, allBindingsAccessor, viewModel) {
    
      },
      update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
    
      }
    };
    
    // once created, you can use your custom binding in similar way as any built-in binding
    <div data-bind="yourBindingName: someValue"> </div>
    
    • The init function will only run the first time that the binding is eveluated for this element. This is usually used to run one-time initialization code or to wire up event handlers that let you update your view model based on an event being triggered in your UI.

    • The update function provides a way to respond when associated observables are modified. Typically, this is used to update your UI based on changes to your view model.

    • The init and update functions are supplied four parameters. Generally, you will want to focus on the element and the valueAccessor parameters, as they are the standard way to link your view model to your UI. You don't actually have to provide both init and update callbacks - you can just provide one or the other if that's all you need.

    • The element parameter gives you direct access to the DOM element that contains the binding.

    • The valueAccessor parameter is a function that gives you access to what was passed to the binding. If you passed an observable, then the result of this function will be that observable (not the value of it). If you used an expression in the binding, then the result of the valueAccessor will be the result of the expression.

    • The allBindingsAccessor parameter gives you access to all of the other bindings that were listed in the same data-bind attribute. This is generally used to access other bindings that interact with this binding. These bindings likely will not have any code associated with them and are just a way to pass additional options to the bindling, unless you choose to pass an object with multiple properties into your main binding. For example, optionsValue, optionsText and optionsCaption are bindings that are only used to pass options to the options binding.

    • The viewModel parameter will provides access to your overall view model for bindings outside of templates. Inside of a template, this will be set to the data being bound to the template. For example, when using the foreach option of the template binding, the viewModel parameter would be set to the current array member being sent through the template. Most of the time the valueAccessor will give you the data that you want, but the viewModel parameter is particularly useful if you need an object to be your target when you call/apply functions.

    - In the following example we will create a custom binding which scale a textarea when it is in focus.

    <textarea data-bind="scaleOnFocus: scaleArea, scaleUp: {height: '200', width: '400'}, scaleDown: {height: '15', width: '150'}"></textarea>
    
    <script type="text/javascript">
    
    // begin custom binding
    ko.bindingHandlers.scaleOnFocus = {
    
     init: function(element, valueAccessor) {
      $(element).focus(function() {
       var value = valueAccessor();
       value(true);
      });
      $(element).blur(function() {
       var value = valueAccessor();
       value(false);
      });
     },
    
     update: function(element, valueAccessor, allBindingsAccessor) {
      var value = valueAccessor();
      var allBindings = allBindingsAccessor();
      var up = allBindings.scaleUp;
      var down = allBindings.scaleDown;
       if (ko.utils.unwrapObservable(value))
        $(element).animate(up);
       else
        $(element).animate(down);
     }
    };
    // end custom binding
    
    function viewModel() {
     var self = this;
     self.scaleArea = ko.observable()
    };
    
    ko.applyBindings(new viewModel());
    </script>
    
    • First, in the init function we declare that when element is in focus then its value will be set to true, and vice versa. Then in the update function we use allBindingAccessor parameter to add additional options to our bindling - scaleUp and scaleDown. We use the ko.utils.unwrapObservable to get the current binding's value and check if it is set to true. If so, the DOM element is scaled up, otherwise it is scaled down.

    • At last let's see an example that comines the hints observable extender and scaleOnFocus custom binding:

    <input data-bind='value: name, hasfocus: name.on' />
    <span data-bind="visible: name.on, text: name.hint"></span>
    <br />
    <input data-bind='value: email, hasfocus: email.on' />
    <span data-bind="visible: email.on, text: email.hint"></span>
    <br />
    <textarea data-bind="value: msg.hint, scaleOnFocus: scaleArea, scaleUp: {height: '200', width: '400'}, scaleDown: {height: '50', width: '150'}"></textarea>
    
    <script type="text/javascript">
    ko.extenders.hints = function(target, hint) {
     target.on = ko.observable()
     target.hint = ko.observable()
    
     function showHint(value){
      target.on(value ? false : true);
      target.hint(value ? "" : hint);
     }
    
     showHint(target());
    
     return target;
    };
    
    ko.bindingHandlers.scaleOnFocus = {
    
     init: function(element, valueAccessor) {
      $(element).focus(function() {
       var value = valueAccessor();
       value(true);
      });
      $(element).blur(function() {
       var value = valueAccessor();
       value(false);
      });
     },
    
     update: function(element, valueAccessor, allBindingsAccessor) {
      var value = valueAccessor();
      var allBindings = allBindingsAccessor();
      var up = allBindings.scaleUp;
      var down = allBindings.scaleDown;
       if (ko.utils.unwrapObservable(value))
        $(element).animate(up);
       else
        $(element).animate(down);
     }
    };
    
    function viewModel() {
     var self = this;
     self.name = ko.observable().extend({hints: 'Type your full name'})
     self.email = ko.observable().extend({hints: 'Type a valid email'})
     self.msg = ko.observable().extend({hints: 'Leave a message...'})
     self.scaleArea = ko.observable()
    };
    ko.applyBindings(new viewModel());
    </script>
    

    You can place the hints observable and scaleOnFocus binding in a separate file and then including them in the main file. This makes the code modular and allows you to re-use it whenever you want.

    That’s it, folks! I hope you enjoyed this series. Now you have all necessary knowledge to start and continue learning and experimenting with Knockout. For more comprehensive examples and tutorials you can go to the Knockout site, which I suggest you to do.

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Ngọc Tú

1 bài viết.
0 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Bài viết liên quan
White
18 1
Toán tử XOR có tính chất: + A XOR A = 0 + 0 XOR A = A Với tính chất này, có thể cài đặt bài toán sau với độ phức tạp O(N) về runtime, và với O(1)...
kiennt viết hơn 1 năm trước
18 1
White
1 1
Chào mọi người, hôm nay mình viết một bài TIL nhỏ về cách lấy độ phân giải của màn hình hiện tại đang sử dụng. xdpyinfo | grep dimensions Kết quả...
namtx viết 7 tháng trước
1 1
White
8 0
Lấy fake path của file trong html input Ngữ cảnh: em cần làm một cái nút tải ảnh lên có preview. GIải pháp đầu: Dùng (Link) đọc file ảnh thành ba...
Hoàng Duy viết gần 2 năm trước
8 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
1 bài viết.
0 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!