Introduction
Knockout js (shortly called KO) is a very popular JavaScript library and increasing its popularity day by day. This library helps to create rich/responsive/interactive web applications. It works directly with the web application's underlying data model. Using KO with any web application is very simple, clean, and straightforward, it is very powerful in the context of dynamic UI creation.
Background
JavaScript plays a very important role for developing today's modern web applications. Once upon a time, we just wrote a few JavaScript lines for data validation from the client side. But day by day, JavaScript plays a vital role not only in data validation but also for creating responsive, robust web UI. For that reason, day by day, a new JavaScript framework/library comes into the market. We as smart developers, must adopt them and try to use them with our applications.
Benefits
If we use KO with web applications, we can get the following benefits:
- Anytime we can connect UI elements with data model.
- Easily create complex dynamic data model.
- Automatically update UI when Data Model is changed, when UI is changed, then Data Model is changed automatically.
- Support event-driven programming model.
- Extend custom behavior very easily.
- All main-stream browsers are supported (Internet Explorer, FireFox, Crome, Safari)
KO with MVVM
What is MVVM? Well, the full form of MVVM is Model View ViewModel. It is an architectural design pattern originated by Microsoft and mainly created for WPF/Silverlight applications. But we can use this Web application too with ASP.NET.
MVVM is a specific implementation targeted at UI development platform which supports event driven programming for WPF/Silverlight. It respects the programming principle "Separation of Concern". It completely separates GUI Rendering logic from Application Logic (Data Logic/Business Logic).
KO built on that architectural design pattern. So if you want to understand KO properly, then you should know about MVVM first.
MVVM Overview
The following diagram will show the style of MVVM:
MVVM has 3 parts:
- Model
- View
- ViewModel
Model: Responsible for holding Application data. When user enter data with UI elements, that data will store to a Model. So we can think like it is the data part of the pattern.
View: Responsible for presenting model data to the user. User elements are the part of the View. It provides structure/Layout of the User Interface and presents to the user.
ViewModel: Connector of Model and View. Main responsibility is to establish communication with View and Model. It holds data and function. Functions manipulate that data and it reflect to the UI. When data is changed, UI is changed, when UI is changed data is changed. ViewModel will do it for us with the help of databinding concept.
MVVM Benefits
Main benefits of MVVM are listed below:
- Provide more flexibility of designer and developer works
- Thorough unit testing
- Provide flexibility to change user interface without having to re-factor other logic of the codebase
- Provide more re-usability of UI component
Include KO Library to Web Application
CDN (Content Delivery Network) reference of Knockout js is available at Microsoft CDN repository. There are two versions available. One for compact (minified) another for debug. It is recommended to use the minified version. The URLs are as given below:
- Minified version: http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js
- Debug version: http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.debug.js
If your application is ASP.NET WebForm, then you can download js file from the above locations and store it to your web folder/subfolder and reference it to your page/master page.
Hide Copy Code
<script src="~/Scripts/knockout-2.2.1.js" type="text/javascript"></script> CDN Reference: <script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js" type="text/javascript"></script>
If ASP.NET MVC application, you can take reference inside
@section
.@section scripts {
<script src="~/Scripts/knockout-2.2.1.js"></script>
}
CDN Reference:
@section scripts {
<script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js"></script>
}
Example 1
I will create a simple
Employee
information entry form. Form
will accept some basic employee related data from the user and submit that data to the MVC Action with the help of AJAX. In View part, I will use various HTML elements so that I can show how all elements are worked with KO.Implementation
Before I start showing code with KO, I will just mention one thing. In MVVM pattern, a personal choice is there. That is which come first, Model Or View. I am not going to discuss details on that. Just mention my personal preference and that is View. I always like to start View first, then for Model.
<form id="employeeForm" name="employeeForm" method="POST">
<div id="form-root">
<div>
<label class="form-label">First Name:</label>
<input type="text" id="txtFirstName"
name="txtFirstName" data-bind="value:Employee.FirstName" />
</div>
<div>
<label class="form-label">Last Name:</label>
<input type="text" id="txtLastName"
name="txtLastName" data-bind="value:Employee.LastName" />
</div>
<div>
<label class="form-label">Full Name:</label>
<input type="text" id="txtFullName" name="txtFullName"
data-bind="value:Employee.FullName" readonly="readonly" />
</div>
<div>
<label class="form-label">Date Of Birth:</label>
<input type="text" id="txtDateOfBirth"
name="dateOfBirth" data-bind="value:Employee.DateOfBirth" />
</div>
<div>
<label>Education:</label>
<input type="checkbox" value="graduation" id="chkGraduation"
name="chkGraduation" data-bind="checked:Employee.EducationList" />Graduation
<input type="checkbox" value="postGraduation"
id="chkPostGraduation" name="chkPostGraduation"
data-bind="checked:Employee.EducationList" />PostGraduation
</div>
<div>
<label>Gender:</label>
<input type="radio" id="rdoMale" name="gender"
value="0" data-bind="checked:Employee.Gender" />Male
<input type="radio" id="rdoFeMale" name="gender"
value="1" data-bind="checked:Employee.Gender" />FeMale
</div>
<div>
<label class="form-label">Department:</label>
<select id="ddlDepartment" name="ddlDepartment"
data-bind="options:$root.Employee.DepartmentList, optionsValue:'Id',
optionsText:'Name', value:Employee.DepartmentId">
</select>
</div>
<div>
<input type="button" id="btnSubmit"
value="Submit" data-bind = "click: submit" />
<input type="button" id="btnReset"
value="Reset" data-bind = "click: reset" />
</div>
</div>
</form>
View
First, I create a view. It looks like:
I bind all of view's html form element with my client side model with KO's data-bind attribute. Why I said Client Side Model? Because I have a Server Side Model too. Later, I talk about server side model. With Data-Bind attribute, KO connects UI element to the
Model
property. Now, explain UI elements connect to the Data Member of the Model one by one.TextBox Binding
<input type="text" id="txtFirstName"
name="txtFirstName" data-bind="value:Employee.FirstName" />
Html form element textbox binds with
FirstName
property of Employee
Model. value
is the KO key by which KO address textbox value property. But remember that value inside data-bind attribute is the KO's key not html native. When textbox value is updated, then FirstName
property of the Employee
model will be updated automatically and vice-versa. No custom code is needed for that.CheckBox Binding
<input type="checkbox" value="graduation"
id="chkGraduation" name="chkGraduation" data-bind="checked:Employee.EducationList" />
<input type="checkbox" value="postGraduation" id="chkPostGraduation"
name="chkPostGraduation" data-bind="checked:Employee.EducationList" />
HTML form element checkbox (input checkbox) binds with
EducationList string
array of Employee
Model. Here, both checkboxes bind with single model property. When user checked any/both checkbox, automatically the value of the checkboxes (1st checkbox value is "graduation
" 2nd is "PostGraduation
") will be added to the array. If un-checked, then the value will be removed from this array. In textbox
, we use KO's key value, in checkbox, here we use another KO's key named checked
. Again, checked in not any HTML native.Radio-Button Binding
<input type="radio" id="rdoMale" name="gender"
value="0" data-bind="checked:Employee.Gender" />
<input type="radio" id="rdoFeMale" name="gender"
value="1" data-bind="checked:Employee.Gender" />
There are two radio buttons and grouped it with
name
property. If radio-buttons name is the same, then they work like group. If one radio is selected, then automatically others will be de-selected and vice-versa. Both radio buttons bind with single Employee
Model property named Gender
(number type). When Male will select, then this property will contain 0
, if FeMale select, it will contain 1
. Same way when Gender contains 0, then rdoMale
radio will be selected, when Gender
contains 1, then rdoFemale
will be selected (checked).Drop-Down/ComboBox/Select Binding
<select id="ddlDepartment" name="ddlDepartment"
data-bind="options:$root.Employee.DepartmentList, optionsValue:'Id',
optionsText:'Name', value:Employee.DepartmentId">
</select>
Drop-down list binds with two
Model
properties. For its data source, it binds DepartmentList
array of object of Employee
Model. Object has 2 properties. Id
and Name
. Id
Binds with value and Name
binds with option
text. Means, drop-down will show all Names of the objects. Drop-down selected value binds with DepartmentId
property. When user selects any item from drop-down, the value of the item will store in DepartmentId
. In the same way, if any value is assigned to the DepartmentId
from the ViewModel
, then drop-down will show that item which matches the value to the user.Button/Command Button Binding
<input type="button" id="btnSubmit"
value="Submit" data-bind = "click: submit" />
Button binds with
submit
method of the ViewModel
with click
event. When user clicks Submit button, then submit
method of the ViewModel
will fire.
There are so many keywords that are used in data-binding in KO. All are KO's own keyword. It should be clear that those are not HTML native property.
Model
function Employee() {
var that = this;
that.FirstName = ko.observable("");
that.LastName = ko.observable("");
that.FullName = ko.computed(function () {
return that.FirstName() + " " + that.LastName();
});
that.DateOfBirth = ko.observable("");
that.EducationList = ko.observableArray();
that.Gender = ko.observable("0");
that.DepartmentList = ko.observableArray([{ Id: '0', Name: "CSE" }, { Id: '1', Name: "MBA" }]);
that.DepartmentId = ko.observable("1");
}
Employee
is my Model
. We can call it data model. In its every property, I initialize with ko.observable
method. What does the actual ko.observable
method return? It returns a function, which notify UI element for update. So in property of Employee
like FirstName
, LastName
, etc., all are functions. If I want to assign any value to the FirstName
, then code should be that.FirstName("Mr.")
, if read that property, then it should be that.FirstName()
computed is another ko
library function which is responsible for dependency tracking. Means, inside that function, I use FirstName
and LastName
property. When FirstName
or LastName
property is updated, then fullName
is automatically updated.
KO has another important thing that is Observable Array. It is nothing but a collection of observable method. Each and every item of array will be observable. If I want to add value to that array, then I need to use:
that.DepartmentList.push({Id:3, Name:"BBA"});
If I want to remove last item of the array, then:
that.DepartmentList.pop();
It might be confused that
push
and pop
are the JavaScript array native method. But here, use push
and pop
with observable array are not the native JavaScript method. It is actually KO's own implemented method. KO implements 6 methods for observable array.push
pup
unshift
shift
reverse
sort
So be clear on that and do not confuse JavaScript array object's method with that.
ViewModel
function EmployeeVM() {
var that = this;
that.Employee = new Employee();
that.reset = function () {
that.Employee.FirstName("");
that.Employee.LastName("");
that.Employee.DateOfBirth("");
};
that.submit = function () {
var json1 = ko.toJSON(that.Employee);
$.ajax({
url: '/Employee/SaveEmployee',
type: 'POST',
dataType: 'json',
data: json1,
contentType: 'application/json; charset=utf-8',
success: function (data) {
var message = data.Message;
}
});
};
};
that.Employee
holds the reference of Employee
Model. Here, I implement 2 behaviors:reset
:reset
method will re-initialize the value of the model.submit
: convertEmployee
model data to jsonstring
and send it to the server'sSaveEmployee
action method with the help of JQuery Ajax method.
Controller Action
[HttpPost]
public ActionResult SaveEmployee(Employee emp)
{
//EmployeeModel save code here
return View("EmployeeInfo");
}
This is the
Employee
controller action method. submit
method of client side view model sends data to this action
method. Actually, this action
receives data from client view and process that data, then save that data to the database or throw exception if any invalid things happen.Server Side Model
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public Gender Gender { get; set; }
public IList<string> EducationList { get; set; }
public int DepartmentId { get; set; }
}
public enum Gender
{
Male =0,
FeMale = 1
}
This is my server side
Employee
Model. Why is server side Model required? We see that from my client part we create an Employee ViewModel
. But it should be kept in mind that in client side, we actually do not write any business logic. My application processes the business logic. Then where do I write that? For server side business logic handle, we create server side Model and we send that model to various layers like domain service layer/Repository layer, etc.Example 2
I will create an
Employee
phone entry form. Employee
could have multiple phone numbers with country code. First time view will show to add a single phone number entry option. If user wants, than if he presses Add button, then it will create another phone number entry option dynamically to the user interface. User can also remove any phone number entry option with press Remove button. So we understand that there dynamic UI rendering feature will come. Let's see how knockout will help in this area.Implementation
View
First, I will complete view creation part. View looks like follows:
<form id="employeeForm" name="employeeForm" method="POST">
<script id="PhoneTemplate" type="text/html">
<div>
<span>
<label>Country Code:</label>
<input type="text" id="txtCountryCode" data-bind="value:CountryCode" />
</span>
<span>
<label>Phone Number:</label>
<input type="text" id="txtPhoneNumber" data-bind="value:PhoneNumber" />
</span>
<input type="button" id="btnRemove"
value="Remove" data-bind="click: $parent.remove" />
</div>
</script>
<div>
<h2>Employee Phone Number</h2>
<div data-bind="template:{name:'PhoneTemplate', foreach:PhoneList}, visible:isVisible">
</div>
<div>
<input type="button" id="btnAdd"
value="Add Another" data-bind="click: add" />
</div>
</div>
</form>
KO supports data template. It develops template engine for rendering template. This works with jQuery template too. I create a template named
PhoneTemplate
.<script id="PhoneTemplate" type="text/html"> </script>
I declare
type="text/html"
with script
tag. That means I notify browser that it is text data and you will not execute it. Inside that script
block, I create a div
. Inside that div
, I create 2 html textboxes, 2 labels and 1 button.
Outside the script block, I create another
div
and bind that div
to my JavaScript viewmodel
's array member PhoneList
. KO has its own syntax to bind with template. Here, inside the data-bind attribute, I declare:<div data-bind="template:{name:'PhoneTemplate', foreach:PhoneList}">
</div>
It binds with a template named '
Phonetemplate
' and uses foreach
statement. That means render this referenced template equal to number of items found in PhoneList
. If I dynamically add item to the PhoneList
, then template will render again with new UI and same way if I remove item from this array automatically, UI element which renders when item is added will be removed.Model
function Phone(code, number) {
var self = this;
self.CountryCode = ko.observable(code);
self.PhoneNumber = ko.observable(number);
}
Client side JavaScript Model has 2 properties:
CountryCode
PhoneNumber
Each property initializes with observable method so that it can work with KO. These two properties are used inside template.
ViewModel
function PhoneViewModel() {
var self = this;
self.PhoneList = ko.observableArray([new Phone("00", "00-00-00")]);
self.remove = function () {
self.PhoneList.remove(this);
};
self.add = function () {
self.PhoneList.push(new Phone("01", "00-00-01"));
};
}
PhoneViewModel
establishes connection between Phone View and Phone Model with the help of data-bind attribute. This ViewModel
contains 2 methods:add
remove
When user presses 'Add Another" button, it will create a new Phone Model with default value and add it to the
PhoneList
array. When PhoneList
array is added new item, automatically UI creates a new div
based on designed template.
When user presses "Remove" button, it will call
remove
method of ViewModel
and remove current item from the PhoneList
array. When an item is removed from array, then automatically UI is re-populated.View & ViewModel Relation
When you start implementing MVVM to your web application, then one issue you might face is how many
ViewModel
s are needed for a view for vice-versa. Means what type of relation is needed for view and model/ViewModel.
There are few opinions that exist in the market:
- One-View to One-ViewModel
- Many-Views to many-ViewModels
- One-View to many-ViewModels
There is no hard and fixed rules here. Many people including me support Per View will have single ViewModel and per ViewModel will have single Model. It will helps us to keep code simpler and more manageable.
If there are re-usability concerns, then I think you can use many views with a single
ViewModel
.
No comments:
Post a Comment