May
17
2012

Single page application in asp.net using Sammy.js

Introduction

Single page applications (SPA) are not new but recently more and more web sites (and some web applications too) can be seen adopting this model of web development. We now also have SPA feature available in MVC4 which uses Knockout and Upshot libraries combined with support on server side classes.

Out of many options to create SPA functionality, Sammy.js is one of the most popular. It can also be used with client side templates for html rendering. In this short article I will demonstrate using Sammy.js to create simple SPA.

Background

What is Single Page Application

Single Page Applications (SPA) are web sites/applications which are consists of single page and provide smooth user experience in contrast with traditional click and refresh web pages

Even in the world of high speed broadband internet connections, changing a web page when clicked on a link is not desirable and would certainly be appreciated if possible to avoid. SPA's provide just that.

SPA either gets all required data on initial load or fetches data from server asynchronously whenever required. But, this is different from regular asynchronous Ajax requests.

Article Body

Before going ahead, I would encourage you to read more about Sammy.js on official page.

Sammy works based on "routes". In Sammy, route is consists of a

  • Hash (#) based path
  • The HTTP verb (get, post, put, delete)
  • Callback function.

Whenever, URL is changed to

http://ServerName/pagename.aspx#/operation

then the route is called which contains "#/operation" as part of path name. By changing URL to a hash based path ("#/pathName") makes it possible to avoid page refresh at the same time handle the event. It also makes it possible to use browser back/forward button.

With Sammy there are many options available for using client side template. Client side template is efficient technique of rendering templated HTML tags using JavaScript coupled with JSON as a data source. Although, client side templates is not specific to Sammy and you can use Sammy without using client side templates. However, client templates are perfect candidate for SPA.

Creating Demo using Sammy

Required Files

To see Sammy.js in action, let us create a simple web form application project and add reference to jQuery file (not less than version 1.4) Then download Sammy.js file from official page here and add reference to this JavaScript file in the default.aspx.

In the course of creating this demo, we will also require Json2.js library for parsing string into Json data and vice versa. hence, let us also download Json2.js using Nuget command...

>> Install-Package json2

Alternatively, you can also download Json2.js from official GitHub page here

We will also require a template plugin to use client side template. You can use any of the template plugin from the big list of supported plugins here. For this demo, I am going to use Sammy.template.js. Download the file and add reference in the default.aspx page.

About Demo

The purpose of this demo is to build a simple application which will have multiple views and which will work without postback. Still responding to browser back/forward buttons.

The demo will display and update "Employee" data. Lets first create new Web Application project and add a new class file inside App_Code folder. Name it as "Employee.cs" and add below code in it

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace SammyTest
{
    public class Employees
    {
        private string _EmployeeFirstName;
        private string _EmployeeLastName;
        private string _EmployeeNumber;
        private string _EmployeeGrade;
        public string EmployeeFirstName { 
            get {return _EmployeeFirstName; }
            set { _EmployeeFirstName = value; } 
        }

        public string EmployeeLastName
        {
            get { return _EmployeeLastName; }
            set { _EmployeeLastName = value; }
        }

        public string EmployeeNumber
        {
            get { return _EmployeeNumber; }
            set { _EmployeeNumber = value; }
        }

        public string EmployeeGrade
        {
            get { return _EmployeeGrade; }
            set { _EmployeeGrade = value; }
        }

        public Employees(string EmployeeFirstName, string EmployeeLastName, string EmployeeNumber, string EmployeeGrade)
        {
            _EmployeeFirstName = EmployeeFirstName;
            _EmployeeLastName = EmployeeLastName;
            _EmployeeNumber = EmployeeNumber;
            _EmployeeGrade = EmployeeGrade;
        }

        public Employees()
        {
        }
    }
}

We have created few attributes in the class coupled with few properties to get/set values from/to these attributes. There are also two constructors of the class; one of them will be used to assign values to the class attributes and another one is a plain empty constructor.

Now, go to default.aspx and start by adding reference to jQuery.js and Sammy.js. Since, Sammy.js uses jQuery, the jQuery file should be referenced first before Sammy.js. To run Sammy as soon as DOM is loaded, add classic $(document).ready event inside script tags

Sammy will run for only specific container available on the page. This way, we could deal with different sections of the web page in Sammy by creating different Sammy instances holding different containers as a Sammy element.

The Sammy function with specific container element is defined as

var app = $.sammy('#sammyDiv', function () {

});

Here, sammyDiv is id of container Div element available on the page.

To indicate Sammy to run the app, run method is called within document.ready

app.run('#/');

The basic skeleton of default.aspx looks like this

<%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
    CodeBehind="Default.aspx.cs" Inherits="SammyTest._Default" %>

<asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
 <script type="text/javascript" src="Scripts/jquery-1.7.1.min.js"></script>
 <script type="text/javascript" src="Scripts/sammy.js"></script>
 <script type="text/javascript" src="Scripts/Json2.js"></script>
 <script type="text/javascript" src="Scripts/sammy.template.js" charset="utf-8"></script>
</asp:Content>
<asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
    <h2>
        Sammy.Js Demo!
    </h2>
    <div id="sammyDiv" style="width:100%">
      
    </div>
    <script type="text/javascript">
        $(document).ready(function () { 
               
            var app = $.sammy('#sammyDiv', function () {

                });

            $(function () {
                app.run('#/');
            });

        });
    </script>

</asp:Content>

Sammy Routes

As explained before, In Sammy, Routes are consists of # based path, HTTP method and a callback function. The specific route is called when path in URL matches with path defined for that route.

Consider this example, when URL of application becomes

http://localhost:xxxxx/Default.aspx#/

then the route having path as "#/" will be called...

Route in Sammy is defined as,

this.get('#/', function (context) {

         });

The get indicates the HTTP method while first parameter of get indicates the # based route it will process. The second parameter of get holds the callback function that will be called when this route is called.

In Sammy, multiple routes are defined with respective path and callback functions which will be invoked whenever corresponding URL is called.

There is another important Sammy method called Around(). around is called before each route is executed. We can use around method to fetch the data from server asynchronously so that the updated data would be available in each routes callback.

To get data from server we could either use simple jQuery ajax. But instead of jQuery ajax, we can use Sammy's load method. Load method gets the data from server in asynch manner at the same time calls next action in sequence when load is complete (which is route's callback function).

