Introduction
This tutorial is about how to create a Single Page Application using Knockout JS JavaScript framework and ASP.NET MVC 4 and perform database operations using Web API.
Content list of this article
- Why Single Page Application?
- What is Knockout JS?
- Patterns used by Knockout JS.
- Description of MVVM pattern.
- Description of Observable pattern.
- Demo application.
Why Single Page Application?
A single-page application (SPA) is a web application or web site that uses only one page to provide a more fluid user experience like a desktop application and it reduces the traffic to the server since it reduce the round-trips to the server.
What is Knockout?
Knockout is a JavaScript library to develop Single Page Application and provides the facility of declarative data binding and automatic UI update when the underlying data Model changes. Knockout use Observable pattern to update the UI when the Model state changes.
Patterns used by Knockout
Knockout uses the MVVM design pattern to separate the View from application data Model and use declarative data binding to isolates the data Model. It also use Observer pattern to update the View automatically when Model state is changed and vice versa.
Description of MVVM pattern
The MVVM (Model-View- View Model) is a UI design pattern developed by Microsoft and derived from the MVC (Model-View-Controller) pattern.
Model: Like the other component of the MV* families Model contains the application data and UI shows this data.
For example a Car Model holds the information of its Color, Wheel Number, Doors Number, Engine power etc.
View: Like the other component of MV* family View is the User Interface and shows the Model state which is exposed by View Model for the View. Unlike other patterns View of MVVM use declarative data binding. It also sends command to the View Model when user interacts and also updates automatically when View Model property value is changed.
View Model: The View Model (VM) part of MVVM is a special component which exposes data from the Model to show in View. Basically View Model exposes some properties and View is bound with these properties to get value.
Basically View Model works as a mediator between the View and Model. In real work we can consider a salesman is selling some products to a customer. The following picture shows the scenario.
In the above picture product is the Model and salesman is the View Model and customer is the View. To get some product, customer needs to go to salesman and ask for products. Here salesman is acting as a mediator between custom and project.
Description of Observable pattern
Observable pattern define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
In Observer pattern there is a Subject and some observer objects, which observe the Subject and when the state of the Subject changes then all the observer of that Subject is notified by the Subject.
For details about Observable pattern please go to the link http://www.dofactory.com/Patterns/PatternObserver.aspx
Demo application
Demo Summary: This demo application shows the list of Countries and Players of FIFA World Cup and allowed user to perform CRUD database operations. The frameworks used here are Knockout JS and Asp.Net MVC 4 and Web API.
The basic output of the project is shown below.
Step-1: First create a database with two tables Country and Player and set one -to- many relationships between them. The following image shows the tables structure.
Step-2: Now create an ASP.NET MVC 4 web application project using Single Page Application template. The following image shows the solution explorer of this demo project.
In the above solution the Controllers folder shows two Controllers, HomeController is the default Controller and CountryController is the custom ApiController. The Views folder shows three Views Index, Country and Player. Index View contains the other two Views. Here I have used Entity Framework as a Data Layer. The Scripts folder contains necessary JavaScript libraries and two customs JavaScript files, fifa.model contains Model objects and fifa.viewmodel contains View Model objects.
Step-3: Now let’s create the following Views.
The content of the index.cshtml is
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>@ViewBag.Title - My ASP.NET MVC Application</title>
<link href="~/favicon.ico" rel="shortcut icon" type="image/x-icon" />
<meta name="viewport" content="width=device-width" />
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
@Styles.Render("~/Content/themes/base/css")
</head>
<body>
<div id="header">
<h3>2014 FIFA World Cup Brazil</h3>
</div>
<div id="page">
<div id="menu">
<ul data-bind="foreach: menuitems">
<li>
<span data-bind="text: name, click: $root.menuClick"> </span>
</li>
</ul>
</div>
<div id="content">
</div>
</div>
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryui")
@Scripts.Render("~/bundles/knockout")
@Scripts.Render("~/bundles/jqueryval")
@Scripts.Render("~/bundles/fifa")
</body>
</html>
This is the main page of the application. When user send a request to server for the first time then sever render the content of the index page. This page uses Razor syntax for Script binding. Here div with id name ‘menu’ contains the list of the Menu items and data is bound declaratively using Knockout JS. And div with id name ‘content’ are used to load the child Views.
The child View page with the name Country.cshtml contains only html elements without body and html elements. The content of this page is loaded using Ajax call. The content of the Country View is shown below.
<div id="banner">Country List</div>
<div>
<input data-bind="value: NewCountry.name" />
<button data-bind="click: AddCountry">Add Country</button>
</div>
<table>
<thead>
<tr><th>Name</th><th></th></tr>
</thead>
<tbody data-bind="foreach: countries">
<tr>
<td>
<span data-bind="text: name, visible: !isEdit()"></span>
<input type="hidden" data-bind="value: id"/>
<input data-bind="value: name, visible: isEdit" />
</td>
<td>
<div data-bind="visible: !isEdit()">
<button data-bind="click: $root.EditCountry">Edit</button>
<button data-bind="click: $root.DeleteCountry">Delete</button>
</div>
<div data-bind="visible: isEdit">
<button data-bind="click: $root.UpdateCountry">Update</button>
<button data-bind="click: $root.CancelCountry">Cancel</button>
</div>
</td>
</tr>
</tbody>
</table>
Here data is loaded using Knockout JS data binding syntax. The top div contains a textbox and a button for adding new Country in the list. You know data-bind attribute is used to bind data using Knockout JS. And textbox value is mapped with the View Model property NewCountry.name. Here NewCountry is a Model object contained by View Model. And the button Click event is bound with the function AddConuntry of the underlying View Model as a callback function.
The html content of the View Player.cshtml shows below.
<div id="banner">Player List</div>
<ul data-bind="foreach: countries">
<li>
<span data-bind="text: name"></span>
<ul data-bind="foreach: players">
<li>
<span data-bind="text: name"></span>
</li>
</ul>
</li>
</ul>
Here lists are mapped with the View Model’s collections countries and players.
Step-4: Now creates two JavaScript Objects as a Model.
function country(id, name) {
return {
id: ko.observable(id),
name: ko.observable(name),
players: ko.observableArray([]),
isEdit: ko.observable(false)
};
}
function player(id, name) {
return {
id: ko.observable(id),
name: ko.observable(name)
};
}
Step-5: Now let’s create the applications View Model.
function MenuViewModel() {
var self = this;
self.menuitems = ko.observableArray([
{ name: 'Country List' },
{ name: 'Player List' }
]);
self.menuClick = function (data,event) {
if (data.name == "Player List") {
$('#content').load("/Home/Player", function () {
ko.applyBindings(countryViewModel, $('#content')[0]);
});
}
else if (data.name == "Country List") {
$('#content').load("/Home/Country", function () {
ko.applyBindings(countryViewModel, $('#content')[0]);
});
}
};
}
This View Model is use to bind menu list. Here menuitems keeps the reference of an observable collection. And menuClick function is a callback function is used to load the content on Menu item click.
The content of the CountryViewModel are shows below.
function CountryViewModel() {
var self = this;
var saveState = {};
self.NewCountry = {
id: ko.observable(0),
name: ko.observable(''),
isEdit: ko.observable(false)
};
self.countries = ko.observableArray([]);
self.GetAllCountry = function () {
$.getJSON("/api/Country", function (data) {
var jsonData = eval(data.content);
for (var i = 0; i < jsonData.length; i++) {
var countryObj = new country(jsonData[i].id, jsonData[i].name);
$.each(jsonData[i].players, function (index, pl) {
countryObj.players.push(new player(pl.id, pl.name));
});
self.countries.push(countryObj);
}
})
.done(function () {
console.log("second success");
})
.fail(function (jqxhr, textStatus, error) {
console.log("Request Failed: " + error);
});
};
self.AddCountry = function (model, event) {
$.post("/api/Country", model.NewCountry, function (data) {
model.NewCountry.id(data.id);
model.countries.push(model.NewCountry);
});
};
self.EditCountry = function (model, event) {
saveState.name = model.name();
model.isEdit(true);
};
self.UpdateCountry = function (model, event) {
$.ajax({
url: "api/Country/" + model.id(),
type: "PUT",
dataType: "json",
contentType: "application/json",
data: ko.toJSON(model),
success: function (data) {
model.isEdit(false);
},
error: function (err) {
alert('Error');
}
});
};
self.CancelCountry = function (model, event) {
model.name(saveState.name);
model.isEdit(false);
};
self.DeleteCountry = function (model, event) {
$.ajax({
url: "api/Country/" + model.id(),
type: "DELETE",
dataType: "json",
contentType: "application/json",
success: function (data) {
self.countries.remove(model);
},
error: function (err) {
alert('Error');
}
});
};
}
The NewCountry object is bound with the Add block, countries collection contains the country list, GetAllCountry function is used to extract the list of countries from database. Inside this function, jQuery getJSON function is used to send Ajax request to the server. The requested url is "/api/Country" which routes to a web ApiController.
Here AddCountry function is used to add a new country in the database, UpdateCountry function is used to update a country in the database and DeleteCountry function is used to delete a country from database.
Step-6: Now let’s write the Server side Web API codes.
Server side C# code to get the Country list is
public ActionResult GetCountries()
{
var countryList = db.Countries.AsEnumerable();
var collection = db.Countries.Select(x => new
{
id = x.Id,
name = x.Name,
players = x.Players.Select(player => new
{
id = player.Id,
name = player.Name
})
});
var json = new JavaScriptSerializer().Serialize(collection);
return new ContentResult { Content = json, ContentType = "application/json" };
}
Server side C# code to save a Country is
public HttpResponseMessage PostCountry(Country country)
{
if (ModelState.IsValid)
{
db.Countries.Add(country);
db.SaveChanges();
HttpResponseMessage response = Request.CreateResponse(
HttpStatusCode.Created, country);
response.Headers.Location = new Uri(
Url.Link("DefaultApi", new { id = country.Id }));
return response;
}
else
{
return Request.CreateErrorResponse(
HttpStatusCode.BadRequest, ModelState);
}
}
Server side C# code to update the state of a Country is
public HttpResponseMessage PutCountry(int id, Country countrypara)
{
Country country = db.Countries.Find(id);
country.Name = countrypara.Name;
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
return Request.CreateErrorResponse(
HttpStatusCode.NotFound, ex);
}
return Request.CreateResponse(HttpStatusCode.OK);
}
Server side C# code to delete a Country is
public HttpResponseMessage DeleteCountry(int id)
{
Country country = db.Countries.Find(id);
if (country == null)
{
return Request.CreateResponse(
HttpStatusCode.NotFound);
}
db.Countries.Remove(country);
try
{
db.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
return Request.CreateErrorResponse(
HttpStatusCode.NotFound, ex);
}
return Request.CreateResponse(
HttpStatusCode.OK, country);
}
Step-7: Finally to bind the View Model with the page we have to write the following lines of code.
var countryViewModel = new CountryViewModel();
countryViewModel.GetAllCountry();
ko.applyBindings(new MenuViewModel(), document.getElementById('menu'));
$('#content').load("/Home/Country", function(){
ko.applyBindings(countryViewModel, $('#content')[0]);
});
This code segment creates an object of CountryViewModel and calls the GetAllCountry function to extract the country and player list from database. And finally extracts the html from "/Home/Country" url and place it in the div with id name “content” and after loading is completed, countryViewModel object is bound with the placeholder to load data.
Conclusion
This is all about how to make a Single Page Application using Knockout JS. So in short we can say Knockout has some strong feature to build a reach JavaScript site. The most useful thing is Knockout uses MVVM pattern. So data are bound using declarative way and observable allows automatic UI update when View Model property value is changed.
To run the Demo please first build the project, it will download the necessary packages from internet listed in the file packages.config and will save the files in the packages folder. I have removed this folder because of large memory size.