jQuery eZ publish Mobile pagelayout.tpl

This code has been updated - use the github link above to get the latest code.

Using the book Head First Mobile Web to learn and as a reference, I created a simple mobile interface for an existing eZ publish installation.

pagelayout.tpl

pagelayout.tpl includes three ‘pages’, content, search, and menu.

{*?template charset=utf-8?*}
<!DOCTYPE html>
<html manifest="/manifest.appcache">
<head>
<meta NAME="robots" CONTENT="noindex,nofollow" />
<meta http-equiv="content-language" content="en" />
<meta http-equiv="content-type" content="text/html;charset=UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css" />
<link rel="stylesheet" href="/design/mobile/stylesheets/core.css" />
<script src="http://code.jquery.com/jquery-1.6.4.min.js"></script>
<script src="http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js"></script>
{include uri="design:page_head.tpl" enable_link=false}
</head>
<body>
{default current_user=fetch('user','current_user')}
<div data-role="page" id="content">
        {include uri="design:header.tpl"}
        <div data-role="content">
                {$module_result.content}
        </div>
        {include uri="design:footer.tpl" page="content"}
        <div id="footer">
                <address>{"Powered by %linkStartTag eZ publish&reg; open source content management system %linkEndTag and development framework."|i18n("design/base",,hash('%linkStartTag',"<a href='http://ez.no'
>",'%linkEndTag',"</a>" ))}<br />Copyright &copy;  2006-{currentdate()|datetime('custom','%Y')} <b>{ezini('SiteSettings','MetaDataArray','site.ini').copyright}</b><br />
                </address>
        </div><!--/footer-->
</div><!--/content-->
<div data-role="page" id="search">
        {include uri="design:header.tpl"}
        <div data-role="content">
                {include uri="design:searchbox.tpl"}
        </div>
        {include uri="design:footer.tpl" page="search"}
</div><!--/search-->
<div data-role="page" id="menu">
        {include uri="design:header.tpl"}
        {cache-block keys=array('nav',$uri_string, $current_user.role_id_list|implode( ',' ), $current_user.limited_assignment_value_list|implode( ',' ))}
        <div data-role="content">
                {menu name="LeftMenu"}
        </div>
        {include uri="design:footer.tpl" page="menu"}
        {/cache-block}
</div><!--/menu-->
{/default}
</body>
</html>

There are several supporting templates.

searchbox.tpl

{*?template charset=utf-8?*}
<form action={"/content/search/"|ezurl} method="get">
<h2>{"Search"|i18n("design/standard/toolbar/search")}</h2>
<input class="searchinput" type="text" name="SearchText" id="SearchText" value="" placeholder="{'Search'|i18n('/design/standard/toolbar/search')}">
<input type="submit" value="{'Submit'|i18n('/design/standard/node')}" data-icon="search" data-iconpos="left" />
</form>

header.tpl


{*?template charset=utf-8?*}
<div data-role="header" data-position="fixed">
<a href="/" data-rel="back" data-icon="back" data-role="button">{'Back'|i18n('design/standard/node')}</a>
<h1>{'Example'|i18n('design/standard/node')}</h1>
<a href="http://example.com/?notmobile" data-rel="external" data-icon="home" data-role="button" data-theme="b" data-iconpos="right">{'Full Site'|i18n('design/st
andard/node')}</a>
</div>

footer.tpl

{*?template charset=utf-8?*}
{if not(is_set($page))}
{def $page='unknown'}
{/if}
<div data-role="footer" data-position="fixed">
<div data-role="navbar">
<ul>
<li><a href="/Home" data-icon="home" data-role="button">{'Home'|i18n('design/standard/node')}</a></li>
{if $page|ne('menu')}
<li><a href="#menu" data-icon="menu" data-role="button">{'Menu'|i18n('design/standard/node')}</a></li>
{/if}
{if $page|ne('search')}
<li><a href="#search" data-icon="search" data-role="button">{'Search'|i18n('design/standard/node')}</a></li>
{/if}
</ul>
</div>
</div>

menu/flat_left.tpl

{*?template charset=utf-8?*}
{let docs=treemenu( $module_result.path,
                    is_set( $module_result.node_id )|choose( 2, $module_result.node_id ),
                    ezini( 'MenuContentSettings', 'LeftIdentifierList', 'menu.ini' ),
                    0, 5 )
                    depth=1
                    last_level=0}
        <ul data-role="listview" data-inset="true">
        {section var=menu loop=$:docs last-value}
            {set last_level=$menu.last|is_array|choose( $menu.level, $menu.last.level )}
            {section show=and( $last_level|eq( $menu.level ), $menu.number|gt( 1 ) )}
                </li>
            {section-else}
            {section show=and( $last_level|gt( $menu.level ), $menu.number|gt( 1 ) )}
                </li>
                    {"</ul>
                </li>"|repeat(sub( $last_level, $menu.level ))}
            {/section}
            {/section}

            {section show=and( $last_level|lt( $menu.level ), $menu.number|gt( 1 ) )}
                {'<ul><li>'|repeat(sub($menu.level,$last_level,1))}
                <ul>
                    <li class="menu-level-{$menu.level}">
            {section-else}
                <li class="menu-level-{$menu.level}">
            {/section}
            {let menu_node=fetch('content','node',hash('node_id',$menu.id))}
              {if $menu_node.class_identifier|ne('link')}
                <a {$menu.is_selected|choose( '', 'class="selected"' )} href={$menu.url_alias|ezurl}>{$menu.text|shorten( 25 )}</a>
              {else}
                <a href={$menu_node.data_map.location.content|ezurl} target="_blank">{$menu.text|shorten( 25 )}</a>
              {/if}
            {/let}
            {set depth=$menu.level}
        {/section}
           </li>

        {section show=sub( $depth, 0 )|gt( 0 ) loop=sub( $depth, 0 )}
            </ul>
        </li>
        {/section}
        </ul>

{/let}

The site includes .mp3 files, so an HTML5 audio tag is included. The .mp3s are also offered as links for native device playback.

datatype/ezbinaryfile.tpl

{default icon_size='normal' icon_title=$attribute.content.mime_type icon='no'}
{if $attribute.has_content}
{if $attribute.content}
{if $attribute.content.original_filename|ends_with('.mp3')}
<audio controls="controls">
        <source src="{$fileurl|ezurl('no','full')}" type="audio/mp3" />
        Audio playback not available.
</audio>
<div class="break"></div>
{/if}
{switch match=$icon}
    {case match='no'}
        <a href={concat("content/download/",$attribute.contentobject_id,"/",$attribute.id,"/file/",$attribute.content.original_filename)|ezurl}>{$attribute.content.original_filename|wash(xhtml)}</a> {$attribute
.content.filesize|si(byte)}
    {/case}
    {case}
        <a href={concat("content/download/",$attribute.contentobject_id,"/",$attribute.id,"/file/",$attribute.content.original_filename)|ezurl}>{$attribute.content.mime_type|mimetype_icon( $icon_size, $icon_tit
le )} {$attribute.content.original_filename|wash(xhtml)}</a> {$attribute.content.filesize|si(byte)}
    {/case}
{/switch}
{else}
        <div class="message-error"><h2>{"The file could not be found."|i18n("design/base")}</h2></div>
{/if}
{/if}
{/default}

core.css is included to support the navigation. Any other CSS required for proper page display could be added.

core.css


/* CORE CSS 20040217 */

/* BODY */

/* PAGE NAVIGATION */

div.pagenavigator
{
    text-align: center;    
}

div.pagenavigator span.previous
{
    float: left;
}

div.pagenavigator span.next
{
    float: right;
}
div#footer
{
    padding: 10px 0 10px 0;
    text-align: center;
}

I used eZ 4.7 (community edition equivalent), which includes auto device detection. This works extremely well.

Notes

Naming the mobile siteaccess and enabling autodetection in site.ini.


settings/override/site.ini.append.php

[SiteAccessSettings]
CheckValidity=false
AvailableSiteAccessList[]=site
AvailableSiteAccessList[]=mobile
AvailableSiteAccessList[]=edit
ForceVirtualHost=true
MatchOrder=host
HostMatchType=map
HostMatchMapItems[]=domain.com;site
HostMatchMapItems[]=m.domain.com;mobile
HostMatchMapItems[]=edit.domain.com;edit
HostMatchMapItems[]=www.edit.domain.com;edit
HostMatchMapItems[]=www.domain.com;site
MobileSiteAccessList[]=mobile
MobileSiteAccessURL=http://m.domain.com
DetectMobileDevice=enabled

The manifest.appcache file is used to reduce bandwidth requirements and speed pages. In this case, the Home page, css, and jQuery are cached.

manifest.appcache


CACHE MANIFEST

NETWORK:
content/search*
*

CACHE:
Home
design/mobile/stylesheets/core.css
http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.css
http://code.jquery.com/jquery-1.6.4.min.js
http://code.jquery.com/mobile/1.1.0/jquery.mobile-1.1.0.min.js

You will probably need to add or update a RewriteRule to ensure proper delivery of the manifest.appcache file:

RewriteRule ^(robots\.txt|favicon\.ico|manifest\.appcache)$ - [L] 

It is a good idea to keep the mobile site out of search engines. This RewriteRule will allow you to deliver a different robots.txt file.

Excerpt from .htaccess

RewriteCond %{HTTP_HOST} ^m\..*
RewriteRule ^robots\.txt$ m.robots.txt [L]

m.robots.txt

User-agent: *
Disallow: /