The syntax for call to load method is like this

this.load('Default.aspx/GetEmployees', {cache: false})
                    .then(function (items) {
                        context.items = JSON.parse(items).d;
          });

As it is evident, the load method needs to be supplied with server side method which will return some data. Before going ahead, let us define a server side events to get and update employee data.

Define server side methods

For this demo I am not going to use any persistent data source/database. Instead, I will use a static List of type "Employee" (You may want to use some database instead)

Go back to Default.aspx.cs. In Page_Load event of page, create multiple Employee objects and add it into Static List of type Employee. The Static List will persist the Employee objects during all callbacks.

Now, define a WebMethod "GetEmployees" which will simply return list of Employee type. The WebMethod is decorated with response format as JSON. This will serialize the "Employee" List into JSON string while returning data through response.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.IO;
using System.Text;
using System.Web.UI.WebControls;
using System.Runtime.Serialization.Json;
using System.Web.Services;
using System.Web.Script.Services;

namespace SammyTest
{
    public partial class _Default : System.Web.UI.Page
    {
        static List<Employees> emps;
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                emps = new List<Employees>();
                Employees emp = new Employees("Kedar", "Kulkarni", "00001", "A");
                Employees emp1 = new Employees("ABC", "XYZ", "21211", "B");
                emps.Add(emp);
                emps.Add(emp1);
            }
        }

        [WebMethod, ScriptMethod (ResponseFormat = ResponseFormat.Json, UseHttpGet=true)]         
        public static List<Employees> GetEmployees()
        {
            return emps;
        }
   }
}

 

