0
Tuesday, December 4, 2012

I am going to walk you through building a SPA (single page application) using AngularJS, Google's latest JavaScript MVC framework. After developing applications using Backbone.js, Spine.js and good ol' jQuery I fell in love with AngularJS. It makes development of SPAs actually fun removing a lot of the boilerplate that most of the other frameworks make you write to get a simple app started.

The app we are going to build today is a simple multipage website that will read data in from a static JSON file. We will be covering setting up AngularJS, routing, rendering partials, communication between controllers and loading JSON asynchronously.

Sections

Before we get started it's a good idea to watch the intro videos on angularjs.org as they cover most of the basics. I'm going to assume that you understand the concepts of how the controller binds $scope with the views and how the ng-model directive works.

Setting up AngularJS

This tutorial will require you to run these files via a web server. If you have apache/MAMP/WAMP set up you should be able to create a new directory under your webroot and access via localhost. I like to run the npm module nws which creates a web server in the current directory allowing me to get started quickly.

Html

The first thing you will want to do is setup your module. You do this by adding the ng-app attribute to your html tag. In the case of our application we will call our module Site.

<html lang="en-US" ng-app="Site">

The next thing we want to do is set up a controller. The controller is bound to the element you add it to and it's children. We are going to add an AppController to our body tag which allows the entire body to inherit from this controller. To add a controller you use the ng-controller attribute.

<body ng-controller="AppController">

Here is a template including AngularJS from CDNJS that I will be using throughout this tutorial.

JavaScript

Now that our html is set up we need to create the module and controller in our /js/site.js file that is included.

var Site = angular.module('Site', []);

function AppController ($scope) {
}

The first line creates our Site module and the function defines our AppController. The controller receives one argument (at the moment) $scope which binds models between the view (html) and controller.

Routing

If you took a look at the template above you will have noticed that there are three links in the nav bar: Home, About and Contact. We want to be able to route to these links and load a section from the JSON file based on the slug.

The first thing we need to do is configure our Site module to use the $routeProvider and then set up our routes.

Site.config(function ($routeProvider) {
  $routeProvider
    .when('/page/:slug', {templateUrl: 'partials/page.html', controller: 'RouteController'})
    .otherwise({redirectTo: '/page/home'});
});

We are passing in an anonymous function to the modules config method. In the function we are passing in Angular's $routeProvider service which allows us to define routes. The two methods we are using are when() which takes a path and hash of options including the templatePath and controller and otherwise() which allows us to redirect to a route if one is not found.

We are defining one route for this application /page/:slug. This path contains the :slug parameter, we can use this later using the $routeParams service to extract the page data from the JSON.

We are also setting a controller for this route (RouteController). This controller is in charge of binding the page content to the view.

function RouteController ($scope, $routeParams) {
  // Getting the slug from $routeParams
  var slug = $routeParams.slug;

  // We need to get the page data from the JSON
  // $scope.page = ?;
}

Now when you try and visit the page you should be redirected to /#/home and if you try and type in anything else it will redirect you back.

Rendering Partials

When we defined our route we set a templateUrl. This points to a static html file that contains 2 simple expressions containing properties that will be bound to the RouteController's $scope.

<!-- partials/page.html -->
<h1>{{page.title}}</h1>
{{page.content}}

To include this partial into your layout you need to use the <ng-view></ng-view> directive. This will automatically be replaced with the partial set in the routes. You can see that this is included already in the template provided above.

Loading JSON

Let's load some data into this application. I've created a simple static JSON data store (in real life this would probably be generated by an API) which we will load in via Ajax when the application starts.

// pages.json
{
  "home": {
    "title": "Home",
    "content": "This is the home page. Welcome"
  },
  "about": {
    "title": "About",
    "content": "This is the about page. Welcome"
  },
  "contact": {
    "title": "Contact",
    "content": "This is the contact page. Welcome"
  }
}

We should only load the JSON file once, cache it to a variable and access the variable when we need the data. Let's use Angular's $http service to grab the JSON file. To use the $http service we will need to pass it as an argument to the AppController.

function AppController ($scope, $rootScope, $http) {
  // Load pages on startup
  $http.get('/pages.json').success(function (data) {
    $rootScope.pages = data;
  });
}

We are also passing in $rootScope which all scopes inherit from. We do this so this so that we can access the pages JSON data in our RouteController. We can then access the page data by using the slug we captured earlier and bind it to the scope so it's accessible in our partial.

function RouteController ($scope, $rootScope, $routeParams) {
  // Getting the slug from $routeParams
  var slug = $routeParams.slug;

  $scope.page = $rootScope.pages[slug];
}

Communicating between controllers

We've already demonstrated one way to communicate between controllers using the $rootScope. Another option is using events. In Angular you can emit events and listen to events in different controllers while passing data from the emitter to the listener.

