Personal tools
You are here: Home Documentation Tutorials Making a website that uses layers to enable a mobile enabled version of the site

Making a website that uses layers to enable a mobile enabled version of the site

Note: Return to tutorial view.

As an example application we're going to develop a very simple site that uses macros and then later add a layer dedicated for mobile phone browsing without having to put in "if" statements in your templates to accommodate for the differences you'll want to apply.

Setting up the web page

We'll first write a normal Grok application from scratch that uses macros to put together the templates.

The first step is to install a new Grok project with grokproject. The name of the project is going to be called "Webpage":

$ grokproject Webpage
$ cd Webpage

There's no real code written yet but if you want to check that the setup worked you can start Zope with this command:

$ ./bin/zopectl fg

This will start Zope on port 8080 and the Grok administration interface should now be available at <http://localhost:8080/>. The next step is to get started coding. Go into the newly created package:

$ cd src/webpage

Start editing the file app.py and extend it with two more views so that it now looks like this:

import grok

class Webpage(grok.Application, grok.Container):
   pass

class Index(grok.View):
   pass # see app_templates/index.pt

class About(grok.View):
   pass

class HeaderFooter(grok.View):
   pass

The two new views need templates, so step out and go into the directory called app_templates. Add a template called headerfooter.pt and enter the following content:

<metal:block define-macro="standard"
><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Web page</title>
  <meta http-equiv="Content-Type"
      content="text/html; charset=utf-8" />
  <link rel="stylesheet" type="text/css"
        tal:attributes="href static/style.css" />
  <link rel="stylesheet" type="text/css" media="screen"
        tal:attributes="href static/screen.css" />
</head>
<body>
<metal:block define-slot="body">
<div id="header">
    <h1><span>Web page</span></h1>
    <hr />
</div>

<div id="content">
  <metal:block define-slot="content">
    walk all over me
  </metal:block>
</div>

<div id="footer">
    <p>&copy; Grok.
    <a href="#">Switch to normal web version</a>
    </p>
</div>

</metal:block>
</body>
</html>
</metal:block>

Now we need to change the index.pt template so that it references headerfooter, so change index.pt to look like this:

<html metal:use-macro="context/@@headerfooter/macros/standard">
<div metal:fill-slot="content">
  <h2>Welcome to Web page</h2>

  <p>This is the front page for the Web page.</p>

</div>
</html>

We'll also add another view called about.pt which looks like this:

<html metal:use-macro="context/@@headerfooter/macros/standard">
<div metal:fill-slot="content">
  <h3>About</h3>

  <p>This is an example Grok application that shows the magic of layers
  and what they can be used for.</p>

</div>
</html>

To anybody who has done Zope 2 development before with macros this should be very familiar. In the headerfooter.pt template we referenced a couple of external stylesheets. To add them you need to create a directory called static in the package sitting alongside the directory called app_templates:

$ mkdir static

In it, create a one CSS file called style.css with the following content:

body {
  font-family:"Lucida Grande",Geneva,Arial,sans-serif;
}
h1, h2, h3, h4 {
  color:#333333;
}

Add another stylesheet which is specific for the web called screen.css with the following content:

#content {
  padding:10px;
  background-color:#efefef;
}

#header h1 {
       width: 419px;
       height: 73px;
       background-image: url(webpage-logo.png);
       }
#header h1 span {
       display: block;
       width: 0;
       height: 0;
       overflow: hidden;
       }

We're using a image replacement technique here to load a fancy image to load a logo into our design, so you need an image in the static directory called webpage-logo.png

That's all you need to write a simple application in Grok. Now start Zope again and add an instance of this application. Call it app and check that everything is working on <http://localhost:8080/app> and <http://localhost:8080/app/about>

In the next step we'll add the layer for mobile phones.

Adding a layer

The layer we add will allow us to override and extend views in the default view.

NOTE: This has changed somewhat since grok 0.14. Defining a separate skin class with grok.Skin is no longer necessary. Take a look at: http://grok.zope.org/project/releases/0.14

A very common pattern when adding a mobile front end to your web application is to write a different layout more suitable for small mobile phone screens with slow connections and secondly go in and explicitly customize some important pages such that the functionality is more suitable for mobile phones. So that's what we'll do.

The first thing to do is to create a layer we'll call mobile so reopen the file app.py and add the following code:

from zope.publisher.interfaces.browser import IDefaultBrowserLayer

class MobileLayer(IDefaultBrowserLayer):
    pass

class MobileSkin(grok.Skin):
    grok.name('mobile')
    grok.layer(MobileLayer)

That's all you need! Now, to actually use this layer we'll need another view that "overrides" the view called HeaderFooter so we'll add that to the end of the file app.py:

class MobileHeaderFooter(grok.View):
    grok.name('headerfooter') # overriding name
    grok.layer(MobileLayer)

Now we need to actually write the macro that we'll use specifically for mobile phones. (Remember, there are many more ways this can be fine tuned and improved):