We now have a server side method which would be called asynchronously from Sammy load event.

Client side template

Since, we are going to use client side template for html rendering instead of using static html, we will first have to create template file. the syntax of template will vary based on your choice of templating engine. Mustache.js is very famous and widely used.

The client side template is used to populate the data in a specific format using predefined html template. Since, focus of this post is not about client side templating, I am not going in details about template system. There is long list of template types supported for Sammy. You should use respective template system's sammy specific plugin in order to use them in Sammy.

We already have added javascript reference to "sammy.template.js" plugin.

Now, create new folder in the application root and name it as "templates" then add a template file named "EmployeeItem.template" in this folder. The syntax of template while using Sammy.template plugin is as below (Note that, in case you are using some other template system then, the template syntax will change accordingly templating system)

The below template formats the employee data into tabular form.

<table width="750px" border="1" style="border-collapse:collapse;" >
  <tr>
    <td style="width:150px;text-align:center">
      <div class="item-EmployeeNumber">
        <%= item.EmployeeNumber %>
      </div>
    </td>
    <td style="width:150px;text-align:center">
      <div class="item-EmployeeFirstName">
        <%= item.EmployeeFirstName %>
      </div>
    </td>
    <td style="width:150px;text-align:center">
      <div class="item-EmployeeLastName">
        <%= item.EmployeeLastName %>
      </div>
    </td>
    <td style="width:150px;text-align:center">
      <div class="item-EmployeeGrade">
        <%= item.EmployeeGrade %>
      </div>
    </td>
    <td style="width:150px;text-align:center">
      <div>
        <a href="#/Employee/<%= item.EmployeeNumber %>">
          Edit
        </a>
      </div>
    </td>
  </tr>
  
</table>

Putting it all together

Let us make use of Around() method along with load() and a default get() route; to retrieve data from server and render data using specified template. 

<script type="text/javascript">
      $(document).ready(function () { 
               
            var app = $.sammy('#sammyDiv', function () {
                this.use('Template');
                this.use('Session');
                this.around(function (callback) {
                    var context = this;
                    this.load('Default.aspx/GetEmployees', {cache: false})
                    .then(function (items) {
                        context.items = JSON.parse(items).d;
                    })
                    .then(callback);
                });

                this.get('#/', function (context) {
                    context.app.swap('');
                    this.partial('templates/EmployeeTable.template')
                    $.each(this.items, function (i, item) {
                        context.render('templates/EmployeeItem.template', { id: i, item: item })
                        .appendTo(context.$element());
                    });
                });
            });

            $(function () {
                app.run('#/');
            });

     });
 </script>

The code line 'this.use("Template")' indicates to use Sammy.Template plugin. In case you are using Mustache or some other template system, then it would change to something like 'this.use("Mustache")' etc.

In above code, the "EmployeeTable.template" is used to display header of the table. The template file contains following tags.

<div class="item">
<table width="750px" border="1" style="border-collapse:collapse;background-color:#336699;color:white">
	<thead>
	 <tr>
		<th style="width:150px;">
			Employee Number</th>
     <th style="width:150px;">
			Employee First Name
		</th>
     <th style="width:150px;">
			Employee Last Name
		</th>
     <th style="width:150px;">
			Employee Grade
		</th>
     <th style="width:150px;">
       Edit Record
     </th>     
	  </tr>
	</thead>
	<tbody>

	</tbody>
</table>
</div>

Now, run the web application. On document load, Sammy application will start and call default path as "#/". This will change URL of current page to

http://localhost:xxxxx/Default.aspx#/

This will call a get path "this.get('#/', blah.."

Since, Around() function is called before every route, it will load the data from server in asynch mode and call the get() route as a callback function.

