In this video we'll take a look at the anatomy of the CSS (or LESS) and JavaScript used to style Experience Manage's Core Title Component using the Style System, as well as how these styles are applied to the HTML and DOM.

Note:

The AEM Style System was introduced with AEM 6.3 SP1 + Feature Pack 20593.

The video assumes the We.Retail Title component has been updated to inherit from Core Components v2.0.0+.

Understanding how to code for the Style System


The provided AEM Package (technical-review.sites.style-system-1.0.0.zip) installs the example title style, sample policies for the We.Retail Layout Container and Title components, and a sample page.

Download

The CSS

The following is the LESS definition for the example style found at:

/apps/enablement/sites/style-system/clientlib-example/components/titles/styles/example.less

For those that prefer CSS, below this code snippet is the CSS this LESS compiles into.

/* LESS */
.cmp-title--example {
	.cmp-title {
		text-align: center;

		.cmp-title__text {
			color: #EB212E;
			font-weight: 600;
			font-size: 5rem;
			border-bottom: solid 1px #ddd;
			padding-bottom: 0;
			margin-bottom: .25rem
		}

		// Last Modified At element injected via JS
		.cmp-title__last-modified-at {
			color: #999;
			font-size: 1.5rem;
			font-style: italic;
			font-weight: 200;
		}
	}
}

The above LESS is compiled natively by Experience Manager to the following CSS.

/* CSS */
.cmp-title--example .cmp-title {
	text-align: center;
}

.cmp-title--example .cmp-title .cmp-title__text {
	color: #EB212E;
	font-weight: 600;
	font-size: 5rem;
	border-bottom: solid 1px #ddd;
	padding-bottom: 0;
	margin-bottom: 0.25rem;
}

.cmp-title--example .cmp-title .cmp-title__last-modified-at {
	color: #999;
	font-size: 1.5rem;
	font-style: italic;
	font-weight: 200;
}

The JavaScript

The following JavaScript collects and injects the current page's last modified date and time beneath the title text when the Example style is applied to the Title component.

The use of jQuery is optional, as well as the naming conventions used.

The following is the LESS definition for the example style found at:

  • /apps/enablement/sites/style-system/clientlib-example/components/titles/styles/js/title.js
/**
 * JavaScript supporting Styles may be re-used across multi Component Styles.
 *
 * For example:
 *    Many styles may require the Components Image (provided via an <img> element) to be set as the background-image.
 *    A single JavaScript function may be used to adjust the DOM for all styles that required this effect.
 *
 * JavaScript must react to the DOMNodeInserted event to handle style-switching in the Page Editor Authoring experience.
 * JavaScript must also run on DOM ready to handle the initial page load rendering (AEM Publish).
 * JavaScript must mark and check for elements as processed to avoid cyclic processing (ie. if the JavaScript inserts a DOM node of its own).
 */
jQuery(function ($) {
    "use strict;"

    moment.locale("en");

    /**
     * Method that injects p element, containing the current pages last modified date/time, under the title text.
     *
     * Similar to the CSS Style application, component HTML elements should be targeted via the BEM class names (as they define the stable API)
     * and targeting "raw" elements (ex. "li", "a") should be avoided.
     */
    function applyComponentStyles() {

        $(".cmp-title--example").not("[data-styles-title-last-modified-processed]").each(function () {
            var title = $(this).attr("data-styles-title-last-modified-processed", true),
                url = Granite.HTTP.getPath() + ".model.json";

            $.getJSON(url, function(data) {
                var dateObject = moment(data['lastModifiedDate']),
                    titleText = title.find('.cmp-title__text');

                titleText.after($("<p>").addClass("cmp-title__last-modified-at").text("Last modified " + dateObject.fromNow()));
            });
        });
    }

    // Handle DOM Ready event
    applyComponentStyles();

    // Apply Styles when a component is inserted into the DOM (ie. during Authoring)
    $(".responsivegrid").bind("DOMNodeInserted", applyComponentStyles);
});

Development best practices

HTML best practices

  • HTML (generated via HTL) should be as structurally semantic as possible; avoiding unnecessary grouping/nesting of elements.
  • HTML elements should be addressable via BEM-style CSS classes.

Good

All elements in the component are addressable via BEM notation.

Bad

The list and list elements are only addressable by element name. 

<div class="cmp-list">
     <ul class="cmp-list__item-group">
        <li class="cmp-list__item">...</li>
     </ul>
</div>
<div class="cmp-list">
     <ul>
        <li>...</li>
     </ul>
</div>
  • It is better to expose more data and hide it than to expose too little data requiring future back-end development to expose it. 
    • Implementing authorable content toggles can aid in keeping this HTML lean, whereby authors are able to select which content elements are written to the HTML. The can be especially important when writing images to the HTML that may not be used for all styles.
    • The exception to this rule is when expensive resources, for example, images, are exposed by default, as event images hidden by CSS will be, in this case, unnecessarily fetched.
      • Modern image components often will use JavaScript to select and load the most appropriate image for the use case (viewport).

CSS best practices

Note:

The Style System makes a small technical divergence from BEM, in that the BLOCK and BLOCK--MODIFIER are not applied to the same element, as specified by BEM.

Instead, due to product constraints, the BLOCK--MODIFIER is applied to the parent of the BLOCK element. 