<metal:block define-macro="standard"
><?xml version="1.0" encoding="iso-8859-1"?><!DOCTYPE html PUBLIC
  "-//WAPFORUM//DTD XHTML Mobile 1.0//EN"
  "http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Web page (Mobile)</title>
  <meta http-equiv="Content-Type"
      content="text/html; charset=utf-8" />
  <link rel="stylesheet" type="text/css"
        tal:attributes="href static/style.css" />
  <link rel="stylesheet" type="text/css"
        tal:attributes="href static/mobile.css" />
</head>
<body>
<metal:block define-slot="body">
<div id="header">
  <metal:block define-slot="header">
    <h2>Web page</h2>
    <hr />
  </metal:block>
</div>

<div id="content">
  <metal:block define-slot="content">
    walk all over me
  </metal:block>
</div>

<div id="footer">
  <metal:block define-slot="footer">
    <p>&copy; Grok.<br />
    You are currently using the mobile version of this page.
    <a tal:attributes="href python:view.url().replace('++skin++mobile/','')"
      >Switch to normal web version</a>
    </p>
  </metal:block>
</div>

</metal:block>
</body>
</html>
</metal:block>

As you can see it's not very different but it's a good start when you start perfecting it. Let's also, for the sake of the exercise, override the index view as well as an example. First define it inside app.py:

class MobileIndex(grok.View):
    grok.name('index')
    grok.layer(MobileLayer)

And following this we of course need the page template to go with it so add mobileindex.pt:

<html metal:use-macro="context/@@headerfooter/macros/standard">
<div metal:fill-slot="content">
  <h2>Mobile Web page</h2>

  <p>Now with mobile specific front end.</p>

</div>
</html>

Restart Zope and try reaching the new mobile layer on <http://localhost:8080/++skins++mobile/app> and <http://localhost:8080/++skins++mobile/app/about>.

Publishing the layer

Views are registered with layers and layers are published by skins.

The most obvious way to differentiate which skin should be applied is to include /++skin++mobile/ in the URL or not. This works well and keeps things clear and obvious. When you deploy your web application you will probably put some sort of server in front of Zope like Apache, Nginx or Varnish. All of these support rewriting or proxying so for example, for www.webpage.com you redirect it like this:

RewriteEngine On
RewriteRule ^(.*) http://localhost:8080/app/++vh++http:%{SERVER_NAME}:80/++/$1 [P,L]

And following, for the other sub-domain m.webpage.com you redirect it like this:

RewriteEngine On
RewriteRule ^(.*) http://localhost:8080/++skin++mobile/app/++vh++http:%{SERVER_NAME}:80/++/$1 [P,L]

But suppose you don't want to use different URLs but instead sniff the HTTP user agent and depending on what you find you apply a different skin on the fly. The best way to do this properly is to use WURFL but for the sake of simplicity we'll do a very basic script.

The key to switching skin depending on certain parameters is to subscribe to the IBeforeTraverseEvent and write a clever function that does the actual business logic. Add the following to your app.py

from zope.app.publication.interfaces import IBeforeTraverseEvent
from zope.publisher.browser import applySkin

@grok.subscribe(Webpage, IBeforeTraverseEvent)
def handle(obj, event):
    if event.request.get('HTTP_USER_AGENT').find('Nokia') > -1:
        applySkin(event.request, MobileLayer)

Don't let the stupidity of the if statement and the strangeness of the zope imports frighten you. At least there's not a single underscore in this code. Apart from extending this with some smart WURFL code you can of course bake in some sort of checks on cookies. Another thing you might want to consider is to do a redirect. Plus WURFL can give you other pieces of information based on the HTTP user agent that you can use to your advantage. For example you can modify the request with information about the screen size when applying the layer and you would then use this information to show different sized thumbnails if your application has that.

Discussion

The tutorial is deliberately short to make it more approachable but there are of course lots of other interesting options to consider.

A lightweight alternative to WURFL is MUA which stands for "Mobile User Agent" and is a project that's come out of Google.

"Detects whether a given User-Agents HTTP header corresponds to a mobile phone, and attempts to extract the vendor/model from it. In Python."

Here's how you could use it:

from MobileUserAgent import parseUserAgent

@grok.subscribe(Webpage, IBeforeTraverseEvent)
def handle(obj, event):
    if parseUserAgent(event.request.get('HTTP_USER_AGENT')):
        applySkin(event.request, MobileLayer)

The objective of this tutorial is to show how to use layers to your advantage. A mobile layer is just one way of doing it of course. There are plenty other use cases. For example, you want to apply a different layer based on logged in user credentials. Or you have a complex CMS with expensive rendering and you want an alternative view that is much simpler and faster and can sustain being dugg.

Another example is when you want to use the same application an add a REST interface on top of it. A point worth making is that the MobileLayer we define in this example application inherits from IDefaultBrowserLayer. That means that it is able to fall back on all the default that comes out of the box of Grok such as the error page you get when you request a page that does not exist. An alternative to this is to create a layer that inherits from grok.IGrokLayer which means you have nothin to fall back on and this can be useful when, for example, you want to have complete control over the error messages and you don't let people request views that aren't defined in the layer.