Templating with XSLT

Introduction

WebPal stores all app content as XML, and, offers XSLT transformations to process the content into HTML. This model allows you to maintain both content and templating instructions in a single user interface.

Even moreso, since XSL is itself a language that can be written in XML, it is maintained within the tree structure of a web site. There are a number of advantages to this combined storage of content and templates,  last not least being the ability to extend templates very easily.

This chapter assumes you have an basic knowledge of the XSL and the XSLT templating language. We recommend some reading at http://www.w3schools.com/xsl/

XSLT in WebPal

Below a visual diagram of how the UI maintains content (XML) and templates (XSL) jointly and the XSLT processor uses the XSL instructions to generate a blade template for the Laravel App to consume. So, in short, WebPal uses:

  1. XSL templates to transform static content as generated by the CMS, and subsequently,
  2. Laravel Blade templates to embed dynamic content generated by the application.

The webpal-core extension supplies a set of default XSL templates to the processor, which can be overridden by supplying additional templates with a higher priority value. This means that as a CMS developer, you can use XSLT templates to define how static content is rendered on the current page to display.

data?command=webpalimage.download&web_na

The Default Page Template

During the Transformation step of a page load, a new XSLT Processor object is created which is initialized with all XSL stylesheets and templates defined within the Site extensions. XSL stylesheets are added to an extension simply by selecting Insert new... > stylesheet for the extension node. 

Note
The XSLT processor simply concatenates all templates in the order as found in the Site XML. This generally does not affect the order in which templates are executed since XSL is a rule-based language and applies templates based on an order of specificity (most specific templates override less specific ones) and the priority attribute (see below).

After initializing, the processor by default applies templates starting with the root node of the site XML document, with is the top-level web node. Since this is in most cases not the desired behaviour, the Core::render() controller method injects the desired starting node using the $NODE parameter, and then applies the best matching template in the mode page-template.

  <xsl:template match="/" priority="0.1">
  <xsl:if test="$NODE">
   <xsl:for-each select="$NODE">
    <xsl:apply-templates select="." mode="page-template"/>
   </xsl:for-each>
  </xsl:if>
 </xsl:template>

Overriding Default Templates

To override the default page-template supplied by webpal-core, you can supply custom templates with a higher priority value, like so:

  1. insert a new extension to contain your custom templates, and create a stylesheet node in it
  2. define a template that matches a page node, and assign it a priority higher than 0.1

The template should generate the HTML for an entire page, including the html, body and head tags:

Page Navigation

Now let's create a couple of simple templates that render the pages in a two-column layout, and adds a nested list of navigation links in the left sidebar.

 <xsl:template match="/web/pages//page"
         mode="page-template"
         priority="0.5">
   <html lang="en">
     <head>
       <title>Page Title</title>
     </head>
     <body>
       <table>
         <tr>
           <td colspan="2">
             <h1>Basic WebPal Site</h1>
           </td>
         </tr>
         <tr>
           <td style="width:300px; vertical-align: top; padding: 10px;">
             <ul>
               <xsl:apply-templates select="/web/pages/page" mode="navigation"/>
             </ul>
           </td>
           <td style="width: 600px; padding: 20px;vertical-align: top; ">
             <xsl:apply-templates select="*"/>
           </td>
         </tr>
       </table>
     </body>
   </html>
 </xsl:template>

 <xsl:template match="page" mode="navigation">
   <xsl:param name="path" select="''"/>
   <li><a href="{$path}/{@name}"><xsl:value-of select="@name"/></a></li>
   <xsl:if test="page">
     <ul>
       <xsl:apply-templates select="page" mode="navigation">
         <xsl:with-param name="path" select="concat($path, '/', @name)"/>
       </xsl:apply-templates>
     </ul>
   </xsl:if>
 </xsl:template>

The first template defines a simple page with a table-based layout, and does two things:

  1. call a WebPal pre-defined macro render-content, which processes all page children
  2. processes the page structure in a different mode "navigation", which effectively calls the second template.

The second template recursively builds a simple navigation menu with links to all pages of the site. View the result by navigation to the "home" page and tapping "Preview".

Let's say that for all pages other than the home page, you want to use a 3-column layout. To achieve this without modifying your existing templates, you can add another template with higher specificity like so:

 <xsl:template match="page[@name != 'home']"
                mode="page-template">
   <html lang="en">
     <head>
       <title>Basic WebPal Site</title>
     </head>
     <body>
       <table>
         <tr>
           <td colspan="3">
             <h1>
                Basic WebPal Site
             </h1>
           </td>
         </tr>
         <tr>
           <td style="width:300px; vertical-align: top; padding: 10px;">
             <ul>
               <xsl:apply-templates select="/web/pages/page" mode="navigation"/>
             </ul>
           </td>
           <td style="width: 600px; padding: 20px;vertical-align: top; ">
             <xsl:call-template name="render-content"/>
           </td>
           <td style="width:300px; padding: 20px; vertical-align: top; ">
             <xsl:apply-templates select="/web//page[@name='common']/html"/>
           </td>
         </tr>
       </table>
     </body>
   </html>
 </xsl:template>

This template will take precedence over the default behaviour for all page nodes that don't have a @name of 'home' and results in this layout:

Obviously, the same can also be achieved in other ways. For example, the default behaviour can be defined as a 3-column layout, and only the home page uses a different template.

Template Priorities

You can define a template priority like so:

<xsl:template match="page" mode="page-template" priority="0.75">

The priority attribute ranges from 0.0 to 1.0, with 0.5 being the default. The default priority for all templates is 0.5. The webpal-core extension uses priority 0.1 for it's default templates.

Named Templates

The XSL standard prescribes that named templates of the form <xsl:template name="some-template"> need to be globally unique for the entire stylesheet.

Since there are a number of templates provided by other extensions, a naming convention of pre-pending the extension name is recommended, for example my-custom-ext--my-named-template.