All other tenants of BEM should be aligned with.

  • Use preprocessors such as LESS (supported by AEM natively) or SCSS (requires custom build system) to allow for clear CSS definition, and re-usability. 
  • Keep selector weight/specificity uniform; This helps to avoid and resolve difficult-to-identify CSS cascade conflicts.
  • Organize each style into a discrete file.
    • These files can be combined using LESS/SCSS @imports or if raw CSS is required, via HTML Client Library file inclusion, or custom front-end asset build systems.
  • Avoid mixing many complex styles.
    • The more styles that can be applied at a single time to a component, the greater the variety of permutations. This can become difficult to maintain/QA/ensure brand alignment.
  •  Always use CSS classes (following BEM notation) to define CSS rules.
    • If selecting elements without CSS classes (i.e. bare elements) is absolutely necessary, move them higher in the CSS definition to make it clear that they have lower specificity than any collisions with elements of that type that do have selectable CSS classes.
  • Avoid styling the BLOCK--MODIFIER directly as this is attached to the Responsive Grid. Changing the display of this element may affect the rendering and functionality of the Responsive Grid, so only style at this level when the intent is to change the Responsive Grid's behavior.
  • Apply style scope using BLOCK--MODIFIER. The BLOCK__ELEMENT--MODIFIERS can be used in the Component, but since the BLOCK represents the Component, and the Component is what is styled, the Style is "defined" and scoped via BLOCK--MODIFIER.


Example CSS selector structure should be as follows:

1st level selector

BLOCK--MODIFIER

2nd level selector

BLOCK

3rd level selector

BLOCK__ELEMENT

  Effective CSS selector
.cmp-list--dark .cmp-list .cmp-list__item

.cmp-list--dark

   .cmp-list

     .cmp-list__item { 

         color: blue;

      }

.cmp-image--hero .cmp-image .cmp-image__caption

.cmp-image--hero

   .cmp-image

     .cmp-image__caption {

       color: red;

     }

In the case of nested components, the CSS selector depth for these nested Component elements will exceed the 3rd level selector. Repeat the same pattern for the nested component, but scoped by the parent Component's BLOCK. Or in other words, start the nested component's BLOCK at the 3rd level, and the nested Component's ELEMENT will be at the 4th selector level.

JavaScript best practices

The best practices defined in this section pertain to "style-JavaScript", or JavaScript specifically intended to manipulate the Component for stylistic, rather than functional purposes.
  • Style-JavaScript should be used judiciously and is a minority use case.
  • Style-JavaScript should be primarily used for manipulating the component's DOM to support styling by CSS.
  • Re-evaluate use of Javascript if components will appear many times on a page, and understand the computational/and re-draw cost.
  • Re-evaluate use of Javascript if it pulls in new data/content asynchronously (via AJAX) when the component may appear many times on a page.
  • Handle both Publish and Authoring experiences.
  • Re-use style-Javascript when possible.
    • For example, if multiple styles of a Component require it's image to be moved to a background image, the style-JavaScript can be implemented once and attached to multiple BLOCK--MODIFIERs.
  • Separate style-JavaScript from functional JavaScript when possible.
  • Evaluate the cost of JavaScript vs. manifesting these DOM changes in the HTML directly via HTL.
    • When a component that uses style-JavaScript requires server-side modification, evaluate if the JavaScript manipulation can be brought in at this time, and what the effects/ramifications are to performance and supportability of the component.

 

Performance considerations

  • Style-JavaScript should be kept light and lean.
  • To avoid flickering and unnecessary re-draws, initially hide the component via BLOCK--MODIFIER BLOCK, and show it when all DOM manipulations in the JavaScript are complete.
  • The performance of the style-JavaScript manipulations is akin to basic jQuery plug-ins that attach to and modify elements on DOMReady.
  • Ensure requests are gzipped, and CSS and JavaScript are minified.

When to use what

Activity Approach Decision Point Approach Guidance
Removing elements Use CSS If the element can be hidden without side-effect (ie. hiding needed children element) Target element using display:none.
Use JavaScript Avoid using JavaScript; prefer CSS N/A
Modify HTML If the removed element is a heavy resource (ie. auto-loads another resource, like an image)

When using Core Components, overlay the HTL that requires modification. 

For custom Components, modify the HTL directly.

 
Adding elements Use CSS N/A Not Possible with CSS
Use JavaScript

If the element to inject can be derived from the content naturally exposed via the HTML.

Use JavaScript to update the DOM with the new element(s).

Avoid making too many AJAX calls.

Modify HTML

If the element requires new information derived from the server, necessitating an adjustment to the HTML via the Component's HTL.

If the Component variation is used many times on a page requiring JavaScript to run and re-draw many times and/or make AJAX calls to pull in new data.

When using Core Components, overlay the HTL that requires modification. 

For custom Components, modify the HTL directly.

 
Rearranging or modifying elements Use CSS

If the element can be adjusted using usual CSS positioning techniques. Use usual CSS positioning techniques such as flexbox, float, absolute/relative/fixed positioning, margin adjustment, etc.
Use JavaScript

If the element can be positioned by moving it within the DOM (and optionally further positioned via CSS) See the example JavaScript above.
Modify HTML

Component variation is used many times on a page requiring JavaScript to run and re-draw many times. When adjusting HTL, follow these patterns:
  • Avoid nesting of HTML when possible
  • Attach BEM CSS classes to new HTML elements to allow for specific and isolated styling.
  • For highly complex style variants, evaluate rendering the same content multiple times in the component, showing them as needed.
    • For example, it is difficult to adjust the stacking order of elements in HTML using CSS; if a style A requires the Article description to be ABOVE the title, and style B requires the description to be BELOW the title, it may be best to render the description twice, once above and once below, and show/hide them based on the variant, rather than attempt CSS or JavaScript gymnastics to affect the layout. 

      Always evaluate the cost of generating duplicative content, however Gzipping should mitigate request performance concerns.

Supporting materials

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License  Twitter™ and Facebook posts are not covered under the terms of Creative Commons.

Legal Notices   |   Online Privacy Policy