Loading...

SammyJS and Umbraco a Love Story

SammyJS what can I say! I ♥ SammyJS

SammyJS is written by Aaron Quint and various contributors. It offers a very developer friendly experience and getting it up and running is very intuitive.

The SammyJS site and blog are extremely well laid out and very easy to read so I am not going to try and reinvent the wheel here.

Sammy development centres around Routes, Events and Plugins.

For us C#/ASP.NET MVC developers the concept isn't new, but clean and familiar.

The best explanation for each of those key Sammy concepts is most certainly on the SammyJS website and blog:

  • Routes - the key concept behind hooking up your application.
  • Helpers - creating reusable code blocks.
  • Plugins - extend the application core.
  • Events - bind and trigger events for your application.
  • Current Version API - very well documented API including plugin usage.

SammyJS primarily focuses on the use of plugins, adapters and readable code. I would strongly suggest you head on over to the SammyJS site to learn more about it as I won't be focusing heavily on detailed Sammy functionality. What I will focus on is getting it up and running and route mapping. It lends itself strongly to a Delegator, Controller type scenario using templating engines such as Handlebars (a SammyJS plugin) or something like the jQuery templating engine.

For the purposes of this demo and in the interests of what we're trying to highlight by making this site, we didn't use a Delegator, Controller type pattern but chose to focus on developing an approach whereby our site would not only function in a very Pretty/Sammy-fied way but also be highly index-able by Google's Ajax crawling standards

Without further ado, let's have a look-see at our humble SammyJS implementation and explore some of the GOTCHA's we found along the way.

Our implementation consists of the following js files/libraries (you can read more about them on the SammyJS site but I will explain them briefly):

    <script src="/scripts/libs/jquery.sammy.min.js" type="text/javascript" charset="utf-8"></script>
    <script src="/scripts/libs/sammy.title.js" type="text/javascript" charset="utf-8"></script>
    <script src="/scripts/libs/sammy.handlebars.js" type="text/javascript"></script>
    <script src="/scripts/libs/sammy.googleanalytics.js" type="text/javascript"></script>
    <script type="text/javascript" src="/scripts/hashbang.ajax.js"></script>
  • jQuery - a given
  • jquery.sammy.min.js - This is the minified version of the SammyJS library
  • sammy.title.js - This is a SammyJS plugin which allows you to update the page title as a response to a route change, event, etc.
  • sammy.handlebars.js - Handlebars is a templating engine very similar to jQuery's templating library which allows you to inject the results of a JSON request into a chunk of html for display purposes. We used it on the robots page to highlight how it might work, but for the purposes of this demo it is not something we'll focus on
  • sammy.googleanalytics.js - This plugin for SammyJS allows us to track Routes as the user navigates through the site using SammyJS. It is as simple as embedding your standard analytics script tag and away you go!
  • hashbang.ajax.js - This is the script file where we store the "guts" of our SammyJS implementation. This would usually be called app.js by convention as it initialises and holds the routes/interaction of a variable called app which is our instance of Sammy.Application

#! It's Our App!

Holy hash bang batman! It's time for the big reveal! Please see the contents of hashbang.ajax.js below (we have excluded some commented out items relating to pushstate, more on that in the pushstate section).

