-
Notifications
You must be signed in to change notification settings - Fork 80
Templating with Detector & Mustache Tutorial
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
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.
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.
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 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";
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',{...}),
),
);
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]
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.
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 & 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.
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.
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:
- Look for the requested partial in the directory based on the family name,
mobile-advanced
- If not found, explode the family name on "-" and drop the last item (e.g. "advanced")
- Look for the requested partial in the directory named "mobile"
- 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
.
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.