Handling multiple “pages” with Meteor.

Meteor is an amazing platform for being able to quickly build web applications. This isn’t a review of it, but I’ll probably get one out at some point.

Meteor’s great and all, but how do you handle multiple pages with it? In it’s templating docs it shows how to use templates inside of other templates, but not how to switch the content/templates. After a good deal of hunting, I found the solution in Meteor’s github wiki however it was lacking any depth of detail so I decided someone ought to post about it.

You can grab the example I made, and thoroughly commented, from github. I’ll go over the important bits here.

I’m not going to go over Meteor or it’s different parts, they have excellent documentation setup for that. I’m just going to be focusing on how to do this multiple page setup.

The tl;dr

By using one or more Block Helpers we can control which template(s) are shown. Block Helpers are, essentially, customizable conditionals. So, we keep track of the page we’re on and change that when links are clicked, stop links’ default behavior, and then put our logic inside the Block Helper. Meteor will handle the rest for us.

HTML

Let’s look at the full .html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<head>
  <title>multiple-view-example</title>
</head>
 
<body>
	<nav>
		<ul>
			<li><a href="/">Default</a></li>
			<li><a href="/one">One</a></li>
			<li><a href="/two">Two</a></li>
			<li><a href="/three">Three</a></li>
		</ul>
	</nav>
	{{> body}}
</body>
 
