CSS3 and HTML5 for a Better Author Experience

Introduction

Why are we here?

It's all about the users!

We have two types of users that we need to account for when we implement websites:

  • End-users
  • CMS-users (content authors/editors)

When we build websites, we account for both kinds of user — but usually the back-end (Tridion) developers focus on the CMS user, while the front-end developers focus primarily on the end-user.

The Problem is the Balance

If front-end developers focus exclusively on the end-user experience, without considering the content-author experience, this can create a lot of strain on the Tridion developer and the Tridion user.

If a Front-end developer codes, without thinking about thinking about how content will be managed, this leads to problems:

  • Inconsistent markup for the same content -> too many Component Templates
  • Too many divs -> developer gets lost in code, contracts 'divitis', perpetuates drinking problem
  • Too many divs -> site isn't SEO friendly or accessible
  • Assuming a container always has content -> blank spaces and weird gaps
  • Hard-coded classes for layout -> no flexibility in adding removing items
  • Hard-coded background images and colors -> no flexibility in changing content areas
  • Every jQuery plugin ever -> content author can't change features in a plugin

The solution is in the code content

  1. Tell the FE dev about the content models (schemas)
  2. Collect authoring rules before writing code
  3. Accept FE code that's flexible, not fixed

We're here today to talk about #3. You're on your own for those first two.

HTML

Introducing HTML5 Semantics

Grouping sets of content

<!-- Put the things unique to this specific page in Main. -->
<main>
  <nav>;Navigation, for either a section, or the whole site</nav>;
  <aside>Content not related to other stuff in my container</aside>
  <section><!-- I'm for a collection of similar pieces of content --></section>
</main>

Classifying the kinds of content

<article><!--I'm syndicatable content --></article>
<figure>
    <!-- I make the content better and easier to understand. 
    It doesn't matter where you put me -->
    <figcaption>explanatory caption</figcaption>
    
</figure>

Enhancing sections of content, and content

<article><!--I'm syndicatable content -->
    <header>
    <h1>A title for this piece of content</h1>
    <h1>spiffy subtitle</h1>
    </header>
    <div>
    All the amazing content goes here
    </div>
<footer>A Conclusion for the content. It was written by Frank. </footer>
</article>

Uncertain as to which semantic element you should use?

There's actually a great flowchart on the internet that you can review.

HTML

HTML5-inspired content modelling

What does semantic markup mean for front end?

A web page is no longer a clump of content

A web page contains collections of content that relate to each other in a meaningful way.

<section class="featuredProducts">
    <header>
        <h1>Check out these amazing products</h1>
    </header>
    <article class="product">
        <h1 class="product__title">Premium Widget ™</h1>
        <p>This premium widget is superior to the regular widget</p>
        <div class="product__cost">$25.99</div>
        <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
    </article>
    <article class="product">
        <h1 class="product__title">Premium Widget ™</h1>
        <p>This premium widget is superior to the regular widget</p>
        <div class="product__cost">$25.99</div>
        <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
    </article>
    <article class="product">
        <h1 class="product__title">Premium Widget ™</h1>
        <p>This premium widget is superior to the regular widget</p>
        <div class="product__cost">$25.99</div>
        <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
    </article>
</section>
<aside class="recommendations">
    <article class="product">
        <h1 class="product__title">Yesterday's Widget ™</h1>
        <p>Yep, it's a widget we're trying to get rid of</p>
        <div class="product__cost">$15.99</div>
        <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
    </article>
    <article class="product">
        <h1 class="product__title">Last year's Widget ™</h1>
        <p>Please buy this widget. Please</p>
        <div class="product__cost">$.99</div>
        <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
    </article>    
</aside> 

Check out these amazing products

Premium Widget ™

This premium widget is superior to the regular widget

$25.99
widget

Premium Widget ™

This premium widget is superior to the regular widget

$25.99
widget

Premium Widget ™

This premium widget is superior to the regular widget

$25.99
widget

HTML

Dediving templates and views

Let semantic markup drive meaningful schema design

  • header and footer can be embedded schemas
  • figure can be a multimedia schema — embedded or content
  • aside can be a single unit of content, or a collection of content, that isn't directly related to the stuff nearby. This is ideal for user targetted content.
  • nav can be any collection of link lists
  • section can be any component that contains component links

