Skip to content
This repository has been archived by the owner on Oct 17, 2021. It is now read-only.

Templating with Detector & Mustache Tutorial

dmolsen edited this page Aug 14, 2012 · 21 revisions

Detector can be used to develop a RESS-like solution similar to the one that Luke Wroblewski (@lukew) proposes in his Sept. 2011 article, Responsive Design + Server Side Components. I also wrote an article on the topic entitled, RESS, Server-Side Feature-Detection and the Evolution of Responsive Web Design

Requirements

For this tutorial my fork of mustache.php will be required. This is because my fork offers the ability to provide MustacheLoader a default directory to pull partials from. MustacheLoader reads in Mustache partials from the file system. You can find the code included in the Detector distribution in the web/demo/mustache/lib directory. You should also be familiar with the Mustache syntax.

If you're not familiar with Detector's family feature please review the tutorial.

The Problem: Tweaking Markup & Images Based on Browser Features

Let's say we have a web page that features a number of large images. While it's a responsive design (in this case, courtesy of Bootstrap 2) the layout isn't quite what we want on smaller devices. Also, we don't want to force users to download such large images over a cell network. If we can, we want to support older devices as well.

You can read on for an explanation of the solution or just check-out the source.

Quick Tangent: Previewing Family Templates

To view how a page will render for a particular set of templates you can simply add family=[familyname] to the end of the web address for the page you want to preview. Here is how we want our demo to render for each family:

The family attribute will be saved in session so any future request will reflect that choice. To clear it out simply add ?family=clear-family to the web address.

The Solution

Setting-up Detector & Mustache

The solution obviously requires us to load Mustache and Detector into our application. The nice thing about Detector is that, just by including it in your app, it automatically runs on page request and populates the variable $ua with all of the information Detector has on the requesting user-agent. This includes family.

// require mustache for the templates
require_once "../lib/mustache-php/Mustache.php";
require_once "../lib/mustache-php/MustacheLoader.php";

// require detector to get the family, autoloads the $ua var
require_once "../lib/Detector/Detector.php"; 

Getting Content

The next thing we'll have to do is get the content/data for the page. This should include standard copy that might appear across any of the partials. In our data, images would be an example of content that will be rendered differently based on family. For mustache.php the data is stored as an array. Other mustache implementations might use, for example, JSON. Please see the demo files for the full content.

$data = array(
    'title'       => 'Hello, World!',
    'description' => 'This extremely simple demo {...}',
    'link'        => '#',
    'images'      => array(
        array({...},'img'=>'images/automobile.jpg','img_sml'=>'images/automobile_sml.jpg',{...}),
        array({...},'img'=>'images/bus.jpg','img_sml'=>'images/bus_sml.jpg',{...}),
        array({...},'img'=>'images/train.jpg','img_sml'=>'images/train_sml.jpg',{...}),
    ),
);

Setting Up the Templates and Partials

It's important to get a visual for how our file system is laid out before understanding the templating set-up. Hopefully it provides a roadmap for the rest of the example:

index.php                 // our main page
templates/
    index.mustache        // the overall template for our main page
    partials/             // organized by families with 'base' as the default
        base/
            css.mustache
            html.mustache
        desktop/
            specialcss.mustache
        mobile-advanced/
            specialcss.mustache
        mobile-basic/
            css.mustache
            html.mustache
        mobile/
            [nothing]

Starting the Rendering

Rendering with with Mustache starts with an overall template for your page. Note that the main template cannot be "failed over" based on family name. The template must first be loaded into a variable:

$template = file_get_contents("templates/index.mustache");

Once we have the data and the main template loaded into variables it's time to load Mustache so we can render our template with its content. In the following bit of code Mustache() simply sets up an environment to parse the Mustache syntax. MustacheLoader() tells Mustache where to load partials from when the Mustache partial syntax ({{>partialName}}) is used.

// support Mustache syntax
$m = new Mustache();

// set-up where partials are loaded from. 
//   1st arg: where to look for a partial first. here it's looking in a dir that matches the family name
//   2nd arg: the extension for the template (e.g. partial.mustache)
//   3rd arg: where to look for a partial if it's not found in the first directory
$partials = new MustacheLoader("templates/partials/".$ua->family,"mustache","templates/partials/base");