(function ($) {
 (function ($) {
  var contentBody, preLoader, firstAccess = true;
  var titleSuffix = "| Hash Bang";
  var hashPrefix = (Modernizr.history) ? '' : '/#!';
  var app = $.sammy('#wrapper', function () {
    this.debug = true;
    this.use(Sammy.Handlebars, 'hb');
    this.use(Sammy.Title);
    this.use(Sammy.GoogleAnalytics, '#!');

    //would be traditionally placing updateTitle in this.bind('event-context-after', function() { });
    //but due to jQuery loading in content the context-after function doesn't always fire
    this.helpers({
      updateTitle: function (newPageTitle) {
        var pageTitle = "One Page Navigation With ASP.NET, Umbraco, SammyJS and IIS7";
        if (newPageTitle.length > 0) {
          pageTitle = newPageTitle + (newPageTitle.indexOf(titleSuffix) == -1 ? titleSuffix : "");
        }
        this.title(pageTitle);
      },
      loadAjaxPage: function (ctx) {
        var p = ctx.path.replace('/#!', '') + 'ajax';
        var self = this;
        var args = arguments;

        preLoader.show();

        var loadResult = $.get(p, function (response, status, xhr) {
          if (status == "error") {
            var msg = "Sorry but there was an error: ";
            contentBody.html(msg + xhr.status + " " + xhr.statusText);
          } else {
            var pageTitle = (/<title>(.*?)<\/title>/m).exec(response)[1];
            var pageContent = $(response, '#content-body');
            contentBody.html(pageContent);
            ctx.updateTitle(pageTitle);
            if (p.indexOf("owls") != -1) {
              appendPreloadify();
            }
          }
          preLoader.hide();
          prettyPrint();
        });
      }
    });

    //WE COULD USE THIS APPROACH IF WE COULD MAKE USE OF THE NATIVE SAMMYJS FALLBACK
    /*this.get('/\/$/', function (ctx) {
    if (firstAccess) {
    firstAccess = false;
    return;
    }
    this.loadAjaxPage(ctx);
    });

    this.get('^(.*)$', function (ctx) {
    this.loadAjaxPage(ctx);
    });*/

    this.get(hashPrefix + '/robots/', function (ctx) {
      preLoader.show();
      this.load('/data/robots.json', function (robotsList) {
        preLoader.hide();
        $('body').data('robots', true);
        this.render('/template/robots.hb', robotsList).then(function (content) { $('#content-body').html(content); });
      });
      this.updateTitle('Robots');
    });

    this.get('^' + hashPrefix.replace('/', '\/') + '\/(.+\/)?$', function (ctx) {
      console.log('getting page ---- ');
      this.loadAjaxPage(ctx);
    });

    /*this.bind('run', function (e) {
    var ctx = this;
    $('#main-menu li a, #logo a').click(function () {
    clicked = $(this);
    e.preventDefault();
    ctx.redirect(clicked.attr('href'));
    return false;
    });
    });*/
  });
  $(function () {
    $('#main-menu li a, #logo a').each(function () {
      var urlParts = /^(https?:\/\/[^\/]+)?(\/[^\?]*)(\?.*)?$/.exec($(this).attr("href"));
      var hashBangLink = hashPrefix + urlParts[0];
      $(this).attr("href", hashBangLink);
    });
    preLoader = $('#page-preload');
    contentBody = $('#content-body');

    app.run(hashPrefix + location.pathname);
  })
})(jQuery);

function appendPreloadify() {
    if (!$.fn.preloadify) {
      $.getScript("/scripts/libs/preloadify.js", function() {
        $('#owl figure').preloadify({ imagedelay: 500 });
      });
    } else {
      $('#owl figure').preloadify({ imagedelay: 500 });
    }
}

As you can see without having a great understanding of what's going on, the code is really quite readable!

The first part of any SammyJS app is thinking about your Routes and the type of actions you want to occur when a given route is matched. For the purposes of this example there were only 2 key routes that were required to get things up and running. Using regular expressions we can do some pretty powerful stuff with Sammy and the way it maps its' routes but simple regex was all that was needed here. If we were dealing with an example making use of HTML5 and pushstate then the routes would only need to be altered slightly (more on pushstate later).

 this.get('^' + hashPrefix.replace('/', '\/') + '\/(.+\/)?$', function (ctx) {
      console.log('getting page ---- ');
      this.loadAjaxPage(ctx);
    });

This route will match our hashPrefix and call our loadAjaxPage helper function. As you can see above, in the helper function we're basically stripping the path of the #! then using jQuery to load in the contents of the page as if we were making a standard page request using Umbraco. The reason we have used $.get over $('some element selector').load is that we didn't want to immediately inject the contents of the load response into our content holding div. We originally did this but then thought what about our page title? This then led us to load the page contents in and grab the page title from the page we were interested in and then make use of the SammyJS title plugin to replace it in the dom.

 this.get(hashPrefix + '/robots/', function (ctx) {
      preLoader.show();
      this.load('/data/robots.json', function (robotsList) {
        preLoader.hide();
        $('body').data('robots', true);
        this.render('/template/robots.hb', robotsList).then(function (content) { $('#content-body').html(content); });
      });
      this.updateTitle('Robots');
    });

The robots page was merely used as an example of how you could load some content in via json, use a SammyJS handlebars template and apply the JSON response to the template. It was more used for illustrative purposes as it was not in the interest of this example to utilise this approach to loading page content with SammyJS. The reason we didn't want to employ this approach on a wide scale was that it is not an SEO friendly solution and thus would defeat the purpose of the exercise.

For those of you who are interested anyway our json data looks like so:

{
   "robots": [
   {
	"name": "Johnny 5",
	"text": "Johnny 5 is alive. More input Stephanie!"
   },
   {
	"name": "Wall-e",
	"text": "EEEEEVVVAAAA"
   },
   {
	"name": "Astro Boy",
	"text": "Soaring high in the sky, He may be small but only in size. AstroBoy, AstroBoy,
        He is brave and gentle and wise!"
   },
   {
	"name": "Envoy Bot",
	"text": "Envoy Bot the greatest bot of them all."
   }]
}

Once that data has been loaded in, we then take it and render our handlebars template passing in the JSON response to the following template:

<div id="robots">
    <h1>Robots</h1>
    <p>Robots are super awesome! see our list of super awesome
    robots!</p>

    <p>My list of robots is pulled in via some json!</p>

    <p>Traditionally if we were going to index this page we wouldn't be
    pulling the data back via JSON but just an example to show you
    what's possible with sammyjs.</p>

    {{#robots}}
        <section class="robot">
            <h3>{{name}}</h3>
            <p>{{text}}</p>
        </section>  
    {{/robots}}
</div>

In the handlebars templating engine you will notice the markup is quite similar to jQuery's soon to be released templating engine. In our travels we saw quite a few implementations utilising the jQuery engine over handlebars as a means of personal preference

The result can been seen on the Robots Page.

This demo is powered by

blog comments powered by Disqus