51黑料不打烊

Understanding how to code for the Style System understanding-how-to-code-for-the-aem-style-system

In this video we鈥檒l take a look at the anatomy of the CSS (or LESS) and JavaScript used to style Experience Manage鈥檚 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

Transcript
Let鈥檚 take a technical - deep dive into 51黑料不打烊 Experience - Manager Style System. In this video we鈥檒l - 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鈥檚 applied to the - Core Title Component by way of the We.Retail Title Proxy Component. The first aspect we鈥檒l - review is the CSS. In this video we鈥檒l 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鈥檛 using it, - I encourage you to check it out. All right. Let鈥檚 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鈥檚 category in our case - we鈥檒l piggyback on the we-retail.component.base category - to ensure it is loaded onto all the We.Retail pages we鈥檒l 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鈥檚 take a look at that. Our main.less file鈥檚 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鈥檒l - 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鈥檒l 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 鈥榚xample鈥. 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鈥檚 dive into the - style / example.less file. This is the file - where we define our Example Style鈥檚 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鈥檚 talk about CSS Class Name - first, AEM鈥檚 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鈥檚 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鈥檚 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鈥檚 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鈥檚 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鈥揺xample. 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鈥揺xample 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鈥檒l see later this class is attached to - AEM鈥檚 Responsive Grid Elements. So unless changing the grid is - explicitly desired it鈥檚 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鈥檚 much easier - to understand why. Okay let鈥檚 skip the javascript - for now but we鈥檒l come back to it while the CSS classes - are fresh in our minds. Let鈥檚 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鈥檚 see how it got there. For this we need to open up the - page 鈥榚dit template鈥, size up to 鈥榓llowed 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鈥揺xample. So let鈥檚 make sure that鈥檚 map to - our example style label and it is. Note that when we add - the CSS class here we don鈥檛 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鈥檚 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鈥檚 take a look at the - DOM and see what鈥檚 happening.
Pay attention to the class - attributes values on the components wrapping div when - we apply our style. Sure enough the - cmp-title鈥揺xample 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鈥檙e going to talk about this - last modified date, That鈥檚 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鈥檚 see how this works. We鈥檒l head back to CRXDE - and look at the title.js which is directly included - by the client鈥檚 js.txt file the use of jQuery in this example - is entirely optional and is simply used because it鈥檚 - ubiquitous and well understood. So let鈥檚 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鈥檒l 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鈥檚 see how - this function works. First it finds all title - components in the DOM. They have the cmp-title鈥揺xample - style applied. It then ensures the components - haven鈥檛 already been processed by checking - for the existence of a marker data dash attribute. Keep this attribute in - mind as we鈥檒l 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鈥檙e 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鈥檚 - 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鈥檚 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鈥檇 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鈥檚 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>
  • 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.

      • Modern image components often will use JavaScript to select and load the most appropriate image for the use case (viewport).

CSS best practices css-best-practices

NOTE
The Style System makes a small technical divergence from , in that the BLOCK and BLOCK--MODIFIER are not applied to the same element, as specified by .
Instead, due to product constraints, the BLOCK--MODIFIER is applied to the parent of the BLOCK element.
All other tenants of should be aligned with.
  • Use preprocessors such as (supported by AEM natively) or (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鈥檚 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 鈥渄efined鈥 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鈥檚 BLOCK. Or in other words, start the nested component鈥檚 BLOCK at the 3rd level, and the nested Component鈥檚 ELEMENT is at the 4th selector level.

JavaScript best practices javascript-best-practices

The best practices defined in this section pertain to 鈥渟tyle-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鈥檚 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鈥檚 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 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.

Additional Resources additional-resources

recommendation-more-help
bb44cebf-d964-4e3c-b64e-ce882243fe4d