It's about relationships

A single unit of content can stand on its own, but it can still have a relationship to other pieces of content on the page.

Teach content authors about these semantic containers so that they can form meaningful relationships between blocks of content.

CSS

Conditional Content Conundrums

Going from 1 to 2 columns: HTML

When making a container that can flex between one and two columns, put the optional column first.

The block of content that is always present should be placed at the end of the container.

First, Some HTML

<main class="demo2">
<aside class="recommendations sidebar">
    <article class="product">
        <h1 class="product__title">Yesterday's Widget ™</h1>
        <p>Yep, it's a widget we're trying to get rid of</p>
        <div class="product__cost">$15.99</div>
        <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
    </article>
    <article class="product">
        <h1 class="product__title">Last year's Widget ™</h1>
        <p>Please buy this widget. Please</p>
        <div class="product__cost">$.99</div>
        <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
    </article>    
</aside>
<section class="content featuredProducts">
    <header>
        <h1>Check out these amazing products</h1>
    </header>
    <article class="product">
        <h1 class="product__title">Premium Widget ™</h1>
        <p>This premium widget is superior to the regular widget</p>
        <div class="product__cost">$25.99</div>
        <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
    </article>
    <article class="product">
        <h1 class="product__title">Premium Widget ™</h1>
        <p>This premium widget is superior to the regular widget</p>
        <div class="product__cost">$25.99</div>
        <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
    </article>
    <article class="product">
        <h1 class="product__title">Premium Widget ™</h1>
        <p>This premium widget is superior to the regular widget</p>
        <div class="product__cost">$25.99</div>
        <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
    </article>
</section>


1 to 2 columns: CSS

The sibling selectors are a great CSS2 approach to detecting whether an element is present.

  1. Style the main container first
  2. Style the sidebar second
  3. Add the conditional style last
main.demo2 {
width: 600px;
margin: 0 auto;
}
.demo2 .sidebar {
display: block;
float: left;
width: 25%;
height: 100%;
background: #efefef;
}
.demo2 .content {
display:block;
width: 100%;
height: 100%;
outline: 2px solid red;
}
.demo2 .sidebar + .content {
width: 75%;
float: left;
}


1 to 2 columns: example

Check out these amazing products

Premium Widget ™

This premium widget is superior to the regular widget

$25.99
widget

Premium Widget ™

This premium widget is superior to the regular widget

$25.99
widget

Premium Widget ™

This premium widget is superior to the regular widget

$25.99
widget

The Big Idea

Sibling selectors are great for detecting whether one or two elements are present.

The problem is that the sibling selectors only let you select the last element that's there.

CSS

Counting Content Blocks

New Pseudo classes

We can select any nth element.

/*the fifth element*/
.demo3 :nth-child(5) {
outline: 2px solid orange;
}
/*Even numbered .products of the same element type*/
.demo3 .product:nth-of-type(even) {
background: #fff;
}
/* a .product of the same element that's a multiple of 3*/
.demo3 .product:nth-of-type(3n) {
color: #c0ffee;
}
/*A multiple of four, starting from the last one*/
.demo3 .product:nth-last-of-type(4n) {
font-weight: bold;
}

We can combine pseudo classes to count the number of elements in a container

.demo3 .product:nth-last-of-type(2):first-of-type,
.demo3 .product:nth-last-of-type(2):first-of-type ~.product {
width: 50%;
}
.demo3 .product:nth-last-of-type(3):first-of-type,
.demo3 .product:nth-last-of-type(3):first-of-type ~.product {
width: 33%;
}
.demo3 .product:nth-last-of-type(4):first-of-type,
.demo3 .product:nth-last-of-type(4):first-of-type ~.product {
width: 25%;
}


How does this affect my your template development?

You can…
  • Adjust dimensions of elements based on the number that appear on the page
  • Create zebra-stripe patterns unique to whether you have even or odd numbered rows or columns
  • Produce layouts that can adapt to the length of the content items on them

… without writing any logic in your templates at all.

There's also a more in depth blog post on this subject

Check out these amazing products

Premium Widget ™

This premium widget is superior to the regular widget

$25.99
widget

Premium Widget ™

This premium widget is superior to the regular widget

$25.99
widget

Premium Widget ™

This premium widget is superior to the regular widget

