OOCSS, living styleguide

Overview

Page objects -links, buttons, comment lists, nav lists.

OOCSS identifies repeatable page objects and defines one set of styles for each. It avoids proliferation of new slightly different styles by reusing 'objects' and their styles.

Each location using a header would inherit one of 6 header objects. Each link would inherit a link object.

Nicole Sullivan uses a 'living styleguide' to anchor development of OOCSS objects. The styleguide is a page showing objects used by the app. They should reference the css files used by the app. The guide and app evolve together as changes to one appear in the other.

Example living styleguide: http://trulia.github.io/hologram-example/

The guide shows style objects in a neutral context disassociated from columns or views. This makes it easier to localise styles for each object. Fonts and colors used by the application are seen in one place. Color and font size are defined in ways accessible to designers who may change them without using developer time.


Getting Started -Trulia

Trulia adopted OOCSS conventions for the ui layer of their web site. The process took them 6 months, the result: HTML 48% smaller, load times 21% faster, time to first byte 60% faster, removed 135KB unused css. Requests for the first page converted to OOCSS up 11%. video source

Nicole Sullivan describes Trulia's process in video lectures.


Establish a start point, YSlow and grep.

YSlow

http://qa1.foxsports.com/watch/fox-sports-live/story/a-new-fox-sports-live-story-093013

Grep

Sullivan recommends grep for finding over-used styles. Try to find frequently-occurring definations to reduce and re-use. Hundereds of header definitions should be ten reusable definitions.

$ grep -r font-size . | wc -l

The following directories were grepped:

$ /opt/local/bin/tree -d -L 1
.
├── main
├── main_2
├── main_responsive
└── show-fslive

Once the process of building components is begun, cssLint may be used as well to find things like chained selectors, 0px definitions and broken box model (ie., width:100%;margin:20%;).


Begin making style objects

The Hologram software may be used with our application today. Begin building style objects.

styleobjects

Potential label, header and mediablock object may be found.

Components Trulia use, include the following:

Hologram parses javascript and css files. Markdown-formatted comments in files are used to create web-servable documents saved to an output directory.

We may want to change this behaviour but its enough to begin making a style guide today. Nicole Sullivan recommends starting with simple boring components first -headers, colors, etc.

Early in the process, we should begin integrating some of the Trulia techniques.

Making theme styles easily editable:

$white: #fff;
$black: #222;
$darkOrange: #d35500;

/* links */
$linkColor : $white;
$linkHoverColor : $black;
$lowlightLinkColor: $darkOrange;
$lowlightLinkHoverColor: $black;

/* Typography Colors */
$baseTypeColor: $black;
$lowlightTYpeColor: #darkGray;
$highlightTypeColor $green;
$warningTypeColor: $red;
$reversedTypeColor1: $white;
$reversedTypeColor2: $paleGreen;

/* typeography */
$baseLineHeight : 1.5em !default;
$baseFontSize : 14px !default;
$baseFontFamily : OpenSans, sans-serif;
$baseTextColor : $black;

Abstracting difficult styles, such as those that would provide pixel-based font styles with rem-equivalents:

mobywhale
@function calculateRem($size) {
 $remSize: $size / $rootFontSize;
 @return #{$remSize}rem;
}

@mixin fontSize($size) {
 font-size: $size;
 font-size: calculateRem($size);
}

.h3, h3 {
 #include fontSize(12px)
 line-height: 1.4
}

Or those that might generate layout definitions with a given column number and gutter size.


Other things we would like:

  1. Cross-platform templates usable in browser environment
  2. Organise around page object
  3. scripts and stylesheets deplopyment -bypass build.

Cross-platform templates

Look at this jsp file, buzzer/content.jsp:

<%@include file="/libs/foundation/global.jsp"%>
<%@taglib uri="http://www.fox.com/utility" prefix="function" %>
<%@ page
import="com.day.cq.wcm.api.WCMMode,org.apache.sling.api.*,org.apache.sling.api.resource.*,com.fox.wcm.fscom.util.*,
        org.apache.sling.api.SlingHttpServletRequest,com.fox.wcm.fscom.util.CommonConstants"