<template name="body">
	{{#page_is "/"}}
		{{> default}}
	{{/page_is}}
	{{#page_is "/one"}}
		{{> one}}
	{{/page_is}}
	{{#page_is "/two"}}
		{{> two}}
	{{/page_is}}
	{{#page_is "/three"}}
		{{> three}}
	{{/page_is}}
</template>
 
<template name="default">
	This is the default page.
</template>
 
<template name="one">
	This is page one.
	<ul>
		<li><a class="subnav" href="/foo">Subpage foo</a></li>
		<li><a class="subnav" href="/bar">Subpage bar</a></li>
		<li><a class="subnav" href="/baz">Subpage baz</a></li>
	</ul>
	{{#sub_is "/foo"}}
		{{> foo}}
	{{/sub_is}}
	{{#sub_is "/bar"}}
		{{> bar}}
	{{/sub_is}}
	{{#sub_is "/baz"}}
		{{> baz}}
	{{/sub_is}}
</template>
 
<template name="foo">
	Sub page foo
</template>
 
<template name="bar">
	Sub page bar
</template>
 
<template name="baz">
	Sub page baz
</template>
 
<template name="two">
	This is page two.
</template>
 
<template name="three">
	This is page three.
</template>

Let’s break that down into bite-sized bits and pieces. First, the body:

5
6
7
8
9
10
11
12
13
14
15
<body>
	<nav>
		<ul>
			<li><a href="/">Default</a></li>
			<li><a href="/one">One</a></li>
			<li><a href="/two">Two</a></li>
			<li><a href="/three">Three</a></li>
		</ul>
	</nav>
	{{> body}}
</body>

All we’re doing in here is setting up the navigation and then referencing the template named body.

17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template name="body">
	{{#page_is "/"}}
		{{> default}}
	{{/page_is}}
	{{#page_is "/one"}}
		{{> one}}
	{{/page_is}}
	{{#page_is "/two"}}
		{{> two}}
	{{/page_is}}
	{{#page_is "/three"}}
		{{> three}}
	{{/page_is}}
</template>

In that template, we’re making use of our main Block Helper (which I’ll get to later on). It’s like a customized if conditional. Whether or not the page is equal to the string we’re passing it, it will then show us whatever is inside the block – in this case other templates.

The rest of the templates are almost all super simple (just text), so I won’t bother going over them. The only one which deserves some attention is the template named one, which showcases how it’s possible to handle subpages with this approach as well.

36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<template name="one">
	This is page one.
	<ul>
		<li><a class="subnav" href="/foo">Subpage foo</a></li>
		<li><a class="subnav" href="/bar">Subpage bar</a></li>
		<li><a class="subnav" href="/baz">Subpage baz</a></li>
	</ul>
	{{#sub_is "/foo"}}
		{{> foo}}
	{{/sub_is}}
	{{#sub_is "/bar"}}
		{{> bar}}
	{{/sub_is}}
	{{#sub_is "/baz"}}
		{{> baz}}
	{{/sub_is}}
</template>

Javascript

We’ve got some content, a subnav, and then another Block Helper which is specific to this template. All things we’ve previously discussed – so let’s not linger on them and instead move on to the javascript.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
if ( Meteor.is_client ) {
 
	var callbacks = {
			'/':		function() {
 
			},
			'/one':		function() {
				Session.set( 'sub', '/foo' );
			},
			'/two':		function() {
 
			},
			'/three':	function() {
 
			}
		},
		noop = function(){};
 
	function getCallback( data ) {
		return callbacks.hasOwnProperty( data ) ? callbacks[ data ] : noop;
	}
 
	Session.set( 'page', '/' );
 
	Template.body.page_is = function( data, options ) {
		if ( Session.equals( 'page', data ) ) {
			setTimeout( getCallback( data ), 0 );
			return options.fn( this );
		}
		return options.inverse( this );
	};
 
	Template.one.sub_is = function( data, options ) {
		if ( Session.equals( 'sub', data ) ) {
			return options.fn( this );
		}
		return options.inverse( this );
	};
 
	Meteor.startup( function() {
		$( document ).on( 'click', function( e ) {
			if ( e.target.nodeName === 'A' ) {
				var $this = $( e.target );
 
				if ( $this.hasClass( 'subnav' ) ) {
					Session.set( 'sub', $this.attr( 'href' ) );
				} else {
					Session.set( 'page', $this.attr( 'href' ) );
				}
				return false;
			}
		} );
 
	} );
}
 
if ( Meteor.is_server ) {
	Meteor.startup( function() {
		return;
	} );
}

Yikes! I know, I know – it seems like a lot. Let’s break it down so that we can digest it. Since there’s nothing in the server bit, we’ll only be going over what’s in the Meteor.is_client section.

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var callbacks = {
		'/':		function() {
 
		},
		'/one':		function() {
			Session.set( 'sub', '/foo' );
		},
		'/two':		function() {
 
		},
		'/three':	function() {
 
		}
	},
	noop = function(){};

Alright, so we’ve got our variable declarations. We’re storing references to callbacks that will happen on the main pages as well as an empty function in case someone stumbles onto a page without a callback assigned to it.

19
20
21
function getCallback( data ) {
	return callbacks.hasOwnProperty( data ) ? callbacks[ data ] : noop;
}

This is our helper function which returns either the callback associated with the page or the empty function.

23
Session.set( 'page', '/' );

Alright, here we’re making use of Meteor’s Session object and setting the default page. This is what will first show up every time you visit the site.

25
26
27
28
29
30
31
Template.body.page_is = function( data, options ) {
	if ( Session.equals( 'page', data ) ) {
		setTimeout( getCallback( data ), 0 );
		return options.fn( this );
	}
	return options.inverse( this );
};

This is the main Block Helper. It simply checks if page in Session is equal to the data we’re checking against (the string from the templates in our .html). If it is, we set the callback to run as soon as possible (so that it will occur after our return) and then return options.fn(this) which will return whatever is inside the main block. The other return of options.inverse(this) is the do whatever is in the {{else}} block, which I didn’t put into the example.

Our subpage Block Helper is pretty much the exact same thing as our main Block Helper, but it doesn’t have any callback in it.

40
41
42
43
44
45
46
47
48
49
50
51
52
53
Meteor.startup( function() {
	$( document ).on( 'click', function( e ) {
		if ( e.target.nodeName === 'A' ) {
			var $this = $( e.target );
 
			if ( $this.hasClass( 'subnav' ) ) {
				Session.set( 'sub', $this.attr( 'href' ) );
			} else {
				Session.set( 'page', $this.attr( 'href' ) );
			}
			return false;
		}
	} );
} );

Meteor.startup() is the equivalent to $(document).ready() for jQuery. Inside of this, we’re capturing all click events on the entire document. We do this so that we can utilize a single handler for all elements, including those added dynamically later on. If the target is a link tag, we handle it by setting either sub or page in Session with it’s href and then returning false which is the same as doing e.preventDefault() and e.stopPropagation().

Simplify and Expedite Front-end Web Development

This is a post I originally did for the blog of Infrared5, where I work. I felt very strongly about it and wanted to share it here as well.

Front-end web development projects can be difficult at times, but they don’t have to be. Every project is unique and each brings new and interesting challenges. These challenges are frequently presented by the very technologies we choose or must use on the aforementioned projects. Although using them is a great way to become more skilled, sometimes they can stand in the way of the project at hand. Mitigating these challenges through a process which can be repeated serves to not only improve the project and it’s schedule, but also your work.

Many, if not all, of the examples that follow are technologies or methodologies that members of Infrared5 have used before, but they’re in no way a comprehensive list of what is available or a recommendation as to what to use. Figuring out what works for you, your team, and the project is an important piece of this repeatable process.

The most important thing you can do is to plan ahead. That may seem obvious, but it can be easy to overlook doing it properly. Planning doesn’t have to be absolute and it doesn’t have to be too granular, but the more you plan for the more you can prepare for. Have you had things go wrong in the past? Make sure you account for similar situations in any new plans you create! You’ll never be able to perfectly account for everything, but you can account for most things.

Decide which technologies you’ll use. Remember, use the right (or best available) tool for the job. Think about what your browser, device, and screen size support will be like for the project. What javascript framework or toolkit, like jQueryor Dojo, will you use, if any? Will you be supporting HTML5 and CSS3? Will you be using any external libraries at all? All of these considerations will be important throughout the project and it is best to decide upon them early and stick with them in order to maximize your efficiency.

Start with something and build up from there. You can utilize HTML5 BoilerplateBootstrap, or whatever other starting point you feel comfortable with. You can use Initializr help set up an entire project, if that’s what makes you feel comfortable. Personally, I like to start with just the 1140 CSS Grid and jQuery.

Have your content and designs ready before you need them. Also make sure your designs are done based on whatever grid you’ll be using for your layout so that you don’t have to retrofit the design to the layout. Getting a running start on a project is all well and good, but breaking the up the development with blockades can make everything take longer than it has to.

You’ve got lots of technology at your disposal, so make it work for you. Your layout grid should serve as a flexible base upon which you can build without constraint. Lean on it’s support for your layout needs when you can, including responsive resizing. Use LESS or Scss/Sass to style faster and in a more organized way with less repetition. Break your javascript up into self-contained, self-cleaning, interconnected modules to help reduce the chances of memory leaks and unexpected errors. Document your javascript as you go so that everyone on the project can easily keep informed of what’s going on. Finally, use an IDE that helps you be more efficient. For me that’s Espresso, but for other developers at Infrared5 it’s Sublime Text 2.

I want to expand upon the modules I mentioned in the previous paragraph. These modules allow you to break functionality into smaller pieces, focus on them, and then combine as a whole to get the job done. Think of it as a loose implementation of feature-driven development. This allows your code to be more organized, more readable, kept in context, and focused on in a feature-by-feature way which, for me, helps development get done quicker.

Another piece of the development process I cannot stress enough is to use version control software and use it often. I prefer git, because Keith Peters turned us all on to this process which has helped reduce conflicts while working with a team. Use whatever you like though, because at the end of the day it’s all so that you don’t lose any work you’ve done.

Separate your development cycles so that your brain isn’t trying to do too many things at once. Do your development, then any and all enhancements, and then whatever bugs there are. This process may need to go through several iterations, becoming something like the spiral model.

Lastly, wrap it up by condensing and publishing a final product. Minify and concatenate your javascript and css, obfuscate if you must, in order to wring every last drop of speed you’re able to get out of the browser. Publish whatever documentation you’ve built up for client use, if applicable. Hand it off. See? Not so difficult after all.