We want to pass the slug from the RouteController to the AppController so that we can set the active class on the current menu link. Angular has a ng-class directive which allows you to add conditional classes to elements.

<li ng-class="{active: slug == 'home'}"><a href="/#/page/home">Home</a></li>
<li ng-class="{active: slug == 'about'}"><a href="/#/page/about">About</a></li>
<li ng-class="{active: slug == 'contact'}"><a href="/#/page/contact">Contact</a></li>

You can see that the RouteController is emitting the slug and the AppController is listening for the slug which then sets it onto it's scope and exposes it to the view.

function AppController ($scope, $rootScope, $http) {
  // Load pages on startup
  $http.get('/pages.json').success(function (data) {
    $rootScope.pages = data;
  });

  // Set the slug for menu active class
  $scope.$on('routeLoaded', function (event, args) {
    $scope.slug = args.slug;
  });
}

function RouteController ($scope, $rootScope, $routeParams) {
  // Getting the slug from $routeParams
  var slug = $routeParams.slug;

  $scope.$emit('routeLoaded', {slug: slug});
  $scope.page = $rootScope.pages[slug];
}

I hope you guys enjoyed this walkthrough. I will be writing more about AngularJS in the future. If you guys have any questions about this post or have any topics you are interested in let me know in the comments.

0
Saturday, December 1, 2012

Every time you load a new page with Turbolinks only the body of the page is replaced. This means that the JavaScript environment stays the same and any variables you've set will remain the same. Sometimes this is actually welcome behaviour as I posted previously about caching dynamic partials but sometimes you want to reset those variables since the context has changed on the new page load.

Resetting JavaScript variables

A simple work around is to just delete the variable and set it again using the Turbolinks page:load event or in a script loaded by the new page. This is the method I use for the tweet links at the bottom of my posts.

delete tweet;
var tweet = function () {
  var left = (screen.width/2)-(600/2);
  var top = (screen.height/2)-(300/2);
  window.open(
    "https://twitter.com/share?via=andruu&text=Check out #{page.title}&url=#{page_url(page.slug)}",
    "Tweet Post", "width=600,height=300,top="+top+",left="+left+""
  );
  return false;
}

You will see that I actually call delete tweet; before I set the variable. If I didn't do this the tweet variable would be set to function the first time it was called and would remain the same after loading a new page.

0
Friday, November 30, 2012

I recently added a dark theme to this blog, it's great for night time reading on your laptop, tablet or phone. This is the first time I've implemented dynamic theming on a site and it was super simple to integrate. I thought I would share my thoughts and the implementation strategies I used.

Integration

The first thing I did was add a class to my body tag, in my case the class name was dark. Using Sass or Less will make the following a lot easier although it's doable with plain css.

Basically what you want to do is create a new body.theme-name definition in your stylesheet and using scss I was able to nest the rest of my styles underneath this.

body.dark {
  a {
    color:lighten($main-color, 20%);
  }
  background-color:#111;
  color:#ddd;
  /* rest of your styles you want to override */
}

After I got everything looking how I wanted I removed the class from the body since I didn't want this to be the default. The idea here is dynamically add the class to the body when the user wants to switch the theme. I also wanted to be able to remember the user's choice so on subsequent page loads it would load the correct theme. This is easy enough with jQuery and the jQuery cookie plugin.

$('.change-theme').on 'click', ->
  if $('body').hasClass('dark')
    $.cookie('dark_mode', 'false')
  else
    $.cookie('dark_mode', 'true')
  $('body').toggleClass('dark')

The above code listens for a click on my change theme button. If the body already has a class name of dark I then set a cookie with the user's preference and toggle the theme. I do the opposite if the body doesn't have the class name.

I now needed a way to load the theme depending on the user's preference that was in the cookie. Again this is quite easy to do.

if $.cookie('dark_mode') is 'true'
  $('body').addClass('dark')

I'm sure there are some jQuery plugins out there that do this for you already but it's easy enough to roll your own and it gives you more control over the implementation. I hope you enjoyed this post and if you have any questions or suggestions on how to improve this implementation leave a comment below.

0
Friday, November 30, 2012

Yesterday I blogged about caching and loading dynamic partials via ajax for non static content. This can sometimes defeat the purpose of caching content if you are hitting the rails stack and accessing the database each time the page loads via ajax.

Turbolinks only loads assets on the original page load, after that it just replaces the body content. This gave me an idea. Load my dynamic partials only on a real page load and not everytime Turbolinks requests a new page.

Caching

Outside of any document ready methods (usually top of your main application js/coffee file) I place my cache holder variables. In this example I will show you how I load and cache the recent posts on this blog.

window.cachedRecentPosts = ''

Then in the document ready and Turbolinks 'page:load' event I check to see if there is anything in the cache holder. If there is I just populate the html with the cached content or call the script via ajax to get the new content.

# Load recent posts
if window.cachedRecentPosts != ''
  $('ul.recent-posts').append(window.cachedRecentPosts)
