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 postback) 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 provide 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 behaviour 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.
this.get('#home', function (){
})
Using the code - setup
To get SammyJS up and running, we need to initialise it, and add some routes.
var app = $.sammy(function () {
this.get('#home', function () {
});
this.get('#address/create', function () {
});
}).run('#home');
In the above code, we initialise 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(..)
<div class="divPanel" id="div1" style="display:none">This is div 1<br />
<label id="div1_SomeLabel"></label></div>
<div class="divPanel" id="div2" style="display:none">This is div 2, the param was: <label id="div2_ParamVal"></label></div>
<div class="divPanel" id="div3" style="display:none">This is div 3</div>
<div class="divPanel" id="divHome">This is the Home div<br />
<p><!--</p>
<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
<a href="#home">Home</a> |
<a href="#d1">Show Div 1</a> |
<a href="#d2/id=1234">Show Div with param</a> |
<a href="#d4">Re-direct to div 1</a> |
<a href="#GetRemote">Get div content from server</a>
There is also have a simple html form that will be used to demonstrate posting form data
<form id="simpleForm" action="#/post/form" method="POST">
<label>Some Name</label><br />
<input type="text" name="some_name" value="a simple name" />
<input type="submit" value="Submit" />
</form>
The first two example routes are very basic, they hide/show different DIVs to the user depending on the route instruction.
this.get('#home', function () {
hideAll();
$('#divHome').fadeIn();
});
this.get('#d1', function () {
hideAll();
$('#div1').fadeIn();
$('#div1_SomeLabel').text('');
});
Sometimes we will want to send the user from one route to another - this is where the re-direct method comes in
this.get('#d4', function () {
hideAll();
this.redirect('#d1');
$('#div1_SomeLabel').text('Redirect from some a-tag');
});
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
<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.
this.get('#d2/:id', function () {
hideAll();
var tempID = this.params['id'];
$('#div2').fadeIn();
$('#div2_ParamVal').text(tempID)
});
Working with forms operates in a similar manner. Declare the form (note the method and the action route)
<form id="simpleForm" action="#/post/form" method="POST">
<label>Some Name</label><br />
<input type="text" name="some_name" value="a simple name" />
<input type="submit" value="Submit" />
</form>
With SammyJS, we then extract the values that are sent in the form. (A small gotcha here is you need to stop the default post back to the server by the form by returning "false")
this.post('#/post/form', function () {
alert('here with ' + this.params['some_name'] + '\n\nNow hiding the form!');
$('#simpleForm').hide();
return false;
});
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.
<!--
<div id="divContent" class="divPanel"></div>
this.get('#GetRemote', function () {
hideAll();
GetRemoteDiv();
});
var GetRemoteDiv = function () {
$.ajax({
method: 'get',
url: '/home/LoadDivFromRemote'
}
).done(function (dataRcvd) {
$('#divContent').empty();
$('#divContent').show();
$('#divContent').html(dataRcvd);
});
}
public ActionResult LoadDivFromRemote()
{
ViewBag.Title = "Remotly injected!";
ViewBag.Message = "This div was remotly injected into the dom from the server at: " + DateTime.Now.ToLongTimeString();
return PartialView("~/Views/Home/RemoteDiv.cshtml");
}
<h2>@ViewBag.Title</h2>
<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 extremly 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 Wassons article on msdn.
Here are 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 !
History
27/05/2015 - Version 1