Logo
ManuelSchoebel

Meteor.js Iron Router Filters, Before and After Hooks

##Introduction The Meteor.js Iron Router is the one package that helps me so much and is the first one I add in every application. If you do not know the Iron-Router make sure to learn how to use it because it realy helps so much!

The Iron Router basically decides what to render for the current Route (URL). Also do not think that you are building a so called one-page app and you do not need urls. You always need urls (e.g. share a state, bookmarking). And even if you are just building a quick prototype, maybe you come up with a feature that people would like to share, so better start with proper urls right from the beginning. But this is just a small part where the Iron Router helps you. One really great feature are the hooks.

##What are Iron Router Hooks? The Iron Router basically works like this:

  1. A new route is called
  2. The Iron Router checks if there are before hooks and runs them all
  3. If all before hooks are done and ready the rendering takes place
  4. Now the after hooks are all run

That means we can create functions that are run before any route and this is amazing if you for example want to filter a route to be only for logged in users.

##Two ways to implement hooks ###Define it in a route itself You can create a before hook for exactly one route like this:

    Router.map(function () {
        this.route('postShow', {
            path: '/posts/:_id',
 
            onBeforeAction: function (pause) {
                if (!Meteor.user()) {
                // render the login template but keep the url in the browser the same
                this.render('login');
            }
        }
    }

I personally would suggest to use this way only if it is a very special before hook that you would never use for any other route. In the example above you filter for logged in users and this is a filter you want to have on many routes. So do not do this in a routes before hook.

###Define it seperated from a specific route For my hooks I create a seperated file that really is only for hooks and nothing else. This way you can take code out of your Iron Router route-map. Even though the Iron Router routes are capable of a lot of things I personally do not want huge route functions. So I create a new file and define hooks like this:

    ////////////////
    // BeforeHooks
    ////////////////
    // I use an object that contains all before hooks
    var IR_BeforeHooks = {
        isLoggedIn: function(pause) {
            if (!(Meteor.loggingIn() || Meteor.user())) {
              Notify.setError(__('Please login.'));
              this.render('login');
              pause();
            }
        },
        somethingForAnyRoute: function() { ... },
        ...
        // add more before hooks here
    }
 
    // (Global) Before hooks for any route
    Router.onBeforeAction(IR_BeforeHooks.somethingForAnyRoute);
    ...
 
    // Before hooks for specific routes
    // Must be equal to the route names of the Iron Router route map
    Router.before(IR_BeforeHooks.isLoggedIn, {only: ['userAreaA', 'userAreaB']});
    ...
 
    ////////////////
    // After Hooks
    ////////////////
    // Another object for all after hooks
    var IR_AfterHooks = {
        fadeContentIn: function() { ... },
        ...
    }
 
    // (Global) After hooks for any route
    Router.onAfterAction(IR_AfterHooks.fadeContentIn);
 
    // After hooks for specific routes
    ...

As we can see, you can specify hooks for certain routes or even globally for any route.

Hook Compilation

There are some hooks I use quite often and I am sure you also have some common hooks. Please feal free to send me hooks you like to use or comment them below so we can gather hooks that are helpful.

Before Hooks Compilation

    var IR_Filters = {
        // All standard subscriptions you need before anything works
        // the .wait() makes sure that it continues only if the subscription
        // is ready and the data available
        // Use: global
        baseSubscriptions: function() {
            this.subscribe('userData').wait();
        },
        // show login if a guest wants to access private areas
        // Use: {only: [privateAreas] }
        isLoggedIn: function(pause) {
            if (!(Meteor.loggingIn() || Meteor.user())) {
              Notify.setError(__('Please login.')); // some custom packages
              this.render('login');
              pause();
            }
        },
        // make sure to scroll to the top of the page on a new route
        // Use: global
        scrollUp: function() {
            $('body,html').scrollTop(0);
        },
        // if this route depends on data, show the NProgess loading indicator
        // http://ricostacruz.com/nprogress/
        // Use: global
        startNProgress: function() {
            if (_.isFunction(this.data)) {
              NProgress.start();
            }
        },
        // tell google analytics that a page was viewed
        // e.g. https://github.com/datariot/meteor-ganalytics
        // Use: global
        pageview: function() {
            GAnalytics.pageview(this.path);
        },
        // only show route if you are an admin
        // using https://github.com/alanning/meteor-roles
        // Use: {only: [adminAreas]}
        isAdmin: function(pause) {
            if (!Roles.userIsInRole(Meteor.userId(), ['admin'])) {
              this.render('login');
              pause();
            }
        },
        // animate old content out using
        // http://daneden.github.io/animate.css/
        // Use: global
        animateContentOut: function() {
            $('#content').removeClass("animated fadeIn fadeInRight");
            $('footer').addClass("hide");
        }
    }

Dynamic Layout Configuration

This is nice if you have different layouts or different regions that are different many times. I do not like defining the same layoutTemplate and regions (named yields) in every route over and over again. So I use a before hook:

    // Define the layouts for your routes
    var Layouts;
    Layouts = [
      {
        // Layout definition
        layoutTemplate: 'leftNavLayout',
        yieldTemplates: {
          'courseLeftNav': {
            to: 'leftNav'
          }
        },
        // where to use
        routes: ['lectureShow', 'courseShow', 'lectureChangeRequest']
      },
      {
        // Layout definition
        layoutTemplate: 'leftNavLayout',
        yieldTemplates: {
          'myCourseLeftNav': {
            to: 'leftNav'
          }
        },
        // where to use
        routes: ['lectureUpdate', 'sectionUpdate', 'courseUpdate']
      }
    ];
 
    // Apply the layout, if none is found the Iron Router nothing happens here
    // and IR uses the standard ones
    var IR_BeforeHooks;
    IR_BeforeHooks = {
      setLayout: function() {
        var l, _i, _len;
        for (_i = 0, _len = Layouts.length; _i < _len; _i++) {
          l = Layouts[_i];
          if (_.indexOf(l.routes, this.route.name) > -1) {
            this.yieldTemplates = l.yieldTemplates;
             // this is for blaze-integration branch of Iron Router
             // for older branches of IR it should be this.setLayout(...)
            this.layout(l.layoutTemplate);
            return;
          }
        }
      }
    };
 
    // Use anywhere
    Router.onBeforeAction(IR_BeforeHooks.setLayout)

###After Hooks Compilation

    var IR_AfterHooks = {
        // if you fade the content out, fade it in again
        // Use: global
        fadeContentIn: function() {
            $('#content').addClass("animated fadeIn");
            $('footer').removeClass("hide");
        },
        // If you started NProgress end it again
        // You can also pass NProgess.done directly to the after hook
        // Use: global
        endNProgess: function() {
            NProgess.done();
        }
    }

###Unload Hooks Compilation

    var IR_UnloadHooks = {
        // anyone?!?
    }

##Conclusion Hooks are really great and are making things much easier, the code cleaner and better to maintain. I am sure there are a lot more practical hooks out there and I would love to learn how you make use of this Iron Router functionality, so please feel free to comment or contact me.

©️ 2024 Digitale Kumpel GmbH. All rights reserved.