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.
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.
technical-review.sites.style-system-1.0.0.zip
The following is the LESS definition for the example style found at:
/apps/demo/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 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/demo/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);
});
Good - All elements in the component are addressable via BEM notation:
<!-- Good practice -->
<div class="cmp-list">
<ul class="cmp-list__item-group">
<li class="cmp-list__item">...</li>
</ul>
</div>
Bad - The list and list elements are only addressable by element name:
<!-- Bad practice -->
<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 author-able 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 are, in this case, unnecessarily fetched.
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.
@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.
Always use CSS classes (following BEM notation) to define CSS rules.
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
is at the 4th selector level.
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.
BLOCK--MODIFIERs
.BLOCK--MODIFIER BLOCK
, and show it when all DOM manipulations in the JavaScript are complete.