Understanding how to code for the Style System understanding-how-to-code-for-the-aem-style-system
Last update: Thu Jan 25 2024 00:00:00 GMT+0000 (Coordinated Universal Time)
CREATED FOR:
- Intermediate
- Experienced
- Developer
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.
Understanding how to code for the Style System understanding-how-to-code-for-the-style-system
Let’s take a technical - deep dive into Adobe Experience - Manager Style System. In this video we’ll - take a look at the anatomy of the CSS - and JavaScript used to style the Core Title - Component using the Style System as well as how these Styles are - applied to the HTML in DOM. This video consists of a simple example Style that’s applied to the - Core Title Component by way of the We.Retail Title Proxy Component. The first aspect we’ll - review is the CSS. In this video we’ll use LESS, - which AEM natively compiles in the CSS. Overall CSS is of - course supported. LESS provides many - advantages in terms organization, - readability and reuse. So if you aren’t using it, - I encourage you to check it out. All right. Let’s get started. This will head over - to CRXDE Lite. I installed the package - with our Example Style which deploys to / apps / - enablement / sites / style-system / clientlibs / clientlib-example. This contains the - CSS, or LESS in our case and JavaScript responsible for rendering the - Component Style. Our example Client Library - is included via it’s category in our case - we’ll piggyback on the we-retail.component.base category - to ensure it is loaded onto all the We.Retail pages we’ll be using. So the entry point - to the CSS in the Client Library is always the css.txt. Our file is fairly simple it - imports the main.less file. So let’s take a look at that. Our main.less file’s job - is to include other LESS files, - these often include Global Style rules, variables and mixins, - but also the component styles as well. In our case we’ll - be providing Styles just for this Style Component. We have a single import for that. Opening the imported - title.less file in component / title, we see that this file - acts in a similar capacity as the main.less file, - but is scoped to the Title Component. At the top are - styles that apply to every instance of - the Title Component. And are naturally inherited - by all Styles. In this example all Title - text is bolded by default. This is achieved by namespacing - these Global CSS rules to the cmp-title selector, which is the wrapping CSS class - used by the Core Title Component. We’ll take a look at - the Naming Convention and what drives - it in a moment. Following the Title - Components Global Styles, each discrete Title - Style is imported. In this case we only have one - style to import ‘example’. However typically a component could - be styled in many different ways, so there would be a - number of imports here. This organization of - breaking out styles by file simplifies managing - and maintaining them. Keep in mind if LESS is - not used, all CSS files must be enumerated in the - clientlibs css.txt file. Next up let’s dive into the - style / example.less file. This is the file - where we define our Example Style’s visual aesthetic, and it looks pretty much - like your typical LESS file. For those more familiar - with CSS, the CSS that the compiles to is provided - commented out below. Looking after example.less - file there should be two main aspects - that jump out at you. First is the CSS Class naming, - and secondly the nesting or namespacing - of the CSS classes. Let’s talk about CSS Class Name - first, AEM’s Core Components for the industry - standard BEM or Block Element Modifier CSS - naming convention. In the case of Core Components, these well-defined CSS class names - are considered the stable API. They can be targeted with CSS - styles and is a generally good practice to follow them or - similar conventions even in custom components, and avoid targeting - bare elements. For example LI or A as this - can lead to CSS selector weight conflicts that can be - hard to hunt down and resolve. So let’s briefly talk - about BEM. Again, standing for Block - Element Modifier. You can think of the Component - itself as the Block and is prefixed with cmp- to drive - home that is as a component. So whenever we see cmp-title, - it’s clear that this Style pertains to - the Title Component. Essentially every CSS - class in the Title Components Example - Style should have the Components - Block and CSS name, since they all - pertain to the Title Component, and as we - see they all do. Next up is the Element. The Element follows - the Block with double underscores and identifies some logical - chunk of HTML. So here we have the Text Element - which represents the Title’s Text and a last-modified-at - Element which well, represents the last - modified date and time. By just using the Block - Elements to name our CSS classes we can quickly - and clearly understand what content of our Component - our Styles are targeting. Lastly we have the Modifier. A Modifier can be applied to - either a Block or an Element and denotes a particular variation - of that Block or Element. Considering this video - revolves around applying an Example Style to - the Title Component, it’s no surprise that we have a CSS - Class that has the Title Component Block or cmp-title followed by the example modifier - or --example. Ok, so now that we understand BEM, - the Nesting or Namespacing of the selector should - start making a bit more sense. The Outermost Selector is - the Block plus modifier or cmp-title–example. This hierarchy allows - only the nested CSS classes to take - effect when an HTML Element with the wrapping - CSS class is present. This provides an easy way - to apply this entire Style. We just need to use - the AEM Style System to wrap the entire - Component with the cmp-title–example CSS class. Whenever we want our example - Style to be in effect. Note that Style rules are - typically not attached to the Block with Modifier - Class, because as we’ll see later this class is attached to - AEM’s Responsive Grid Elements. So unless changing the grid is - explicitly desired it’s best not to attach styles - to avoid potential collisions with AEM grid styles. The next lesson class is - typically the block CSS class that represents the - component in question. This may seem redundant - as the wrapping class already includes the - block identifier but this extra level of nesting - provides convenient selector weight which - comes in handy and more advanced style - system in these cases. CSS rules can be - applied at this level as needed including - things like margin, padding, within height, then - otherwise might disturb the AEM grid if applied - at one level higher. The third level - nesting consists of the styles applied to the - block with elements. For instance, cp-title__text. There are most styles for your - components will be defined. Typically there is - little need to finesse further though occasionally - as required the selector organization - is helpful as a CSS code base grows large - since it helps ensure selector - weight is consistent and well understood - by developers. Following this convention - ensures the weight of each style rule - is three CSS classes reducing the chances - of CSS selector collisions and if collisions do occur it’s much easier - to understand why. Okay let’s skip the javascript - for now but we’ll come back to it while the CSS classes - are fresh in our minds. Let’s head back and see how - the style is applied in AEM. Heading back to our - page we set our example style is available - for application to our title component so - let’s see how it got there. For this we need to open up the - page ‘edit template’, size up to ‘allowed components’ - pages at the policy level. So open the title - components policy. Head over to the styles tab and - you can see the available styles. The key here is we map - this style label. The author will see - to the CSS classes we want to wrap the - components with. So the styles take effect as you - remember our wrapping style was cmp-title–example. So let’s make sure that’s map to - our example style label and it is. Note that when we add - the CSS class here we don’t prefix it - with a dot as whatever is added to this field - is simply injected into the wrapping - elements class attribute This has a happy side effect of allowing us to apply - multiple classes to a single style - label if needed. Let’s head back to the page and - see what actually happens here. We select the style and CSS styles - are applied just as expected so let’s take a look at the - DOM and see what’s happening.
Pay attention to the class - attributes values on the components wrapping div when - we apply our style. Sure enough the - cmp-title–example is added by the AEM style - system and that dictates how this instance of the - title component renders.
So you might be asking - yourself when we’re going to talk about this - last modified date, That’s not part of the - core title componente . So how did it get there. This is where the - javascript comes in. JavaScript primary role in the style system is to - make client side augmentations to the - components HTML. When the HTML provided by - the components HDL script is insufficient to achieve - the desired styling. Let’s see how this works. We’ll head back to CRXDE - and look at the title.js which is directly included - by the client’s js.txt file the use of jQuery in this example - is entirely optional and is simply used because it’s - ubiquitous and well understood. So let’s look at the - top and work down. All the code is wrapped in - the typical JQeury DOM ready event handler and defined - in an anonymous namespace. The bulk of the - work is done by the ApplyComponentStyles - javascript function. So what we’ll be doing - for the style is executing javascript - that will derive and then inject the - last modified date for the current page beneath - the title text. So let’s see how - this function works. First it finds all title - components in the DOM. They have the cmp-title–example - style applied. It then ensures the components - haven’t already been processed by checking - for the existence of a marker data dash attribute. Keep this attribute in - mind as we’ll understand the importance of this - a little later on. For each of the title - components it finds in the DOM that meets - these criteria. Each is immediately - marked as processed by adding this data - dash attribute. Next. The last modified - date is derived and injected into our - components DOM. This is done by making - an ajax call to the current pages - model.json selector which is provided via AEM content - services and provides an enriched json rendition of - the page from which we can get the - last modified time the open source moment - json date and time library is used to format the last modified - time into a human readable value and then this value is - injected via a paragraph tag underneath the title text and - also applies the CSS class name cmp-title__last-modified-at that follows DOM - naming convention. So this function is fairly - simple but we still have to figure how and - when to invoke it. There are two hooks - we need to consider. The first is on page load - or on the DOM is ready. And since we’re already - in a DOM ready event listener via the wrapping jQuery call. we can - simply call the function applyComponentStyles - immediately. This will ensure that whenever - there is a page load such as a user loading the page on - AEM publish our javascript will execute and inject the last - modified time into the DOM. The second hook is a - bit more interesting. Under the covers AEM site page - editor updates and injects components into the DOM during - page and component authoring to ensure the styles are - applied appropriately during this authoring - experience. We need to execute the - javascript whenever we notice inserted into the - responsive grid as well. We can do this by binding - the applyComponentStyles function to the DOM - notice inserted event for the responsive grid. Now you might remember our - applied component sales actually inserts a new node “
” an - element into the DOM since we just added an - event listener that calls applyComponentStyles - every time a DOM notice inserted - we need a way to prevent the insertion - of our paragraph element from kicking off - the cycle all over again. And this is why we need to - apply and check for it the data dash attribute - marker indicating if a component has - already been process. So far then our Javascript - based style application from spinning off into - an infinite loop.
If we had back to our - page we can verify our understanding - of the javascript by first viewing the source of - the page which is the HTML. That AEM generate server side - and serves up to the browser. As we can see there’s - no paragraph tag with our last modified - data in HTML. If we look back to our - Web page and inspect the DOM we can see the - paragraph element has in fact been injected into - the DOM by the javascript and we can also see that our - data dash attribute markers applied indicating that - this has been processed. The last thing to note is not only - can components have styles applied. Below containers and pages - can have them apply as well. If the approaches and - conventions align in this video or followed styles - the layout container or page levels will - cascade down and apply to all applicable - components therein.
It’s worth noting - that when styles are applied at the page - level the CSS classes are added to the - body tag itself and when applied to the - layer container the classes are applied at - the AEM grid element level. All right well that - about covers it. I hope this is giving you - a decent idea about some of the technical - underpinnings and how you’d worked with the - AEM 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.
technical-review.sites.style-system-1.0.0.zip
The CSS the-css
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 JavaScript example-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/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);
});
Development best practices development-best-practices
HTML 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:
<!-- 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>
CSS best practices css-best-practices
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:
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.
JavaScript best practices 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.
- 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.
Additional Resources additional-resources