In the get() route, "context" already has a data available; which will then be used to interpolate the it using a template file. Finally, the data is rendered in the browser as below.

Edit/Delete the data

We already had formatted the template so as to display last column as "Edit" link. Naturally, we now want to let user click on Edit button and open that perticular record in edit mode. To achieve that, let us create another route which will handle the edit link.

this.get('#/Employee/:id', function (context) {
                    // define temp variable to store index of matching employee number
                    var empIndex;
                    // loop through all items in employee list and match employee number in the list and querystring
                    jQuery.each(this.items, function (i, obj) {
                        if (obj['EmployeeNumber'] == context.params['id'])
                            empIndex = i;
                    });
                    //get matching employee record and set it to context.item
                    this.item = this.items[empIndex];
                    if (!this.item) { return this.notFound(); }
                    context.app.swap('');
                    //use edit template which will use context.item to interpolate data
                    this.partial('templates/EmployeeEdit.template');
                });

This route will be called when the URL becomes,

http://localhost:xxxx/Default.aspx#/Employee/zzzz

If you notice the "EmployeeItem.Template" file which we used to render in the employee table, it already does this.

Now, create a "EmployeeEdit.Template" using following tags

<div style="width:100%">
  <br/>
  <a href="#/">Go Back</a>
  <br/>
  <br/>
  <form action="#/UpdateEmployee" method="post">
  <div style="width:300px">
    <div style="float:left;height:25px">
      Employee Number :
    </div>
    <div style="float:right;height:25px">
      <input type="text" name="txtEmpNumber" readonly="true" value=<%= item.EmployeeNumber %> />
    </div>
    <div style="float:left;height:25px">
      Employee First Name :
    </div>
    <div style="float:right;height:25px">
      <input type="text" name="txtEmpFirstName" value=<%= item.EmployeeFirstName %> />
    </div>
    <div style="float:left;height:25px">
      Employee Last Name :
    </div>
    <div style="float:right;height:25px">
      <input type="text" name="txtEmpLastName" value=<%= item.EmployeeLastName %> />
    </div>
    <div style="float:left;height:25px">
      Employee Grade :
    </div>
    <div style="float:right;height:25px">
      <input type="text" name="txtEmpGrade" value=<%= item.EmployeeGrade %> />
    </div>
  </div>
  <br/>
  <br/>
  <input type="submit" value="Update Employee Record"/>
  </form>
</div>

Again, run the application and click on the Edit link. The page will smoothly change to new view which let user edit the employee record.

The "Go back" link button simply changes the current URL to "#/" which invokes default get() route and displays Employee record in grid format.

The "EmployeeEdit.template" uses <form> tag which has action attribute set to string "#/UpdateEmployee".

This causes "#/UpdateEmployee" route to be called when clicked on a update button. To perform update operation, we can now create "#/UpdateEmployee" route.

this.post('#/UpdateEmployee', function (context) {
                    var empNumber = this.params['txtEmpNumber'];
                    var empFirstName = this.params['txtEmpFirstName'];
                    var empLastName = this.params['txtEmpLastName'];
                    var empGrade = this.params['txtEmpGrade'];
                    var Employee = new Object();
                    Employee.EmployeeFirstName = empFirstName;
                    Employee.EmployeeLastName = empLastName;
                    Employee.EmployeeNumber = empNumber;
                    Employee.EmployeeGrade = empGrade;

                    var empJSON = '{"EmployeeNumber":"' + empNumber + '","EmployeeFirstName":"' + empFirstName + '","EmployeeLastName":"' + empLastName + '","EmployeeGrade":"' + empGrade + '"}';
                    $.ajax({
                        type: "POST",
                        url: 'Default.aspx/UpdateEmployees',
                        data: "{'empUpdated':" + JSON.stringify(Employee) + "}",
                        contentType: 'application/json',
                        datatype: 'json',
                        success: function (items) {
                            var msg = items.d;
                            alert(msg);
                            jQuery.each(context.items, function (i, obj) {
                                if (obj['EmployeeNumber'] == empNumber) {
                                    context.items[i]["EmployeeFirstName"] = empFirstName;
                                    context.items[i]["EmployeeLastName"] = empLastName;
                                    context.items[i]["EmployeeGrade"] = empGrade;
                                }
                            });
                            context.redirect("#/");
                        },
                        error: function (xhr, ajaxOptions, thrownError) {
                            alert(thrownError);
                        }
                    });
                });

            });

