Building responsive web pages with the Enonic STK

A responsive web site adapts to the viewing environment, with the ambition of providing an optimal viewing experience for any given client. With the STK, building responsive web sites on Enonic CMS is easy.

Defining the grid

The responsive grids are specified in the theme.xml configuration file. As always, a page is built by placing portlets in regions defined in a page template, and the grouping of regions (which form some or all of the responsive grid) is defined in the theme.xml.

The first example is defining a layout consisting of only one region (named "r1-c1", short for "row1-column1"). This is a 12 column grid, and the region is supposed to fill the entire width of this.

<device-class name="desktop,unknown">
    <layout name="default">
        <row cols="12">
            <region name="r1-c1">
                <cols>12</cols>
            </region>
        </row>
    </layout>
</device-class>

Below we have a more realistic example, consisting of two rows, where the first row is split into two regions (8 and 4 columns wide), and where the second row is full width.

<device-class name="desktop,unknown">
    <layout name="default">
        <row cols="12">
            <region name="r1-c1">
                <cols>8</cols>
            </region>
            <region name="r1-c2">
                <cols>4</cols>
            </region>
        </row>
        <row cols="12">
            <region name="r2-c1">
                <cols>12</cols>
            </region>
        </row>
    </layout>
</device-class>

In some cases we need a different grid for one or more pages on the web site. As an example, the front page might have a completely different layout than the rest of the pages on the site. To accomplish this, define separate layouts with the grid you need for each, as follows:

<device-class name="desktop,unknown">
    <layout name="default">
        <row cols="12">
            <region name="r1-c1">
                <cols>8</cols>
            </region>
            <region name="r1-c2">
                <cols>4</cols>
            </region>
        </row>
        <row cols="12">
            <region name="r2-c1">
                <cols>12</cols>
            </region>
        </row>
    </layout>
    <layout name="frontpage">
        <row cols="12">
            <region name="r1-c1">
                <cols>4</cols>
            </region>
            <region name="r1-c2">
                <cols>4</cols>
            </region>
            <region name="r1-c3">
                <cols>4</cols>
            </region>
        </row>
        <row cols="12">
            <region name="r2-c1">
                <cols>6</cols>
            </region>
            <region name="r2-c2">
                <cols>6</cols>
            </region>
        </row>
    </layout>
</device-class>

As a different region definition requires a different page template, it is recommended to define one page template XSL per unique layout. To avoid redefining the entire page template HTML for each layout, it is recommended to define the common HTML in one XSL ("page-includes.xsl"), and the region definitions in separate XSL files importing the common HTML. One example of this is listed below:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet exclude-result-prefixes="#all" version="2.0" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"   
    xmlns:portal="http://www.enonic.com/cms/xslt/portal"
    xmlns:stk="http://www.enonic.com/cms/xslt/stk">
    
    <xsl:import href="page-includes.xsl"/>
    
    <!-- page type -->
    <!-- For multiple layouts on one site. Various layouts can be configured in theme.xml, each with a different 'name' attribute on the 'layout' element. -->
    <xsl:param name="layout" as="xs:string" select="'default'"/>
    
    <!-- regions -->
    <xsl:param name="r1-c1">
        <type>region</type>
    </xsl:param>
    
</xsl:stylesheet>

Complex grids

In some cases a regular grid won't suffice, for example if one region is supposed to fill the left part of the screen, and several other regions is supposed to form a grid of it's own on the right part of the screen. For handling this, the regions can be grouped as illustrated below. This simply adds another wrapping element to the HTML generated. Note that widths are relative to their parent in the grid definition. In a 9 column grid, a region with a width of 9 placed inside a group of width 4 will fill 100% of the available space (defined by it's parent).

<layout name="frontpage">                
    <row cols="9">
        <group cols="4">
            <region name="r1-g1-c1">
                <cols>9</cols>
            </region>
        </group>
        <group cols="5">
            <region name="r1-g2-c1">
                <cols>4</cols>
            </region>
            <region name="r1-g2-c2">
                <cols>5</cols>
            </region>
            <region name="r1-g2-c3">
                <cols>9</cols>
            </region>
            <region name="r1-g2-c4">
                <cols>4</cols>
            </region>
            <region name="r1-g2-c5">
                <cols>5</cols>
            </region>
        </group>        
    </row>
</layout>

The CSS

Based on the grid definition in the theme.xml, the regions are created by the STK with class names reflecting their cols attribute. A 12 column region will be given a "span-12" class name, a 6 column region will be given a "span-6" class name, and so on.
For the actual CSS to be calculated, the total grid width (number of columns) has to be defined in the theme's "responsive.less" file, located under the "_public" directory (in the current theme).

Note that the actual class definition of the column widths is created automatically by the STK. For example, ".span-6" in a 12 column grid will be given a (default) width of 50%. 

/* Modify this to reflect the number of columns in your grid */
@grid-columns: 12;

/* The STK will then automatically generate the following CSS */

.span-12 { width:100% }
.span-11 { width:91.66666666666666% }
.span-10 { width:83.33333333333334% }
.span-9 { width:75% }
.span-8 { width:66.66666666666666% }
.span-7 { width:58.333333333333336% }
.span-6 { width:50% }
.span-5 { width:41.66666666666667% }
.span-4 { width:33.33333333333333% }
.span-3 { width:25% }
.span-2 { width:16.666666666666664% }
.span-1 { width:8.333333333333332% }

Responsive breakpoints

You are of course free to override these widths in order to make the regions behave responsive, as illustrated in the example below. Here we want all columns to fill the entire width on mobile devices.

@media (max-width: 580px) {
    .span-6, .span-3 {
        width: 100%;
    }   
}

/* This can be simplified by defining a LESS variable for each device */
@device-mobile: ~'(max-width: 580px)';

@media @device-mobile {	
	.span-6, .span-3 {
		width: 100%;
	}	
}

Image handling

For avoiding large images being used on smaller screens, the STK serves a range of image sizes, where the optimal size is chosen for it's current use. Described in short, each image element contains several image URLs of prescaled versions, ranging from 64 pixels to 2048 pixels wide. The closest prescaled version is used, if the image is supposed to be displayed as 900 pixels wide, the 1024 pixel version is used (scaling down in the browser). On a mobile device the image size might be 100 pixels, then the 128 pixel version is used.

Most of this is done automatically by the STK, just make sure that the following JavaScript runs:

$(function() {
    STK.responsive.optimizeImages();    
});

Before the appropriate image is loaded, the STK inserts a transparent placeholder image with the same aspect ratio. This is done to avoid content "jumping" when the actual images appear, which might be visible on slow bandwidth speeds. As a "no JavaScript" fallback, a 300 pixel version of the served image is placed in a noscript element.

These predefined sizes are set by the STK because they match the prescaled versions created by the CMS when an image is uploaded. If for some reason other sizes are required, these can be defined in the theme.xml as shown below:

<device-classes>
    <device-class name="all">
        <image>
            <prescaled>
                <size>64</size>
                <size>128</size>
                <size>256</size>
                <size>512</size>
                <size>1024</size>
            </prescaled>
        </image>
    </device-class>
</device-classes>

Dealing with iframes

The use of iframes on a responsive web site might be problematic, but sometimes the use of iframes is required nonetheless. The STK provides a short JavaScript for letting iframes behave better in a responsive setting (the script wraps the iframe making it scale in size without breaking the aspect ratio).

$(function() {
    STK.responsive.optimizeIframes($('.youtube.video'));    
})