And now we simply start the rendering of our main template:

print $m->render($template, $data, $partials);

Now the template is parsed for partials and this is when the magic happens related to families. Let's look at what's going on.

Rendering Family Specific Partials

Because of the way our fork to mustache.php works this templating solution can "failover" in templates. This means that families can share templates thereby limiting duplicative code. An example of this from the demo is the {{>css}} partial. Our main template starts off this way:

{{>html}}
   <head>
      <meta charset="utf-8">
      <title>Templating with Detector &amp; Mustache RESS Demo</title>

      <!-- Le styles -->
      {{>css}}

Neither the desktop family nor the mobile-advanced family have a css.moustache partial so mustache.php looks in the default directory (base) and uses the css.moustache file from there. The css.moustache file looks like this:

<link href="css/bootstrap.css" rel="stylesheet">
{{>specialcss}}
<link href="css/bootstrap-responsive.css" rel="stylesheet">

Both the desktop family and the mobile-advanced family are going to use Bootstrap's CSS but there are some special CSS tweaks for each family. In parsing this partial mustache.php will now look for the partial related to {{>specialcss}}. The great thing is that mustache.php will look in the family-related directory first even though it found the parent partial in the default directory. In this way we can nest partials and switch between default and family-specific partials as necessary.

Please note: The CSS could have been done with media queries. I'm simply using it as an example of nesting partials.

Serving Adaptive Images

One of the advantages to using a RESS system is the ability to serve appropriately sized media like images. In this demo we want to serve one style of image to desktop browsers, another to advanced mobile browsers, and, on lower class mobile browsers, a simple link with an accesskey. Our main template where we want to include the images looks like this:

<div class="row-fluid">
   {{>imagelist}}
</div><!--/row-->

The imagelist partial, found in the default partial directory, simply iterates over the images array from our $data variable above. The partial looks like:

{{#images}}
   {{>image}}
{{/images}}

Then, using the image partial found in each family's set of partials, Mustache renders an image tag or link to the image. I do want to point out how the different images are handled though. The image tag in the desktop partial looks like:

<img src="{{img}}" alt="{{alt}}" />

In the mobile-advanced partial the image tag looks like:

<img src="{{img_sml}}" alt="{{alt}}" />

Note the difference between the values for the src attribute. If you review the $data variable from earlier you'll see separate key:value pairs for those attributes. That's how, in the demo, we handle adaptive images. Alternatively, we could have had one {{img}} attribute that served as the ID for an image (e.g. auto) and then in each partial we could have supplied either the .jpg or _sml.jpg ending thus limiting duplicative information.

Even More Flexible Templating

This is a very quick tutorial showing how Detector can be combined with a templating engine like Mustache to create a RESS solution. To create an even more flexible solution you can use Detector's splitFamily config option. Rather than one failover (e.g. mobile-advanced directly to base), MustacheLoader can look for templates based on parts of the family name as well as the full family name. A quick example based on the family names we already have. They are:

  • mobile-advanced
  • mobile-basic
  • desktop

Both of the mobile-related families share "mobile" in their name. By setting splitFamily to true in the Detector config MustacheLoader will now do the following when looking for a partial:

  1. Look for the requested partial in the directory based on the family name, mobile-advanced
  2. If not found, explode the family name on "-" and drop the last item (e.g. "advanced")
  3. Look for the requested partial in the directory named "mobile"
  4. If not found, look for the requested partial in the default directory

You can nest specificity for families as deep as you want just by simply adding attributes separated by "-"s. Then MustacheLoader would just work its way up the chain until it found the correct shared partial. A good example of using this would be for retina images for mobile-advanced partials. Most devices will share the same partials but on the retina-capable devices you might want to push a custom image tag as its own partial. That partial could be in mobile-advanced-retina while the regular partial could be in mobile-advanced.

More Than Just a Mobile Solution

As I've noted in other tutorials this is more than just a way to handle mobile devices. Families and templating can be used with desktop browsers. We've used it for modifying markup for IE 7+8 versus other desktop browsers.