Lightweight Single Page Application With MVC And SammyJS

Introduction

Traditional web applications reside mostly on the Server, with heavy traffic flow between browser and server using GETs, POSTs (and in webforms, the dreaded post-back) being the order of the day. More and more web applications are using a new paradigm, the Single Page Application or "SPA".

Common examples of SPAs are Facebook and Gmail. The concept of an SPA is to keep providing a better, more responsive, seamless experience for the user, so that they seldom have to leave the browser and therefore avoid long tedious full page refresh round-trips to the Server. Frameworks like Angular provide extremely robust methods for creating SPAs, but when you don't have the time for the learning curve, or want something that's light-weight and works simply, a very focused router like SammyJS is an extremely useful alternative. This article introduces the benefits of using an SPA router and explains how to use SammyJS to quickly put together a clean, scalable, modular single page application with MVC.


Why use a router

Let's consider a web based address book. It would have very basic functionality-

  • List addresses
  • Search address function
  • Create/Edit/Delete address entries

Even that restricted functionality however carries a reasonable data entry and management overhead.


A common approach to workflow in a Single Page Application is to use JavaScript button clicks to move things along. This methodology encourages behavior, for example like using global variables, to track the ID or state of a selected/current data record. While using, this approach works to a degree, it can get messy very quickly. If we can solve this work-flow problem, we have the basis for clear separation of concerns which allows for a far cleaner, scalable, and more maintainable solution.


SammyJS is described as "Restful, evented JavaScript". It allows us to work in an MVC manner, but on the client. Just as we have controllers in MVC and Web API that can accept verbs of get/post/delete etc, we can also implement these patterns using SammyJS.

In a traditional Web Server, we access the controller using full routes. SammyJS routing however uses ANCHOR TAGS "#" to hook routing together - you can see this in the example below that uses a standard "GET" verb to request data from the "#home" route.

  1. this.get('#home'function (){    
  2.    // do something here and send data back to the caller    
  3. })     

Using the code - setup

To get SammyJS up and running, we need to initialize it, and add some routes.

  1. //initialize "SAMMY"    
  2. var app = $.sammy(function () {    
  3.     
  4.     this.get('#home'function () {    
  5.         // show home screen    
  6.     });    
  7.     
  8.     this.get('#address/create'function () {    
  9.         // show create new address screen    
  10.     });    
  11.     
  12. }).run('#home');//run it with default html fragment     
In the above code, we initialize the library, and activate it at the same time using the "run" method, giving a default route of "#home". We now have two routes, one that serves the home page to the user and another that shows a create new address screen. Both are using simple GET verbs. The objective of this article is to get you up and running quickly with SammyJS as a routing solution to use in your MVC apps so I will now run through common use-case scenarios and how they might work - we will avoid building a full application, however trivial.

Using the code - routes and verbs

For demo purposes, I create a number of DIVs that will act as display forms/regions that are shown/hidden depending on the route choice the user makes. These can be separated out into individual cshtml files and rendered using HTML.Partial(..).

  1. <div class="divPanel" id="div1" style="display:none">This is div 1<br /> <label id="div1_SomeLabel"></label></div>  
  2. <div class="divPanel" id="div2" style="display:none">This is div 2, the param was: <label id="div2_ParamVal"></label></div>  
  3. <div class="divPanel" id="div3" style="display:none">This is div 3</div>  
  4. <div class="divPanel" id="divHome">This is the Home div<br />  
  5.     <p>  
  6.         <!-- master div that will be used to host other divs -->  
  7.     </p>  
  8.     <div class="divPanel" id="divContent"> </div>   
Of course, the routes have to be called from somewhere - I have a basic menu structure that demonstrates different use cases.
  1. <a href="#home">Home</a> |  
  2. <a href="#d1">Show Div 1</a> |  
  3. <a href="#d2/id=1234">Show Div with param</a> |  
  4. <a href="#d4">Re-direct to div 1</a> |  
  5. <a href="#GetRemote">Get div content from server</a>  
There, I also have a simple HTML form that will be used to demonstrate posting form data.
  1. <form id="simpleForm" action="#/post/form" method="POST">  
  2.    <label>Some Name</label><br />  
  3.    <input type="text" name="some_name" value="a simple name" />  
  4.    <input type="submit" value="Submit" />  
  5. </form>  