$25.99
widget

Premium Widget ™

This premium widget is superior to the regular widget

$25.99
widget

The Big Idea

The content author doesn't have to be concerned with how many items are in a container or on a page.

Thanks to CSS3, enumerated class names are a thing of the past.

One Page Template to Rule them All

Scalable Layouts

<div class="demo4">
<header class="ge-header">
    <div class="ge-header_hgroup">
        <h1>Site Title</h1>
        <h2>Site Subtitle</h2>
    </div>
    <nav class="nav nav nav-aux">
    <ul class="nav_list list list-horizontal">
        <li class="list_item"><a href="#">Aux 1</a></li>
        <li class="list_item"><a href="#">Aux 2</a></li>
    </ul>
    </nav>
    <nav class="nav nav nav-primary">
    <ul class="nav_list list list-horizontal">
        <li class="list_item"><a href="#">main 1</a></li>
        <li class="list_item"><a href="#">main2 </a></li>
        <li class="list_item"><a href="#">main 3 </a></li>
        <li class="list_item"><a href="#">main 4 </a></li>
    </ul>
    </nav>
</header>
<main class="main">
<aside class="sidebar">
    <h1>A sidebar</h1>
    <nav class="nav nav">
        <ul class="list list-vertical">
            <li class="list_item"><a href="" title="">sidenav 1</a></li>
            <li class="list_item"><a href="" title="">sidenav 2</a></li>
            <li class="list_item"><a href="" title="">sidenav 3</a></li>
        </ul>
    </nav>
</aside>
    <section class="content">
        <aside class="sidebar">
            <h1>A sidebar</h1>
            <article class="product">
                <h1 class="product__title">Nifty Widget ™</h1>
                <p>This related-ish widget is similar to the regular widget</p>
                <div class="product__cost">$21.99</div>
                <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
            </article>

            <article class="product">
                <h1 class="product__title">Less Nifty Widget ™</h1>
                <p>This premium widget is similar to the regular widget</p>
                <div class="product__cost">$9.99</div>
                <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
            </article>
            <article class="product">
                <h1 class="product__title">Widget ™</h1>
                <p>This not so related widget  to the regular widget</p>
                <div class="product__cost">$12.99</div>
                <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
            </article>
        </aside>
        <header class="content_header">
        <h2>Premium Widgets</h2>
        <h3>Available at a store near you</h3>
        </header>
        <article class="product">
            <h1 class="product__title">Premium Widget ™</h1>
            <p>This premium widget is superior to the regular widget</p>
            <div class="product__cost">$25.99</div>
            <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
        </article>

        <article class="product">
            <h1 class="product__title">Premium Widget ™</h1>
            <p>This premium widget is superior to the regular widget</p>
            <div class="product__cost">$25.99</div>
            <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
        </article>
        <article class="product">
            <h1 class="product__title">Premium Widget ™</h1>
            <p>This premium widget is superior to the regular widget</p>
            <div class="product__cost">$25.99</div>
            <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
        </article>
        <figure class="featured">
            <div class="featuredWrapper">
                <div class="featured_product">
                    <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
                </div>
                <div class="featured_product">
                    <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
                </div>
                <div class="featured_product">
                    <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
                </div>
                <div class="featured_product">
                    <img class="product__img" src="http://placeimg.com/120/120/tech" alt="widget"/>
                </div>
            </div>
            <figcaption>Check out these sweet products</figcaption>
            
        </figure>
    </section>
</main>
<footer class="ge-footer">
    <small>Copyright. All rights reserved</small>
