Overview
In this article we are trying to learn how to create a Single Page Application
using a JavaScript library (knockout.js) and Sammy.js (Client-side Navigation
Library). We will use the MVC 4 Basic template for this demo.
What is Single Page Application?
A Single Page Application or Single Page Interface is a web application or web
site that will have only one page. The page does not re-load at any point in the
navigation process. For server communication we use Ajax calls and update the
sections appropriately. Client-side navigation is done using Hash based
techniques.
Knockout.js
JavaScript implementation of a Model-View-ViewModel pattern. It helps in:
- Binding ViewModel with View using
Declarative Binding.
- Automatic UI refresh when changed in the
ViewModel properties.
Sammy.js
Sammy.js is a JavaScript library to help us implement client-side navigation.
Getting Started
Let us create the demo application and learn step-by-step. Here we are trying to
create a single page where the user can browse the list of products, edit the
existing product and delete the product. We will create a single page to perform
all these operations.
1. Open Visual Studio 2012 and create a new MVC 4 project and select the Basic
template as below.
2. Now we have the solution ready.
3. Now let's create a new controller class by right-clicking the Controller
folder. Select "EmptyMVCController" in the Add Controller dialog.
Now we have our controller ready with an Index action.
Now go to "App_Start" -> "RouteConfig.cs" and update the default route so that
when we run the application our product controller's Index action is called.
4. Now let's create the View ("Index.chtml"). Go to "ProductController",
right-click on the Index action and click on "Add View".
We are ready with our view that has been created under the "Views" -> "Product"
folder.
Now we are ready to run our application. We should be able to see the Index
page.
5. Now let's start creating the ViewModel. We will name it "ProductViewModel".
Create a new folder "Application" under the "Script" folder. Add a new
JavaScript file in the Application folder. We will name the JavaScript file "Product.js".
Now we will create the Product model class in JavaScript. Here we are using the
object oriented JavaScript syntax. Also make all the properties observable, this
will help us to leverage the declarative binding concept of Knockout.
No we will create our ViewModel and name it "ProductViewModel" in the same "Product.js"
file.
To understand the concept of declarative binding and observable properties, we
will initially keep only one property in the view model.
We will name it "currentProduct", this property will hold the current product
information. For now create a new Product object, initialize it with some dummy
values and assign it to the "currentProduct" property.
Now our Product.js will look like as below.
6. Now the next step is to bind our ViewModel ("ProductViewModel") with the view
which is "Index.chtml".
7. Now we need to add reference of Knockout.js. Go to "App_Start" -> "BundleConfig.cs".
Add the new script bundle as below.
Go to "_Layout.chtml" and update the code as below.
Now go to "Index.chtml" and the changes in the following screen shot. Here we
are adding the reference of "Product.js" and also binding the "ProductViewModel"
to the view using the "applyBindings" function of the Knockout library.
Now we will see how to bind the ViewModel properties to UI controls. Just to
explain the concept I will use one TextBox and one label and bind the
productName to both controls.
Add the following code in the "Index.chtml" file.
You can see that we have used the data-bind attribute for binding. First we
bound the "<div>" with the "currentProduct" property of ProductViewModel using
WITH binding so that we can access the properties of the Product object that has
been assigned to currentProduct.
Now run the application and you should see the following output.
Because of the use of declarative binding we can see the product name shown on
the UI. Now since we have bound the same property to both text and label, if we
change the text box value then it should automatically be reflected in the
label.
Now try to change the text box value and tab off. You should see the following
output.
This proves that we have implemented MVVM correctly.
Now we will return to our actual UI that we had planned to show, the list of
products and the user can edit and delete the product.
Remove the following code from "Index.chtml".
Now we will update the ProductViewModel as per the requirements. We need a list
of products. So add the products property into ProductViewModel to hold the list
of Product objects.
Here we are taking observableArray to hold the list of products so that it
automatically refreshes the UI when we add/remove items to this list.
Now since this a Single Page Application we will create two sections on the same
page, one section to show the list of products and another section to display
the product information in edit mode.
Let's have two DIV sections as below.
I have made the Product List section visible by default.
Now let's add some UI code to populate the list of products. Please use the
following screen shot and update the Index.chtml.
Here we used the foreach binding on the "<tbody>" tag and it will automatically
iterate through the observableArray of products and create "<tr>" tags based on
the product count.
Now run the application and you should see the following output.
Now we will render two action links for each product to edit and delete that
product.
Make the highlighted changes in Index.chtml.
Here we are using click binding and calling the "editProduct" and "removeProduct"
functions of ProductViewModel. Now let's create the editProduct and
removeProduct functions.
When the user clicks on the Edit link, we should present an interface where
he/she edits the currently selected product information. The Product Code field
should be disabled. Let's create the UI for editing the product.
Here we are binding with the currentProduct property of ViewModel. Let's create
this property. By default I initialized the currentProduct with blank values.
Now when the user clicks on the Edit link we need to redirect to the Edit
Product section. In other words we need to navigate from one section to another
in the same page and to do this client-side navigation we will use a small web
framework from Sammy that uses the hash based navigation.
You can get the JavaScript library from
http://sammyjs.org/ . Save the file in the Script folder and name it
"Sammy.js".
Also refer to it in "_Layout.chtml".
Add the new bundle for Sammy.js.
_Layout.chtml
First we need to configure all the routes in our application. Add the
highlighted code to ProductViewModel.
Here we are calling one function from Sammy.js to configure our route. The
route, "#:product/:productCode", is mapped to "/product/[productCode]".
When we change the Location.Hash, Sammy will capture the path assigned to the
hash and match with the configured routes, if it matches it will invoke the
associated code block.
Now let's add the editProduct and removeProduct functions to ProductViewModel.
Here you can see that in the editProduct function we are just setting the Hash.
Because of the Knockout binding mechanism we get the product object that is
bound to that row. In the Sammy function the code block will be executed
automatically for a specific route. In our case we are just hiding the product
list div and showing the edit product div.
Now when you run the application, you should be able to see the following
output.
Now when you click on "Edit", you will see the Edit Product section visible,
that we are doing in the Sammy function.
Please observe the URL in the address bar in the following screen shot.
We still don't see product information populated in the fields, because we did
not set the currentProduct yet. Let's add one more function "getProduct" that
will be called from the anonymous function associated with the route "/product/[ProductCode]"
. In the getProduct function we are just traversing the observableArray and
matching the selected product based on the product code. Also setting the
currentProduct property.
Update the Sammy function as below.
Now run the application, you should be able to see the output as below.
Now try to change the product code directly in the address bar and see the
behavior.
When we change the URL in the address bar, Sammy captures the URL and it matches
with the route we configured and the code block associated to that route is
executed.
Conclusion
So in this article I tried to explain how to leverage the power of Knockout
(JavaScript Implementation of the MVVM pattern) and the small web framework to
do client-side navigation to create a Single Page Application.