Cascade + Bootstrap = Awesome [Webinar and Walkthrough]

By Charlie Holder — Thursday, April 2nd, 2015 at 9:00am
Cascade + Bootstrap = Awesome [Webinar and Walkthrough]

This is a walkthrough of the material covered in our latest webinar "Cascade + Bootstrap = Awesome," presented by Charlie Holder. If you're interested in additional material, you can watch a recording of the webinar, download a copy of the slides, or check out the Bootstrap Site on our Cascade Server Exchange.

Cascade + Bootstrap = Awesome

In 2014, Litmus reported that 51% of emails in 2013 were opened on mobile devices. That same year, Business Insider published findings that roughly 60% of social media time is spent on smartphones or tablets. As of January 2014, the Pew Research Internet Project reports that 58% of American adults have a smartphone and 42% of American adults own a tablet computer.

Mobile traffic is growing and users want the same kind of experience on their smaller device as they get on their desktop computer. But how do you give it to them? How do you create a single website that works on both traditional PCs and emerging technologies?

Responsive web design (RWD) is an approach that strives to provide an ideal viewing experience for a wide variety of devices. A site designed with RWD principles attempts to respond to each device accessing the content and adapt the layout to fit the user's display.

In this blog post, I want to highlight how to leverage the power of Cascade Server to help create and manage content for a responsive website.

In order to develop a responsive site, concepts such as fluid grids, percentage units, flexible images, and CSS3 media queries are used. These concepts can be a lot to take in for a developer creating their first site using RWD principles or an end user responsible for managing the content styled by the site. Fortunately for the design and development communities, front-end frameworks for creating responsive websites emerged.

There are many frameworks to help with the development of a responsive site -- Foundation, HTML5 Boilerplate, Gumby, and Skeleton to name a few. For this post, I'll be using Bootstrap.

Introduction to Bootstrap

Like many of the frameworks, Bootstrap offers a fluid (or fixed) grid system that scales as the device size changes. Bootstrap's grid system is based on 12 columns and includes predefined classes for easy layout options. For example, creating a 3-column layout is done like this:

<div class="container">
 <div class="row">
   <div class="col-xs-12 col-md-4"></div>
   <div class="col-xs-12 col-md-4"></div>
   <div class="col-xs-12 col-md-4"></div>
 </div>
</div>

This means that on medium devices (a viewport area ≥ 992px), content will automatically be structured into 3 columns of equal width. The 12 columns are totaled up by adding 4 + 4 + 4.

Similarly, you could weight the columns in either direction to create a 2-column layout:

<div class="container">
 <div class="row">
   <div class="col-xs-12 col-md-3"></div>
   <div class="col-xs-12 col-md-9"></div>
  </div>
</div>

This example creates two columns on medium devices, the first with 25% (3/12) of the available width and the second with 75% (9/12) of the available width.

In both examples, however, as the device size decreases toward extra small (a viewport area <768px), the layout automatically adapts and collapses all columns into a single column layout spanning all 12 columns. This functionality is handled by the CSS3 media queries which change how much weight each column gets when rendered on a particular device.

The question now becomes, how do you structure your Cascade Server implementation to easily allow content contributors to create and edit responsive content? And the answer lies within the power of the Data Definition and Format. We know that Bootstrap does most of the heavy lifting to create the layout and adapt to any changes in device size. The important HTML markup is the classes on each content column. Those classes will be created dynamically in Cascade Server.

Basic Examples

One method for providing content contributors a simple interface for managing their content is to allow them to select how many columns they want and progressively show them that number of WYSIWYGs:

<system-data-structure>
 <text type="dropdown" identifier="number" label="Number of Columns">
   <dropdown-item value="1" show-fields="column1"/>
   <dropdown-item value="2" show-fields="column1, column2"/>
   <dropdown-item value="3" show-fields="column1, column2, column3"/>
   <dropdown-item value="4" show-fields="column1, column2, column3, column4"/>
 </text>
 <text wysiwyg="true" identifier="column1" label="Column 1"/>
 <text wysiwyg="true" identifier="column2" label="Column 2"/>
 <text wysiwyg="true" identifier="column2" label="Column 3"/>
 <text wysiwyg="true" identifier="column4" label="Column 4"/>
</system-data-structure>

As users select a value from the dropdown, the number of WYSIWYGs they're presented with changes. Each additional WYSIWYG is labeled to identify content designated for a separate column.

To output the content from the Data Definition to a Page region, we use a Format that takes note of how many columns the user wanted, dynamically calculates the column width, and creates each HTML content column.

#set ( $data = $page.getChild("system-data-structure") )
#set ( $number = $data.getChild("number").value )
<div class="container">
 <div class="row">
 ## Select all WYSIWYGs that have content.
 #set ( $wysiwygs = $_XPathTool.selectNodes($data, "*[contains(name(), 'column')][node()]") )
 ## Calculate the column size for the grid.
 #set ( $width = 12 / $_MathTool.toInteger($number) )
 #foreach ( $wysiwyg in $wysiwygs )
   <div class="col-xs-12 col-md-${width}">
     $_SerializerTool.serialize($wysiwyg, true)
   </div>
   ## Limit how many WYSIWYGs get displayed.
   #if ( $foreach.count == $number )
     #break
   #end
 #end
 </div>