</footer>
</div>
.demo4 {
  font-family: arial, sans-serif;
  width: 40em;
  margin: 0 auto;
}
.demo4 .ge-header,
.demo4 .ge-footer {
  width: 100%;
  min-height: 80px;
}
.demo4 .ge-header {
  border-bottom: 2px solid #808080;
}
.demo4 .ge-header_hgroup {
  float: left;
}
.demo4 .nav .list {
  list-style-type: none;
  width: inherit;
}
.demo4 .nav .list_item {
  margin: 2.5%;
}
.demo4 .nav .list-horizontal .list_item {
  float: left;
  display: inline-block;
}
.demo4 .nav-aux {
  float: right;
  width: 45%;
}
.demo4 .nav-aux .list {
  float: right;
}
.demo4 .nav-primary {
  width: 100%;
  clear: both;
}
.demo4 .sidebar {
  float: left;
  width: 25%;
  height: 100%;
}
.demo4 .main > .sidebar {
  border-right: 1px solid #808080;
}
.demo4 .main::after,
.demo4 .main::before {
  content: '';
  display: table;
}
.demo4 .main::after {
  clear: both;
}
.demo4 .main: {
  zoom: 1;
}
.demo4 article {
  padding: 2.5%;
  margin: 2.5%;
}
.demo4 .content .sidebar {
  float: right;
  border-left: 1px solid #808080;
}
.demo4 .content .sidebar + * {
  float: left;
}
.demo4 .featured_product {
  float: left;
}
.demo4 .featured_product:nth-last-of-type(2):first-of-type,
.demo4 .featured_product:nth-last-of-type(2):first-of-type ~.product {
  width: 50%;
}
.demo4 .featured_product:nth-last-of-type(3):first-of-type,
.demo4 .featured_product:nth-last-of-type(3):first-of-type ~.product {
  width: 33%;
}
.demo4 .featured_product:nth-last-of-type(4):first-of-type,
.demo4 .featured_product:nth-last-of-type(4):first-of-type ~.product {
  width: 25%;
}

    

Site Title

Site Subtitle

Premium Widgets

Available at a store near you

Premium Widget ™

This premium widget is superior to the regular widget

$25.99
widget

Premium Widget ™

This premium widget is superior to the regular widget

$25.99
widget

Premium Widget ™

This premium widget is superior to the regular widget

$25.99
widget
Copyright. All rights reserved

JavaScript

Content Manageable jQuery Plugins

The Problem

jQuery plugins are used everywhere, and there's millions to choose from. But few were made to work well with a CMS.

The challenge is when a content author wants to be able to control something within the jQuery plugin.

  • Content Authors don't usually know JavaScript
  • Generating script elements on the page containing parameters can get messy
  • Generating JavaScript from a schema can be time-consuming

The Solution

The best way to output a content-author's preferences for a given jQuery widget is to do it within the markup that has been generated. For this, the data- attribute is our friend

.
<div id="video__player2" data-video='{"height": "300px", "type":"embed"}' data-video-url="http://nationwide.dist.sdlmedia.com/Distributions/?o=1F2C97BE-DE64-467C-A6D1-3A5DCFE38215" data-video-player='{"autoplay": "false"}'>
  • A data attribute can contain any kind of string we want, even JSON
  • We can give our attribute any name we want, so long as it starts with data-
  • Browsers that support the data- attribute will create a dataset property on the element that we can access

Modifying a jQuery plugin

You can modify any existing plugin to accept data- attributes.

    /*Added to the top of the plugin*/
this.data = $.extend($(this).data('sdlmm'), params);

Then, loop over the data attributes and add them to the data:

    /*Added near the top, before you get too deep into the plugin*/
for (var attr, i = 0, attrs = $this[0].attributes, l = attrs.length; i < l; i++) {
    attr = attrs.item(i);
    if (attr.nodeName.match('data-sdlmm-')) {
        name = attr.nodeName.replace('data-sdlmm-', '');
        this.data[name] = attr.nodeValue;
    }
}
    

The Big Idea

jQuery plugins can easily be made CMS-friendly if you employ data- attributes.

You need to partner with your front-end developers to design the schema for your plugins

<shameless-plug>There's even some examples for you to see:

JavaScript

Brightness Detection with Canvas

One of the new features of 'HTML5' is the Canvas API, which allows you to draw bitmap graphics on to the web page.

Canvas has a lesser-known interface, called ImageData that lets us get information about an image that's been loaded into the canvas.

getData= function (imgEl) {
 var _this = imgDetect,
    ctx = _this.CvsCtx(imgEl), //declare a context of the canvas and then grab it
    imageData = ctx.getImageData(0,0,imgEl.width,imgEl.height), //get the data about the image in the canvas
      data = imageData.data,
      colorSum = 0,
      imgData = {},
      r, g, b, avg, brightness; // other variables for later fun and profit
}