%>
<%@taglib uri="http://www.fox.com/utility" prefix="function" %>
<%--Helper dialog for User --%>
<c:set var="isWCMEditOrDesign" value="<%= (WCMMode.fromRequest(request) == WCMMode.EDIT || WCMMode.fromRequest(request) == WCMMode.DESIGN)%>" />
<c:set var="pageResourceType" value="${currentPage.properties['sling:resourceType']}" />

<%--
BE todo:
1. more link global link path
2. return tags
3. how to use adapative image in headline image
--%>

<c:if test="${isWCMEditOrDesign}">
    <c:set var="WCMEditOrDesign" value="editMode"/>
    <h4>Double-click to configure #Buzzer</h4>
    <br/>
</c:if>
<%@include file="findObjectListAndSaveInPageContext.jsp" %> 
<%-- <cq:include script="/apps/fscom/components/content/general/buzzer/findObjectListAndSaveInPageContext.jsp" /> --%>


<c:forEach items="${buzzerList.contentList}" var="buzz" varStatus="status">
      <%-- check if first 3 stories, add featured classname to trigger big layout of buzzer. this view for showing all tags pages--%>

<%
    if (resource.getResourceType().equals("fscom/components/content/general/filteredByEditorialTag")) {
%>
         <c:choose>

          <c:when test="${buzz.primaryImage == null}">
                <article class="buzzer-article noImage">
          </c:when>

          <c:otherwise>
                <article class="buzzer-article smaller-article">
          </c:otherwise>
      </c:choose>

<%
    } else {
%>
         <c:choose>
          <c:when test="${status.index < 3}">
              <%-- add class name featured for first 3 stories --%>
              <article class="buzzer-article featured">
          </c:when>
          <c:when test="${buzz.primaryImage == null}">
                <article class="buzzer-article noImage">
          </c:when>
          <c:when test="${buzz.primaryImage == null && status.index < 3}">
                <article class="buzzer-article featured noImage">
          </c:when>
          <c:otherwise>
                <article class="buzzer-article smaller-article">
          </c:otherwise>
      </c:choose>
<%
    }

%>

        <div class="buzzer-header">
            <%-- temporarility removed till BE story is complete
            <span class="buzzer-exclusive">FOXSPORTS EXCLUSIVE</span>
            --%>
                <c:if test="${fn:endsWith(pageResourceType,'blogLayoutPage')}">
                <time class="header-date" pubdate="pubdate">
                       <c:out value="${buzz.createdDateShort}" />
                </time>
            </c:if>



            <a href="${buzz.path}" class="buzzer-title-link"><h3 class="buzzer-title">${buzz.headline}</h3></a>



        <c:choose>
            <c:when test="${(buzz.author == null)}">
                <c:set var="byLine" value="${buzz.source}" />
            </c:when>
            <c:otherwise>
                <c:set var="byLine" value="${buzz.authorFirstName}  ${buzz.authorLastName}" />
            </c:otherwise>
        </c:choose>
        <c:if test="${fn:endsWith(pageResourceType,'blogLayoutPage')}">
            <c:set var="byLine" value="Posted by ${byLine}" />
        </c:if>
        <div class="buzzer-details">

                <c:choose>
                <c:when test="${fn:endsWith(pageResourceType,'blogLayoutPage')}">
                    <span class="buzzer-pubdate">${buzz.createdTime}</span>
                </c:when>
                <c:otherwise>
                    <span class="buzzer-pubdate">${buzz.publicationDate}</span>
                </c:otherwise>
            </c:choose>
            <span><c:if test="${not empty byLine}">|</c:if> </span><span>${byLine}</span>


            </div>

        </div>
        <div class="buzzer-body">
            <a class="buzzer-image-link" href="${buzz.path}" title="${function:escapeApostropheAndQuotes(buzz.headline)}">                

            <c:set var="path" value="${buzz.imageResourcePath}"/>
            <c:set var="imagePath" value="${buzz.fileReference}"/>
            <c:set var="alt" value="${buzz.headline}"/>            

                <%
            String path = (String)pageContext.getAttribute("path");
            String imagePath = (String)pageContext.getAttribute("imagePath");
            String alt = (String)pageContext.getAttribute("alt");
                %>
                 <%@include file="/apps/fscom/components/content/resize-image/resize-image-div.jsp"%>

                <!--<img alt="${buzz.headline}" title="${buzz.headline}" class="buzzer-image" src="${buzz.primaryImage}"> -->
                <%-- /etc/designs/fsdigital/foxsports/styles/main/images/testPrimary.png --%>
                <span class="buzzer-content-icon icon-FS_Icons_${buzz.iconographyType}"></span>
            </a>
            <c:if test="${buzz.blurb != null}">
                <div class="buzzer-blurb-container">
                    ${buzz.blurb}
                    <a class="buzzer-blurb-link" href="${buzz.path}">VIEW MORE &raquo;</a>
                </div>
            </c:if>
        </div>


        <div class="buzzer-footer">

            <div class="buzzer-tags">
                <span href="#" class="buzzer-more icon-FS_Icons_tag"></span>
                <c:forEach items="${buzz.tags}" var="buzzTag" varStatus="TagStatus" end="2">
                    <c:set var="tagName" value="${fn:replace(buzzTag.name, '_', '-')}" />
                    <c:set var="tagName" value="${fn:toLowerCase(tagName)}" />
                    <c:set var="tagPathName" value="<%= CommonConstants.TOPICPAGE_TAG_BASE_PATH %>" /> 
                    <c:set var="tagPathAfterGlobalLinkUtil" value="${ function:URLBuilder(slingRequest,tagPathName)   }" />
                    <a href="${tagPathAfterGlobalLinkUtil}/${tagName}"><span>${buzzTag.title}</span></a>
                    <c:if test="${not TagStatus.last}"><span>  \  </span></c:if>
                </c:forEach>

            </div>
            <div class="buzzer-share">
            </div>



        </div>
        <span class="buzzer-underline"></span>
    </article>
     <c:choose>
         <c:when test="${status.index == 2}">
             <c:if test="${!isWCMEditOrDesign}">
                 <c:set var="buzzerClass" value="buzzerAd"/>
             </c:if>
             <div class="${buzzerClass} ">
                 <cq:include path="buzzerTeaser" resourceType="fscom/components/content/advertising/teaser" />
             </div>
         </c:when>
    </c:choose>