</div>

This example hardcodes each WYSIWYG as a separate element in the Data Definition and progressively displays them based on the user's choice in the dropdown. This means that we may end up with empty WYSIWYG elements that need to be excluded when creating the content columns.

An alternative to this method relies on the ability to dynamically repeat the WYSIWYG element inside the Data Definition. This differs from the first example in that there is no dropdown menu to choose a number of columns with. The number of WYSIWYGs created by the author signifies the number of columns that will be created. (Administrators should set a maximum of 4 in the Advanced settings of the Data Definition element to ensure that the number is evenly divisible into 12.)

Here is what the Data Definition would look like:

<system-data-structure>
   <text wysiwyg="true" identifier="column" label="Content Column" multiple="true" maximum-number="4"/>
</system-data-structure>

As more columns are added, the "Content Column" label will start to number itself and provide an indication of how many have been added and which one the user is currently interacting with.

The corresponding Format for this example would be:

#set ( $data = $page.getChild("system-data-structure") )
#set ( $wysiwygs = $_XPathTool.selectNodes($data, "column") )
#set ( $width = 12 / $wysiwygs.size() )
<div class="container">
 <div class="row">
 #foreach ( $wysiwyg in $wysiwygs )
   <div class="col-xs-12 col-md-${width}">
     $_SerializerTool.serialize($wysiwyg, true)
   </div>
 #end
 </div>
</div>

This Format differs in a few ways from the previous example. There will never be additional column elements to handle. The number of column elements in the Page XML will always match the number of WYSIWYGs that are visible. For this reason we won't need to #break out of the #foreach loop either. The XPath expression that selects the WYSIWYGs is less complicated as well. We simply select all of column elements.

Adding Rows

Let's take a step forward and add some more functionality. We'll nest this repeatable WYSIWYG inside a repeatable group. Are you still with me? In the first example we only gave users 4 options to choose from -- 1 column, 2 columns, 3 columns, or 4 columns. What if we also allowed them to choose how many rows they wanted (max of 4)? By nesting the repeatable WYSIWYG within a repeatable group we gain the ability to create many more layouts with a single Data Definition and Content Type -- 337 to be exact!

 <system-data-structure>
 <group identifier="row" label="Row" multiple="true" maximum-number="4">
  <text wysiwyg="true" identifier="column" label="Content Column" multiple="true" maximum-number="4"/>
 </group>
</system-data-structure>

Just like before, adding more rows causes Cascade Server to begin numbering the elements to indicate how many have been created and which one the user is currently working within.

Our Format changes slightly, but for the most part it's the same code as before:

#set ( $data = $page.getChild("system-data-structure") )
#set ( $rows = $_XPathTool.selectNodes($data, "row") )
<div class="container">
#foreach ( $row in $rows )
 <div class="row">
   #set ( $wysiwygs = $_XPathTool.selectNodes($row, "column") )
   #set ( $width = 12 / $wysiwygs.size() )
   #foreach ( $wysiwyg in $wysiwygs )
     <div class="col-xs-12 col-md-${width}">
     $_SerializerTool.serialize($wysiwyg, true)
     </div>
   #end
 </div>
 ## Use a horizontal rule to provide a border between rows.
 #if ( $foreach.hasNext )
   <hr/>
 #end
#end
</div>

Instead of only looping through a dynamic number of columns, we apply similar logic and loop through our dynamic number of columns as we loop through a dynamic number of rows at the same time. In the end there can be up to 4 rows each of which can have 1 to 4 columns.

Do you think we can push a little further?

Adding Other Types of Content

The WYSIWYG is great. It's a familiar face to the majority of content contributors. But there are other types of content than just styled text. What if users want to insert media? What if they want a column heading? What if they want to reuse content from somewhere else in the system? For my last example I'll add additional options for including other types and sources of content.

We're about to add a few more options to the Data Definition, but we don't want to overcomplicate it. It needs to stay useable and easy to understand. In order to do that I'm going to make use of Cascade Server's Smart Fields. I'll offer the user options for the type of content they want to include in the column and progressively display fields for entering it.

