Application using KnockoutJS framework
PUBLISHED
Overview
This article demonstrates the use of "Knockout.js", which is a thrid party JavaScript library that helps to create rich, responsive display and editor user interfaces with a clean underlying data model. If there are any sections of UI that update dynamically (e.g., changing depending on the user’s actions or when an external data source changes), knockout can help in implementing it more simply and maintainably. The documentation and source can be found here.
Some of it's key features are :
- Elegant dependency tracking - automatically updates the right parts of your UI whenever your data model changes.
- Declarative bindings - a simple and obvious way to connect parts of your UI to your data model. You can construct a complex dynamic UIs easily using arbitrarily nested binding contexts.
- Trivially extensible - implement custom behaviors as new declarative bindings for easy reuse in just a few lines of code.
KnockoutJS follows Model-View-ViewModel (MVVM) style of development for websites and web applications. MVVM is a style of development where different object classes are designed to separate user-interface logic from business functions for testability purposes.
- The first principle of a MVVM-style application is defining the business Models. A Model is an object that usually most directly represents a real-world object in the business system you are working in. It contains properties and functions that have business-like names and reactions.
- The second principle of a MVVM-style application is the View. A View is the HTML markup that describes the layout, elements (buttons, textboxes), colors, and other visual pieces of a portion of the user interface. It has no logic or code embedded in it, and it is completely declarative.
- The third part of a MVVM-style application is the ViewModel. The ViewModel provides the connection between the View and the Model.
- The final principle of MVVM is the concept of Binding. Binding is the idea of connecting the properties and events of user interface elements (such as HTML elements) to functions and properties of an object such as a ViewModel. Bindings are often declared in the View markup.
KnockoutJS Application
This application shows a way to edit a nested data structure to add, delete contacts and save them to JSON format.
View Part of Application
Add the below line referencing the JavaScript file using a <script> tag.
<script type="text/javascript" src="./js/knockout-3.0.0.debug.js"></script>
The "View" consists of a table with input fields to enter contact details and buttons to delete each contact and phone number. Outside the table are two more buttons to add more contacts and save them to JSON format and paste it in the textarea at the end.
<table class='contactsEditor' align="center"> <tr> <th>First name</th> <th>Last name</th> <th>Phone numbers</th> </tr> <tbody data-bind="foreach: contacts"> <tr> <td class="contactsEditor1" align="center" valign="bottom"> <input data-bind='value: firstName' size="7" class="input" /> <a data-role="button" data-inline="true" href='#' data-bind='click: $root.removeContact' data-icon="delete"></a> </td> <td class="contactsEditor1" align="center"> <input data-bind='value: lastName' size="7" class="input" /> </td> <td class="contactsEditor1"> <table> <tbody data-bind="foreach: phones"> <tr> <td align="center"><input data-bind='value: number' size="8" class="input" /></td> <td align="center"> <a data-bind='click: $root.removePhone' data-role="button" data-inline="true" data-icon="delete"></a> </td> </tr> </tbody> </table> <center> <a href='#' data-role="button" data-inline="true" data-bind='click: $root.addPhone'>Add number</a> </center> </td> </tr> </tbody> </table> <center> <button data-bind='click: addContact' data-inline="true">Add a contact</button> <button data-bind='click: save, enable: contacts().length > 0' data-inline="true">Save to JSON</button> <textarea data-bind='value: lastSavedJson' rows='15' cols='60'> </textarea> </center>
In the above code "data-bind" attributes are how Knockout helps to declaratively associate viewmodel properties with DOM elements.
- foreach binding is used to transform the JavaScript array of contacts into a TABLE. Whenever the array changes, the UI changes to match so that there is no requirement to figure out how to inject new <tr> or where to inject them. The rest of the UI stays in sync.
- value binding, along with some regular HTML <input> controls, makes the data editable.
- click binding is used to associate clicks with the viewmodel function that is added.
- enable binding is used to disable or enable the button depending upon the condition given.
View Model Part of Application
Knockout has a concept of observables - these are properties that automatically will issue notifications whenever value of input field changes. All the contact names, numbers are kept in an array. Now using "ko.observableArray" bind the it to "View" so that it detect and respond to any changes of this array made by user. This is useful in many scenarios when displaying or editing multiple values and need repeated sections of UI to appear and disappear as items are added and removed.
var initialData = [ { firstName: "Danny", lastName: "LaRusso", phones: [ { number: "9160876557" }, { number: "9989779179"}] }, { firstName: "Sensei", lastName: "Miyagi", phones: [ { number: "9959392729" }, { number: "8985627183"}] } ]; var ContactsModel = function(contacts) { var self = this; self.contacts = ko.observableArray(ko.utils.arrayMap(contacts, function(contact) { return { firstName: contact.firstName, lastName: contact.lastName, phones: ko.observableArray(contact.phones) }; })); self.addContact = function() { self.contacts.push({ firstName: "", lastName: "", phones: ko.observableArray() }); }; self.removeContact = function(contact) { self.contacts.remove(contact); }; self.addPhone = function(contact) { contact.phones.push({ number: "" }); }; self.removePhone = function(phone) { $.each(self.contacts(), function() { this.phones.remove(phone) }) }; self.save = function() { self.lastSavedJson(JSON.stringify(ko.toJS(self.contacts), null, 2)); var string=JSON.stringify(ko.toJS(self.contacts), null, 2); console.log(string); tizen.filesystem.resolve( 'documents', function(dir){ documentsDir = dir; dir.listFiles(onSuccessFilesList,onerrorFiles); }, function(e){ console.log("Error" + e.message); }, "rw" ); function onSuccessFilesList(files) { var fileName = "Contacts"+ (files.length + 1)+".json"; var testFile = documentsDir.createFile(fileName); if (testFile != null) { testFile.openStream( "w", function(fs){ fs.write(string); fs.close(); alert("Contacts are saved to "+fileName+" inside Documents folder"); }, function(e){ console.log("Error " + e.message); }, "UTF-8" ); } } function onerrorFiles( err){ console.log("--------Error"+ err ); } }; self.lastSavedJson = ko.observable("") }; ko.applyBindings(new ContactsModel(initialData));
addContact, removeContact, addPhone, removePhone, save, lastSavedJson are the functions binded to the UI part.
Each time the "Add Number" button is clicked, this will invoke addPhone() on the view model, which in turn changes the view model state, which causes the UI to update. In similar way rest of the functions work. The JSON data created is binded with the textarea and pasted into it and saved into a file in "Documents" folder using Filesytem API. Since data-bind attribute isn't native to HTML, the browser doesn't know what it means, so Knockout is to be activated to make it take effect. "ko.applyBindings" is used to activate knockoutJS. This takes view model object as parameter which is used with the declarative bindings and activates it.