In UpdateEmployee route, values entered in the form are read using this.Param[""] value collection. Then, the JSON string is created using all the entered values and posted to server side method using jQuery Ajax.

The server side method "UpdateEmployees" accepts parameter of type "Employee" as below

        [WebMethod]        
        public static string UpdateEmployees(Employees  empUpdated)
        {
            //var empObj = emps.Select(c => c.EmployeeNumber==empUpdated.EmployeeNumber).Select(c => { c.EmployeeNumber = empUpdated.EmployeeNumber; return c; }).ToList();
            //var empObj = emps.Single(c => c.EmployeeNumber==empUpdated.EmployeeNumber); 

            foreach (Employees e in emps.Where(e => e.EmployeeNumber == empUpdated.EmployeeNumber) )
            {     
                e.EmployeeFirstName = empUpdated.EmployeeFirstName;
                e.EmployeeLastName= empUpdated.EmployeeLastName;
                e.EmployeeGrade = empUpdated.EmployeeGrade; 
            } 
            return "Employee record updated successfully";
        }

The JSON string passed from jQuery Ajax is smoothly converted into "Employee" object and passed to UpdateEmployee method. Then, In the server side method we can actually read the values from object variable and update the List variable accordingly.

Finally, string value returned from Ajax call is displayed to user and page is redirected to "#/". This will again display Employee record in grid format (but with updated record).

The whole operation is done using without postback but still you can click on back/forward button of browser, this will change the URL of the page and display next/previous view.

The single page application of this kind can be developed in number of other JavaScript libraries. But, Sammy is certainly one of the best candidates for the task.

Thank you for visiting my blog! I would love to read your comments/critic and know about your choice of library for creating better SPA style apps.

Comments (6) -

SimpleScripts

Good post. I learn something totally new and challenging on blogs I stumbleupon on a daily basis. It will always be exciting to read through content from other writers and use something from their websites.

magellings

I find this very fascinating!  Makes me eager to want to try SPAs...  Thanks for an excellent read.

tzvi

seems not ordinarily and interesting
can i download you sample's demo code from somewhere?

hiten

Hi! Good Post.
But I am newbie for jquery.  I don't know how to create .template file. In Microsoft VS 2010 there is option to create template text file but is has .tt extension.
Please guide me how to create .template file and how to use it in jquery.

If you have full source code for this, Please provide it.

Any help will be appreciated.

Thanks in advance.

Felice Pollano

Great job, very useful tutorial. Thanks a lot!

Dennis Fike

First off, great post.
I did have a problem running the above sample code, but after just adding in my "var loadOptions" part, my problems went away.
Now it off to connect to a db. This has some much potential.
Again, awesome post!

my fix:
        var loadOptions = { type: 'get', cache: false, contentType: "application/json; charset=utf-8" };
        var app = $.sammy('#sammyDiv', function () {
            this.use('Template');
            this.use('Session');
            this.around(function (callback) {
            var context = this;
            this.load('Default.aspx/GetEmployees', loadOptions)

Pingbacks and trackbacks (2)+

About Me

You are visiting personal website of Kedar (KK)

Please go here to know more about me

Disclaimer

The opinions expressed here represent my own and not those of my past or present employers.

The concept/code provided on this site may not work as described. If you are using any code provided on this site. Then, please test it thoroughly. I shall not be responsible for any issues arising in the code. 

Month List