<system-data-structure>
 <group identifier="row" label="Row" multiple="true" maximum-number="4">
  <group identifier="column" label="Column Content" multiple="true" maximum-number="4">
   <text type="checkbox" identifier="features" label="Features">
    <checkbox-item value="Heading" show-fields="row/column/heading"/>
    <checkbox-item value="Image+Link" show-fields="row/column/image, row/column/alt, row/column/type"/>
    <checkbox-item value="WYSIWYG" show-fields="row/column/content"/>
    <checkbox-item value="Block" show-fields="row/column/block"/>
   </text>
   <text identifier="heading" label="Heading" required="true"/>
   <asset type="file" identifier="image" label="Select Image" required="true"/>
   <text identifier="alt" label="Image Alt Text" required="true"/>
   <text type="dropdown" identifier="type" label="Link Type" default="None">
    <dropdown-item value="None"/>
    <dropdown-item value="Internal" show-fields="row/column/internal"/>
    <dropdown-item value="Custom" show-fields="row/column/custom"/>
   </text>
   <asset type="page,file,symlink" identifier="internal" label="Image Link"/>
   <text identifier="custom" label="Custom URL"/>
   <text wysiwyg="true" identifier="content" label="Content" required="true"/>
   <asset type="block" identifier="block" label="Block Content" render-content-depth="2" required="true"/>
  </group>
 </group>
</system-data-structure>

This gives the content author options for a text heading, an image with optional link, WYSIWYG content, and reusable Block content.

Since the content features are all optional we'll need to check to see which ones were selected before trying to output that content through the Format. We'll make a slight adjustment to the XML structure and include a few #if checks for proper handling.

#set ( $data = $page.getChild("system-data-structure") )
#set ( $rows = $_XPathTool.selectNodes($data, "row") )
<div class="container">
#foreach ( $row in $rows )
 <div class="row">
   #set ( $columns = $_XPathTool.selectNodes($row, "column") )
   #set ( $width = 12 / $columns.size() )
   #foreach ( $column in $columns )
     <div class="col-xs-12 col-md-${width}">
     ## Did they select Heading from the features list?
     #if ( $_XPathTool.selectSingleNode($column, "features[value='Heading']/value") )
       #set ( $heading = $column.getChild("heading") )
       <h2>$_EscapeTool.xml($heading.value)</h2>
     #end
     ## Did they select Image+Link from the features list?
     #if ( $_XPathTool.selectSingleNode($column, "features[value='Image+Link']/value") )
       #set ( $image = $_XPathTool.selectSingleNode($column, "image/link").value )
       #set ( $alt = $column.getChild("alt").value )
       ## Which type of link did they select?
       #if ( $column.getChild("type").value == "Internal" )
         #makeImageLink($_XPathTool.selectSingleNode($column, "internal/link").value, $image, $alt, "img-responsive")
       #elseif ( $column.getChild("type").value == "Custom" )
         #makeImageLink($_XPathTool.selectSingleNode($column, "custom").value, $image, $alt, "img-responsive")
       #else
         #makeImage($image, $alt, "img-responsive")
       #end
     #end
     ## Did they select WYSIWYG from the features list?
     #if ( $_XPathTool.selectSingleNode($column, "features[value='WYSIWYG']/value") )
       #set ( $content = $column.getChild("content") )
       $_SerializerTool.serialize($content, true)
     #end
     ## Did they select Block from the features list?
     #if ( $_XPathTool.selectSingleNode($column, "features[value='Block']/value") )
       #set ( $block = $_XPathTool.selectSingleNode($column, "block/content") )
       $_SerializerTool.serialize($block, true)
     #end
     </div>
   #end
 </div>
 #if ( $foreach.hasNext )
   <hr/>
 #end
#end
</div>
#macro ( makeImage $imgSrc $imgAlt $imgClass )
 #set ( $imgSrc = $_EscapeTool.xml($imgSrc) )
 #set ( $imgAlt = $_EscapeTool.xml($imgAlt) )
 #set ( $imgClass = $_EscapeTool.xml($imgClass) )
 <img src="${imgSrc}" alt="${imgAlt}" class="${imgClass}" />
#end
#macro ( makeImageLink $linkHref $imageSrc $imageAlt $imageClass )
 #set ( $linkHref = $_EscapeTool.xml($linkHref) )
 <a href="${linkHref}">#makeImage($imageSrc, $imageAlt, $imageClass)</a>
#end

In addition to the 337 possible layouts that are available with this single Data Definition (and corresponding Configuration Set and Content Type), each possible content column/cell would have 15 different combinations of content that could be entered. Suffice to say that the possible configurations for content on the page are endless!

Recap

Cascade Server's powerful Data Definitions give you the ability to provide a large number of different page layouts and content configurations from a single Template, Configuration Set, Content Type, and Format. Adding in Smart Fields to provide additional options for configuring content keeps things simple while still offering maximum flexibility.

Have questions about this post? Contact us any time! Want a version in XSLT? Download a standalone Site that contains all of these examples in both Velocity and XSLT! Never forget that you can use either language at any time. You can even use both at the same time on the same page if needed!

Subscribe to our blog via RSS Download RSS

Hannon Hill Corporation

3423 Piedmont Road NE, Suite 520
Atlanta, GA 30305

Phone: 678.904.6900
Toll Free: 1.800.407.3540
Fax: 678.904.6901

info@hannonhill.com

GSA Contract Holder

JOIN OUR MAILING LIST

CONTACT US