First a pixel, then the world

  getData: function (imgEl) {
     var _this = imgDetect,
        ctx = _this.CvsCtx(imgEl),
        imageData = ctx.getImageData(0,0,imgEl.width,imgEl.height),
          data = imageData.data,
          colorSum = 0,
          imgData = {},
          r, g, b, avg, brightness;
    //Loop through the entire array of pixels
    // every fourth element in the array starts a new pixel
      for(var x = 0, len = data.length; x < len; x+=4) {
        r = data[x]; // red value
        g = data[x+1]; // green value
        b = data[x+2]; //blue value
        avg = Math.floor((r+g+b)/3); 
        colorSum += avg; // let's track the average f=of the colors
      }
      imgData.r = r;
      imgData.g=  g;
      imgData.b = b;
      imgData.avg = avg;
      //simple math to calculate brightness
      imgData.brightness = Math.floor(colorSum / (imgEl.width*imgEl.height));
      return imgData;
  }
//Declare the img detection variable
var imgDetect;
imgDetect = {
  init: function (selector) {
    var _this = this,
        els = document.querySelectorAll(selector);
    //loop through every element 
    [].forEach.call(els, function (el) {
      if (el.complete) {
        _this.setAttributes(el);
      }
      
    });
  },
  helpers: {
    getRelativeBrightness: function (brightness) {
      return brightness > 125 ? 'imgDetect--lighter' : 'imgDetect--darker';
    },
    getRelativeColor:function (data) {
      var highClr = Math.max(data.r, data.g, data.b),
          color = '';
      console.log(data);
      switch (highClr) {
        case data.r:
          color = 'imgDetect--redder';
          break;
        case data.g:
          color = 'imgDetect--greener';
          break;
        case data.b:
          color = 'imgDetect--bluer';
        default:
          break;
      }
      return color;      
    }
  },
  CvsCtx: function (imgEl) {
      var canvas = document.createElement('canvas'),
          ctx = canvas.getContext('2d');
      canvas.width = imgEl.width;
      canvas.height = imgEl.height;
      ctx.drawImage(imgEl,0,0);
      return ctx;
  },
  getData: function (imgEl) {
     var _this = imgDetect,
        ctx = _this.CvsCtx(imgEl),
        imageData = ctx.getImageData(0,0,imgEl.width,imgEl.height),
          data = imageData.data,
          colorSum = 0,
          imgData = {},
          r, g, b, avg, brightness;
      for(var x = 0, len = data.length; x < len; x+=4) {
        r = data[x];
        g = data[x+1];
        b = data[x+2];
        avg = Math.floor((r+g+b)/3);
        colorSum += avg;
      }
      imgData.r = r;
      imgData.g=  g;
      imgData.b = b;
      imgData.avg = avg;
      imgData.brightness = Math.floor(colorSum / (imgEl.width*imgEl.height));
      return imgData;
  },
  setClass: function (el, className) {
    el.classList.add(className);
  },

  setAttributes: function (imgEl) {
    var _this = imgDetect,
        data = _this.getData(imgEl);
    switch (imgEl.getAttribute('data-imgDetect')) {
      case 'parentClass':
        _this.setClass(imgEl.parentNode, 'brightness--' + data.brightness);
        _this.setClass(imgEl.parentNode, _this.helpers.getRelativeBrightness(data.brightness));
        _this.setClass(imgEl.parentNode, _this.helpers.getRelativeColor(data));
        break;
      case 'selfClass':
        _this.setClass(imgEl, 'brightness--' + data.brightness);
        _this.setClass(imgEl, _this.helpers.getRelativeBrightness(data.brightness));
        _this.setClass(imgEl, _this.helpers.getRelativeColor(data));


      default:
      break;
    }
  },
};
imgDetect.init('[data-imgDetect]');

A live action example

Animals!
Nature!
Tech! Wait, what?
People, everywhere

It's only polite to share a gist

Conclusions

What have we learned

  • Semantic HTML5 makes templates easier to read, and a way to help content authors form relationships between types of content
  • Layouts get better, with minimal code, when using semantic containers
  • CSS enables ways to style enumerated elements without creating extra classes
  • CSS enables us to count the number of items in a container
  • The data attribute is our friend, and helps us make CMS-friendly plugins
  • The Canvas API enables client-side detection of image brightness, dominant color, and much more

Browser support

Just about everything here is supported in IE9 and up. But if you're ever curious, there's some tools