The first two example routes are very basic, they hide/show different DIVs to the user depending on the route instruction.
  1. this.get('#home'function() {  
  2.     hideAll();  
  3.     $('#divHome').fadeIn();  
  4. });  
  5. this.get('#d1'function() {  
  6.     hideAll();  
  7.     $('#div1').fadeIn();  
  8.     $('#div1_SomeLabel').text('');  
  9. });  
Sometimes, we will want to send the user from one route to another - this is where the re-direct method comes in.
  1. this.get('#d4'function() {  
  2.     hideAll();  
  3.     this.redirect('#d1');  
  4.     $('#div1_SomeLabel').text('Redirect from some a-tag');  
  5. });  
The next example shows how to get a value form a query-string that is sent in - first, here is the HTML again that calls the route.
  1. <a href="#d2/id=1234">Show Div with param</a>   
Note the parameter "id", and the value "1234". We access the query name and value in a route using "params". Note the value place-holder ":" in the route pattern, and the "params" value being accessed.
  1. this.get('#d2/:id'function() {  
  2.     hideAll();  
  3.     var tempID = this.params['id'];  
  4.     $('#div2').fadeIn();  
  5.     $('#div2_ParamVal').text(tempID)  
  6. });  
Working with forms operates in a similar manner. Declare the form (note the method and the action route).
  1. <form id="simpleForm" action="#/post/form" method="POST">  
  2.    <label>Some Name</label><br />  
  3.    <input type="text" name="some_name" value="a simple name" />  
  4.    <input type="submit" value="Submit" />  
  5. </form>  
With SammyJS, we then extract the values that are sent in the form. (A small gotcha here is that you need to stop the default post back to the server by the form by returning "false").
  1. this.post('#/post/form'function() {  
  2.     alert('here with ' + this.params['some_name'] + '\n\nNow hiding the form!');  
  3.     $('#simpleForm').hide();  
  4.     return false// stops the default POST behaviour back to server  
  5. });  

Beyond the router

Creating a Single Page Application has many challenges, including memory management, data exchange with Server, etc. Here is a small example of taking a fresh, updated server-side partial page and using it to update the interface for the user.
  1. <!-- master div that will be used to host other divs -->    
  2. <div id="divContent" class="divPanel"></div>    
  1. // Sammy routing    
  2.  this.get('#GetRemote'function () {    
  3.             hideAll();    
  4.             GetRemoteDiv();    
  5.         });    
  6.     
  7. // Ajax call to get remote html to replace page content    
  8.     var GetRemoteDiv = function () {    
  9.         $.ajax({    
  10.             method: 'get',    
  11.             url: '/home/LoadDivFromRemote'    
  12.             }    
  13.            ).done(function (dataRcvd) {    
  14.                $('#divContent').empty();    
  15.                $('#divContent').show();    
  16.                $('#divContent').html(dataRcvd);    
  17.            });    
  18.     }    
  1. // server-side C# code that generates the dynamic page data    
  2.     
  3.  public ActionResult LoadDivFromRemote()    
  4.         {    
  5.             ViewBag.Title = "Remotly injected!";    
  6.             ViewBag.Message = "This div was remotly injected into the dom from the server at: " + DateTime.Now.ToLongTimeString();    
  7.             return PartialView("~/Views/Home/RemoteDiv.cshtml");    
  8.         }    
  1. <h2>@ViewBag.Title</h2>    
  2. <h3>@ViewBag.Message</h3>    

Wrap-up

This article has focused on the core concept of routing - what you do with routing is another thing - I suggest you work through the "JSON Store" examples (part 1, part 2) on the SammyJS website to get an in-depth understanding of the capabilities of this library.

SammyJS comes with a raft of extremely useful plugins that cover topics such as html templating, client-side local storage, SPA Google Analytics helper, etc.

I strongly encourage you to download the sample code and try SammyJS out.

Other resources

For a more in-depth walk-through of the architecture of an SPA using Microsoft technologies, read Mike Wasson's article on msdn. Here are the two other general resources on Single Page Applications that go quite in-depth.

Finally, if this article was useful to you, please don't forget to give it a vote at the top of the page !

Have fun coding!