else
  $.getScript('/pages/recent_posts')

When /pages/recent_posts is called via $.getScript() it loads app/views/pages/recent_posts.js.erb which will populate the window.cachedRecentPosts variable with the recent posts and also populate the html with the content.

window.cachedRecentPosts = '<%= j render("layouts/recent_posts") %>';
$('ul.recent-posts').append(window.cachedRecentPosts);

The above script will only be called on the original page load with subsequent requests using the cached content greatly improving the speed of the page and lessening the load on the server.

2
Wednesday, November 28, 2012

Recently I implemented full page caching on this blog. Not because of the insane amount of traffic I've been receiving (10 hits a day) but because it was something I wanted to experiment with and I also wanted to squeeze some more speed out of this CMS.

Using Turbolinks the pages were loading fast but now with the full page caching clicking on a link instantly shows the new page. You don't even have time to blink!

Page Caching

So I went about my way implementing Rails page caching which basically caches the entire page including the layout to a static html file in your public directory. This has the advantage of not hitting the rails stack and it's called directly from the web server (nginx/apache). There are a few gotchas when implementing page caching in rails. First you can't really have anything dynamic which for this site doesn't seem like to much of a problem at first, although when you update a post or someone leaves a comment the web server continues to serve up the static html file leaving you scratching your head.

Rails has some awesome utilities to get around this. There is the expire_page method which allows you to pass in a route to remove the static file. This is useful after updating a post or when someone leaves a comment. At first I created a helper method to clear the cache until I came across Rail's cache sweepers. These allow you to observe a model and run methods after save, update and destroy. So I moved all of my caching logic into the sweeper so that whenever a post is updated the cache is cleared for the post and any other page that contains post data (home page, archives, etc.).

Here is the sweeper I originally used:

class PageSweeper < ActionController::Caching::Sweeper
  observe Page

  def sweep(page)
    # Page Caching
    expire_page pages_path
    expire_page page_path(page.slug)
    expire_page '/'
    expire_page archives_path
    FileUtils.rm_rf "#{page_cache_directory}/pages"
  end

  alias_method :after_create, :sweep
  alias_method :after_update, :sweep
  alias_method :after_destroy, :sweep
end

So everything is running quick, Rails doesn't have to serve up pages anymore but wait... unfortunately Heroku doesn't support page caching.

Heroku has an ephemeral file store, so while page caching may appear to work, it won’t work as intended.
https://devcenter.heroku.com/articles/caching-strategies#page-caching

I didn't realize this until after checking out the logs and doing a bit of research. After reading their caching strategies page I decided to use action caching.

Action Caching

Action caching works similarly to page caching although it still hits the Rails stack. There are some advantages to using the action cache over the page cache such as being able to access your before filters to determine if a user's logged in or whatever you usually do in the before filter.

Action cache uses in memory stores (memcache usually) to cache the html of the page. You can opt-out of caching the layout if you want to keep certain things dynamic such as if a user is logged in and just cache the html of the action itself.

After implementing the action cache I really couldn't tell the difference between it and page caching. It was still super fast and made clicking on links seem like they were static html files. Action caching uses an almost identical API to page caching with a few small differences.

I decided to keep both page and action caching in case one day I move away from Heroku to a host with a standard file system. I ended up creating some configuration to determine whether to use page or action caching.

In the pages controller:

caches_page :index, :show, :archives if Blog.cache == :page
caches_action :index, :show, :archives if Blog.cache == :action

And I updated my sweeper method to clear the cache based on the configuration:

class PageSweeper < ActionController::Caching::Sweeper
  observe Page

  def sweep(page)

    if Blog.cache == :page

      # Page Caching
      expire_page pages_path
      expire_page page_path(page.slug)
      expire_page '/'
      expire_page archives_path
      FileUtils.rm_rf "#{page_cache_directory}/pages"

    elsif Blog.cache == :action

      # Action Caching
      expire_action controller: '/pages', action: :index
      expire_action "#{request.host}/#{page.slug}"
      expire_action controller: '/pages', action: :archives

      # Remove pages cache
      pages = (Page.published_posts.count.to_f / Blog::POSTS_PER_PAGE.to_f).ceil
      1.upto(pages) { |p| expire_action "#{request.host}/pages/#{p}" }

    end

  end

  alias_method :after_create, :sweep
  alias_method :after_update, :sweep
  alias_method :after_destroy, :sweep
end

Loading dynamic partials

Since originally I was using full page caching I decided to load certain parts of the page via ajax and partials. For the most part this just checks if the admin is logged in and will show extra links in the navigation and options to moderate comments. I am also loading in the recent posts in the side bar via ajax so that I don't have to clear every page from the cache when adding a new post.

I will write another post on some of the cool techniques I used to only load the partials once via ajax per viewing session, since Turbolinks doesn't do full page refreshes.

More reading