</c:forEach>

<div class="buzzer-more-wrapper" style="display:none" data-more-options='{ "path" : "${resource.path}", "size" : ${pageCount}, "topicTitle" : "${topicTitle}" }' data-load-module='{"name":"buzzer-init" ,"callback":"init" }'>
    <div class="buzzer-load-more">
        <span>+ &nbsp;</span><span> SHOW MORE </span><span>&nbsp; +</span>
        <div class="buzzer-load-more-fang"></div>
    </div>
</div>

<!--  Clear the parsys -->
<div class="fs-clear"></div> 

<%-- Start DOM templating for load more --%>
<script id="buzzer-template" type="text/x-handlebars-template">
<%
    if (resource.getResourceType().equals("fscom/components/content/general/filteredByEditorialTag")) {
%>
        <article class="buzzer-article {{#if noImage}}noImage{{/if}}">
<%
    } else {
%>
        <article class="buzzer-article {{#if isFeatured}}featured{{/if}} {{#if noImage}}noImage{{/if}} {{#unless isFeatured}}  {{#unless noImage}} smaller-article    {{/unless}} {{/unless}}">
<%
    }

%>

        <div class="buzzer-header">
            <time class="header-date" pubdate="pubdate">{{shortDate}}</time>
            <%-- temporarility removed till BE story is complete
            <span class="buzzer-exclusive">{{exclusive}}</span>
            --%>
            <a href="{{linkPath}}" class="buzzer-title-link"><h3 class="buzzer-title">{{headline}}</h3></a>
            <div class="buzzer-details">
                <span class="buzzer-pubdate">{{pubdate}}</span><span> | </span><span>{{author}}</span>
            </div>
        </div>
        <div class="buzzer-body">
            <a class="buzzer-image-link" href="{{linkPath}}" alt="{{headline}}" title="{{headline}}">
              {{#if resizableImg}}
                     <div class="responsive-image-wrapper wide" data-picture data-alt="{{headline}}">       
                         <div data-src="{{imageResourcePath}}.resize.330.186{{imageExtension}}"></div>
                         <div data-src="{{imageResourcePath}}.resize.456.256{{imageExtension }}" data-media='(min-width: 250px)'></div>
                         <div data-src="{{imageResourcePath}}.resize.489.275{{imageExtension }}" data-media='(min-width: 687px)'></div>
                         <div data-src="{{imageResourcePath}}.resize.693.390{{imageExtension}}" data-media='(min-width: 1022px)'></div>    
                         <noscript>  
                         <img src="{{imageResourcePath }}.resize.330.186{imageExtension}}" alt="{{headline}}" title="{{headline}}"/>
                         </noscript>
                     </div>
            {{else}}
                    <img src="{{fileReference }}" alt="{{headline}}" title="{{headline}}"/>

            {{/if}}
                <span class="buzzer-content-icon icon-FS_Icons_{{iconographyType}}"></span>
            </a>
            {{#if hasBlurb}}
                <div class="buzzer-blurb-container">
                    {{{blurb}}}
                    <a class="buzzer-blurb-link" href="{{linkPath}}">VIEW MORE &raquo;</a>
                </div>
            {{/if}}
        </div>
        <div class="buzzer-footer">

            <div class="buzzer-tags">
                <span href="#" class="buzzer-more icon-FS_Icons_tag"></span>
                   {{#each tags}}
                       {{#if @index}}<span>  \  </span>{{/if}}<a href="{{tagPath}}"><span>{{tagTitle}}</span></a>
                   {{/each}}
            </div>
            <div class="buzzer-share">
            </div>


        </div>
        <span class="buzzer-underline"></span>
    </article>
    {{#if teaser}}
        <div class="buzzerAd">{{{teaser}}}</div>
    {{/if}}

</script>

Markup is repeated twice. If our templates were usable in a browser we could use one markup rather than two.

Oracle strongly recommend keeping Java out of jsp files, -its not always possible to do. Foxsports use java code in many of their jsp and this makes refactoring and testing more difficult. Cross-platform templates such as Mustache avoid the anti-pattern by limiting templates to data presentation.

The Mustache implementation for Java is supported and used by Twitter.


Cross-platform templates

Look these 'style' directories:

$ /opt/local/bin/tree -d -L 1
.
├── main
├── main_responsive
└── show-fslive

Each has css and image folders and those have component sub-folders. Sometimes images are in the css folders.

$ /opt/local/bin/tree -d -L 6
.
├── main
│   ├── audio
│   ├── css
│   │   ├── assets
│   │   │   └── nav
│   │   ├── global
│   │   └── libs
│   └── images
│       ├── buzzer
│       ├── content
│       ├── gallery
│       ├── galleryIcons
│       ├── nav
│       │   └── thumbs
│       ├── story
│       └── writers
├── main_responsive
│   └── css
│       └── assets
│           ├── nav
│           └── sidebar
└── show-fslive
    ├── css
    └── images

Directories contain files that associate with specific components and views, such as viewsidebar.

$ /opt/local/bin/tree -L 2 main/css
main/css
├── assets
│   ├── articleInlineQuote.less
│   ├── buzzer.less
│   ├── channel.less
│   ├── channelFinder.less
│   ├── channelfeaturedcontent.less
│   ├── featuredVideoComponent.less
│   ├── foxui.less
│   ├── hashTagOftheDay.less
│   ├── iconFonts.less
│   ├── inlineArticleLinkedList.less
│   ├── inlineHTML.less
│   ├── inlinePhoto.less
│   ├── inlineTwitterInjector.less
│   ├── inlineVideo.less
│   ├── inlineVideoEmbed.less
│   ├── layout.less
│   ├── layoutFooter.less
│   ├── nav
│   ├── photoCollection.less
│   ├── photo_gallery.less
│   ├── secondaryNav.less
│   ├── sharetool.less
│   ├── show.less
│   ├── storyStyles.less
│   ├── tabbedSocialBox.less
│   ├── transitions.less
│   ├── videoTemplateStyles.less
│   └── viewsidebar.less
├── global
│   ├── janrain_livefyre.css
│   └── janrain_livefyre.less
└── libs
    ├── bootstrap-responsive.min.css
    ├── bootstrap.min.css
    └── normalize.css

Go up the parent directories and see the branching repeated with scripts and styles

$ /opt/local/bin/tree -d -L 2 
.
├── iconFonts
│   ├── css
│   └── fonts
├── scripts
│   ├── components
│   ├── main_async
│   ├── main_sync
│   ├── personalization
│   ├── static
│   ├── tests
│   └── tv-listings
└── styles
    ├── blog-lacesout
    ├── channel-mlb
    ├── channel-nascar
    ├── channel-ncaafb
    ├── channel-nfl
    ├── channel-worldCup
    ├── common_styles
    ├── components
    ├── event-connectlive
    ├── full-schedule
    ├── main
    ├── main_2
    ├── main_responsive
    ├── shake-n-bake
    ├── show-being
    ├── show-crowd
    ├── show-foxfootball
    ├── show-foxsoccer
    ├── show-fslive
    ├── show-tag
    ├── show-tuf
    ├── static
    └── tv-listings

To find the templates you'd need to visit subdirectories from several directories up.

Here are files used for sidebar/foxbox:

/jcr_root/apps/fscom/clientLibs/js.txt # describe location of javascript files
/jcr_root/etc/designs/fsdigital/foxsports/scripts/main_async/source/app/views/foxbox.js
/jcr_root/etc/designs/fsdigital/foxsports/scripts/main_async/source/app/views/socialbar.js
/jcr_root/etc/designs/fsdigital/foxsports/styles/main/css/assets/viewsidebar.less
/jcr_root/etc/designs/fsdigital/foxsports/styles/main/css.txt # describe location of less files
/jcr_root/etc/designs/fsdigital/foxsports/styles/main_responsive/css/assets/sidebar/viewsidebar.less
/jcr_root/etc/designs/fsdigital/foxsports/styles/main_responsive/css/assets/sidebar/viewsidebarFixed.less
/jcr_root/etc/designs/fsdigital/foxsports/styles/main_responsive/css/assets/sidebar/viewsidebarRegular.less
/jcr_root/etc/designs/fsdigital/foxsports/styles/main_responsive/css.txt # describe location of less files
/jcr_root/apps/fscom/src/impl/core/src/main/resources/META-INF/fsdigitalFoxbox.tld
/jcr_root/apps/fscom/src/impl/core/src/main/java/com/fox/wcm/fscom/taglib/FoxesBoxesDefineObjects.java
/jcr_root/apps/fscom/components/page/fixedSidebarLayoutPage/content.sidebar.jsp
/jcr_root/apps/fscom/components/page/regularSidebarLayoutPage/content.sidebar.jsp
/jcr_root/apps/fscom/components/page/channelLayoutPage/content.sidebar.jsp
/jcr_root/apps/fscom/components/content/sidebar/foxbox/content.jsp
/jcr_root/apps/fscom/components/content/sidebar/foxbox/foxbox.jsp

Maintaining the layout creates extra work for front-end developers. Navigating these folders is confusing.

Reconsider that project layout.

Trulia and other OOCSS projects organise folders around page objects. BEM and OOCSS examples use similar organisation. An example java framework that organizes files this way is Apache Wicket.

Files for a specific page object are kept in one place -images, scripts, templates, styles.

trulia, box:

.
├── script
│   └── tabs.js
├── skin
│   ├── boxBasic.scss
│   └── boxHighlight.scss
├── _box.scss
└── box.handlebars
└── box_doc.handlebars

scripts and stylesheets deplopyment -bypass build.

Development of stylesheets and scripts will be faster if the build process is bypassed. It should be possible to deploy front-end files in an un-optimised state so that updated version of these files may be copied to the target folder and served.

There are various techniques for doing this. We could begin using one.

https://github.com/stubbornella/oocss/wiki/standard-module-format
https://github.com/stubbornella/oocss/wiki/Module

puppet
https://github.com/stubbornella/oocss/tree/master/oocss#required-vagrant

http://www-12.lotus.com/ldd/doc/oneuidoc/docpublic/components.htm

http://127.0.0.1:8080/docs/components/

https://github.com/stubbornella/oocss/releases/tag/v2.0.0
https://github.com/stubbornella/oocss/releases/download/v2.0.0/build2.0.0.zip