When I talked about modern front-end productivity on AEM last year, I lamented how there wasn't a good solution for Javascript templating (really, Single Page Applications).

Templating... templating is a challenging one that I don't think has been solved sufficiently. Handlebars is a natural fit, but it misses the mark in certain contexts. If I'm doing a new AEM project today, I'm using HBS, but I'm yearning for something better.

The dust has settled a bit since that post:

  • Sling Model Exporters have been shipping for a year. 
  • Content Fragments are now naturally exposed as JSON.
  • Core Components 2.0 are now backed by Sling Model Exporters.
  • React has gone MIT.

We're still in a bit of a holding pattern, though. We likely still need to rely on HTL (server-side rendering) in 2018. Especially if we want 100% global SEO coverage. So what do we want from an SPA framework?

Ideal SPA Requirements

  1. 100% global SEO coverage
  2. Little-to-no Flashes Of Unstyled Content (FUOC)
  3. Authorable
  4. Front-end developer friendly... community momentum, documentation, hire-ability, etc.
  5. Mix and match with existing HTL components

Pick two? Maybe?

Enter React DOM Components

In short, React DOM Components allow a developer to build Javascript data models with regular old DOM. This DOM could be built using PHP, .NET, or in our case, Java (HTL). Want to build your Javascript model with data attributes? Child nodes? Child Arrays? Text Content? The API allows for simple or complex models to be built so data can be passed from server-side DOM to a React Component.

How does this look in AEM land?

At a high level, like this:


  1. The Sling Model (with Exporter) powers both server-side and client-side rendering.
  2. The HTL component is built the same way as always (but with a custom element name for efficiency)
  3. A DOM Model is built to define the attributes, nodes, and content to be used from DOM.
  4. A DOM Component is built to find the custom element, build the data model, and pass that data to a React Component.
  5. The React Component is just a vanilla React Component.
  6. A DAO class is built to fetch data from the Sling Model Exporter. Click a button? The React Component tells the DAO to fetch the data and update the state.

The big down-side to this process is that we're essentially building two models and two views. It's not ideal, but it lets each team (FE & BE) continue to build in ways they are familiar with. A back-end developer can still work in Java. A front-end developer who has never used AEM can work in almost 100% pure React the day they start the job.

React DOM Components are also AEM-friendly. They know when editor.html has mutated a page for authoring and pushes the state to the React Component automatically.

Sample Code

Step 1 - Sling Model

Step 2 - TabControl HTL Component

Step 3 & 4 - TabControlDOM & TabControlModel

Step 5 & 6 - TabControl React Component with DAO

The React Component and Data Access Object (DAO) have been combined for demonstration simplicity.

Prior Art

What is outlined above is not the only way to integrate AEM with React. There is a separate project using Typescript and Nashorn to achieve something similar. For my purposes, this is a little more opinionated and heavy handed than I like, hence the process above.

A Full Project

All the source with a sample implementation can be found on GitHub. This project is what I'm calling "Pug Ranch 2018" which is a reference implementation of AEM Archetype 12 plus a few front-end niceties to create a "tab-control" component using HTL and React.

Front-ends for AEM pre-2017

If you've paid attention to the front-end world within AEM you've likely seen blogs to integrate grunt or gulp. These solutions are good starts, but don't solve many problems.

  • How do we write modern ES6 syntax?
  • What about linting?
  • How do we have good separation between authoring and publish clientlibs?
  • How do we support a multi-tennant lifestyle?
  • Why are we using tools that have overlap? i.e. NPM and Bower

My front-end proposal scales better than most solutions while supporting ultra-modern front-end standards.

Modern front-ends for AEM in 2017

Let's talk tools, the whys, and then about the hows.

Tools

  • Dependency management: NPM
  • Task Runner: Webpack
  • Bundler: Webpack
  • Transpiling: Babel
  • Linting: ESLint + Airbnb Style Guide
  • Syntax: ES6 & LESS
  • Templating: TBD

Whys

We're using NPM instead of Bower because there's no longer a need for Bower... most FEDs understand package.json these days.

We're using webpack because it's a decent task runner and an excellent bundler... let's not use Grunt / Gulp to then run webpack... one tool. Webpack also doesn't need to be installed globally which makes on-boarding easier.

We use Babel to transpile our ES6 into something browsers can understand. It's widely documented which, again, helps on-boarding.

We're using ESLint + Airbnb Javascript Style Guide so our code follows strict standards that are widely used and documented. Again, this helps on-boarding... the clearest path to having documentation for your own project is to leverage tools that already have exceptional docs.

ES6 is the future (technically the present), let's start using it. LESS is AEM friendly, so if we ever decide to use AEM's native LESS compiler, we're not having to re-write everything.

Templating... templating is a challenging one that I don't think has been solved sufficiently. Handlebars is a natural fit, but it misses the mark in certain contexts. If I'm doing a new AEM project today, I'm using HBS, but I'm yearning for something better.

Hows

So, how does all of this work?


The high level overview

  • We start with a simple folder for our area of concern... publish, author, theme, bu1, bu2, whatever.
  • It uses .content.xml, css.txt, and js.txt to create a proper clientlib.
  • It has src and dist folders in it.
  • Inside src are our source files to be run through eslint, babel, and webpack.
  • Our dist folder consumes the output.

About our clientlib definition

If you've never worked with AEM, you might just think it's OK to drop a script tag on your page and be done. Unfortunately, you will end up hobbling AEM functionality and make more work for your back-end developers. Wrapping your JS (minified or not) inside a proper clientlib is incredibly important and will reduce work in the long run. Certain granite views are built around consuming a namespaced clientlib, and if you wish to view the raw JS files on non-dispatched servers (i.e. 4502), BE winds up needing to write content disposition configs because AEM serves up raw JS as attachments when they're not proper clientlibs.

Proxied Clientlibs

We add a property to our clientlib definition to allow it to be proxied out to /etc.clientlibs/. This reduces folder traversal... we're no longer digging into /etc/clientlibs/ourproject and /apps/ourproject/components. Everything just sits in /apps/ourproject/clientlibs and /apps/ourproject/components.

The .content.xml

Our webpack config

The basics is that we have entries, outputs, rules, and plugins.

The entries exist in the src folder, outputs go in the dist folder, rules and plugins should be fairly readable...

With our three entries (clientlibs) we have a lot of ground covered while being separated into their areas of concern:

  1. Base Publish - All structure and base-level features consumed by all parties (authors, visitors, etc.)
  2. Base Author - All structure and base-level features that are only consumed by authors. Everything from TouchUI hacks to Dialog listeners.
  3. Base Theme - All LESS / JS that can be individualized based on a tenant's requirements (think font families, margin & padding sizes, etc.)

Developer on-boarding

With one maven command, everything is pulled in and setup to start working:

mvn clean install -PautoInstallPackage
Day-to-day development

Things differ a bit here for BE and FE. BE can continue to use the maven tools they're use to. FE can switch over to start using tools they're use to:

cd ui.apps
npm run aemlocal
<new terminal tab>

aemsync

By paring our aemlocal script (defined in package.json and basically calling webpack --watch) with aemsync, we can now build anything on the front-end without any maven cruft. Want to add a new dropdown to a dialog? No problem. Want to wire up a new some new javascript functionality? No problem. All of this syncs up seamlessly to AEM with perfect separation of concerns.

Source Code

Want to see how it all works? Go check the source code.

aemfront-endback-endtoolingwebpacknpmES6

If you ask any front-end developer what they need to be successful, they'll likely tell you they three things: 

  1. A rapid prototyping tool.
  2. A JSON end-point to grab data.
  3. A template language that makes sense.
  4. Don't tell me how to do my job.

If you ask any back-end developer what they need to be successful, they'll likely tell you they need three things:

  1. Ability to leverage Java / whatever frameworks to make them highly efficient.
  2. A good testing / CI / automation process.
  3. Not too much business, front-end, or ops noise.
  4. Don't tell me how to do my job.

AEM is built around experiences (FE) but powered through rich and deep integrations (BE) that keep businesses productive. How does a business consume CRM data? It's not through the front-end.

This is where we have to think about MVC at a macro level. FE might be asking for data in a concise JSON feed that can be consumed by a hot new template language (V), but BE is talking OSGi, separation of concerns, and scale-ability (C). Where the data is coming from is anyone's guess. If you live in the AEM world, you're hoping the JCR is building models the way you want (M). So whose domain is the model? I'd argue it's FE.

As an FE developer (FED), I build the dialogs that POST against SlingPostServlet and store my data exactly how I want. Do I want 45 properties outlining how this component should behave in responsive views or should I break them out as children? What does that mean for the back-end?

We're getting closer to a scenario where all parties can agree. FEDs just want a JSON end-point and a template language they're use to (JSX, HBS, whatever). BED's want to write productive Java classes (POJOs, Servlets, OSGi services). We just seem to be missing that final link. HTL is so incredibly powerful when rendered server-side, but doesn't satisfy the need of in-page updating. How do I sort a list without a query param and a page refresh? Angular? Handlebars? React? All of these solutions have felt like after thoughts or had down-sides when coupled with HTL.

At some point, the power of the back-end needs to be coupled with the front-end. Exposing the entire JCR tree to the front-end or writing servlets for every single view is not the answer.


front-endback-endJCR

Let's start with a few basic assumptions, a good error page is:

  1. Branded
  2. Helpful
  3. Localized
  4. Fast

Out of the box, AEM uses Sling's default error page. Most projects, however, will overlay sling/servlet/errorhandler/404 to create a branded error experience. This can cause performance problems with Dispatcher. Let's look at a typical go-live example.


  1. A user requests /us/en/sup.html
  2. Dispatcher doesn't have it in cache, so it must...
  3. Ask Publish if it has the resource
  4. Publish returns our branded 404.
  5. Dispatcher will not cache any error documents.
  6. Apache sends the user the branded 404 at the original request path.

This creates several problems. The big one? The request lifecycle above is for every request. This means we're constantly talking to Publish when a document 404s. What happens if a marketer sends out a bad URL? You'll DDoS yourself.

By combining a few clever configs, we can achieve our four original tenets for a good error page. Let's look at the first request:


  1. Our user requests a page.
  2. Dispatcher does not have it in cache. It must check with Publish.
  3. Publish doesn't have the page in question.
  4. Publish serves up the default Sling error page (it's... not attractive).
  5. Dispatcher identifies the error, doesn't cache, but let's Apache handle the request with the ErrorDocument directive.
  6. Dispatcher makes another Publish request based on the ErrorDocument path.
  7. Publish responds with a 200 and our branded 404 error page.
  8. Dispatcher caches the new 404 page.
  9. Apache delivers our 404 content at the original location (with the correct 404 status code).

This approach may seem complex, but let's take a look at every subsequent request:


In every following request, Publish can be hyper-efficient responding to 404s with the default Sling error page, but dispatcher will continue to keep the branded and localized version in cache for fast delivery. No more rendering of components for every error.

So, how can we achieve this? Let's start with the AEM layer.

AEM

This is the easy part. Your 404 page can be a simple content page built with your components. You build this page as you would any other. Want search? Add a search component. Want a sitemap, add your sitemap component. Best of all, this can follow your typical localization process to propagate to all other locales and languages.

Dispatcher

With Dispatcher, we need to change our http.conf to let Dispatcher pass the error to Apache. Take a look at line 24:

This let's us pass only our status code to Apache and tells it to deal with the error.

Important Note: I would strongly recommend only specifying the error codes you have HTML content for. There may be scenarios where you deliver a 500 and a JSON string to be handled client-side (handlebars, angular, etc.).

Apache

Our final piece. As of Apache 2.4.12, you can use 'variables' with an ErrorDocument. Here's how we can deliver our localized error pages:

By using an if statement and routing our locale, we can insert the correct ErrorDocument for a given locale.

Additional Reading

dispatcherapacheerrordocument

Introduction

Building an AEM Admin interface, also known as a console, is not a trivial task at first glance. This post describes how to create an admin console for AEM 6.2 to list, sort, create, and delete centralized data.

Source Code: available here

What are we building?

A method to create content in centralized location that can be shared across many different sites and locales. In our case, we will be creating calendar entries to convey data center uptime for our Pug Ranch's Live Video Feed.

Why are we building this?

Typically components get localized across many locales. This creates live copies of data across our Oak repository. Our data does not need to be localized because our data center uptime covers all locales. By centralizing the data, we remove it from the localization process and remove unnecessary localized nodes.

High Level Design

The design can be broken down into three high-level pieces.

  1. Admin Console Menu Item
  2. Admin Console Content, Components, and Clientlibs
  3. Servlet to handle creating and editing of data

Creating Console Menu Item

Below is the structure of our menu item. It's important to know that we need to overlay the existing libs/cq/core/content/nav/tools/operations nodes. 

Notes:

  • The id corresponds to the consoleId (defined later)
  • Icon should be labeled with a corresponding Coral UI icon name.
  • Href should point to the location of your console (defined later)


The full uptime menu item JCR node description:

Once you have made the above nodes, you will be able navigate to AEM (1) Tools (2) Operations (3) and Data Center Uptime (4).

Admin Console Content, Components, and Clientlibs

The next piece we need to build is the actual console and the components within the console. This is the most complex piece, so let's break this down further:

  1. Content
    1. Admin - This is the main view to list our entries. It's defined by a .content.xml
    2. createEntry - This is the create view. It's is also defined by a .content.xml
    3. editEntry - This is the edit view. Surprise, also defined by a .content.xml
  2. Components
    1. Datasource - This is a JSP that provides data (our entries) to our list.
    2. Uptimeentry - As we iterate through our list of entries, this defines the view for each entry.
    3. Uptimeform - This handles both create and edit operations.
  3. Clientlibs - This single JS will provide:
    1. A Granite Delete Confirmation Dialog
    2. AJAX Call to delete an entry. It leverages the SlingPostServlet for simplicity.
    3. Pass a path parameter to our editEntry page.

Content / Admin

This file is huge, so go take a look here. If you've ever worked with ui.content this file should feel familiar as we're declaring almost all of our layout in XML.

Notes:

  • consoleId should match our Menu Item "id" we created above.
  • The clientlibs node allows us to include clientlibs. In our case, Coral 3 and our own uptime clientlib.
  • Our list and datasource are what pull in the list of entries. The datasource component will be covered later.
  • itemResourceType is used to display each entry. It will also be covered later.
  • Within actions, the selection nodes (delete, edit) are used when selecting an item in the list. 
  • Within actions, the secondary action always shows and is used for create. It's uses a plain href property since no params need to be passed.

Content / createEntry & Content / editEntry

You can find createEntry here. You can find editEntry here.

Notes:

  • These files are identical with the exception of one line: the text property of the title node.
  • The content node that has a property of granite/ui/components/coral/foundation/form. This has a property of action that submits to our servlet, /bin/pugranch/uptimeEntry which will be defined later.
  • The node uptimeform has a sling:resourceType of pugranch/admin/ext/uptime/components/uptimeform which we will build later using Sightly, Coral, and HTML.

Components / datasource

This component is not a typical front-end style component. We only need the folder and the JSP. You can find the source here.

Notes:

  • The goal of the data source is to find resources, and build an iterable list of those resources.
  • It's important to know that for any value you want exposed, you must add that to the ValueMap of the child. You cannot rely on typical resource methods I.E. resource.getPath, resource.getName if you do not include those values in the datasource.
  • Working in JSP is not fun, but it's a bit more direct than Sightly + Java.

Components / uptimeentry

This component takes the entries from our data source and uses the values provided to display each entry. Source here.

Notes:

  • Again, it's important to know that the only values we have available to us are coming directly from the datasource. Typical methods you would use to get values from a resource are not available here.
  • Note data-id and data-path. Our Javascript will be acting on these properties as needed.
  • A lot of the Coral UI classes and naming conventions are using 6.2 conventions. Your mileage may very.

Components / uptimeform

This component is the inside of our createEntry and editEntry form. In the case of editEntry, we have a use class to provide additional logic to pull in information. Source here.

Notes:

  • We're again using some Coral Kung Fu to create our form elements.
  • We are using a Sling convention, :redirect to provide our servlet with the location we want to be redirected to upon successful entry creation.
  • Remember, our form action and method were declared in our content nodes, so the do not need to be declared here.
  • The step value (.01) seems to have a bug with rouding, so we fix this in our servlet.

Clientlibs / pugranch.uptime

The client library only contains on Javascript file. It's relatively straight-forward with only four functions. The source is here.

Functions to Note:

  • Register the buttons during a selection change.
  • Toggle the Delete Dialog when "Delete" is clicked.
  • Handle the Confirm Deletion - Uses an AJAX call and Sling Post Servlet conventions to delete the node.
  • Handle the "Edit" action - Passes the path to the editEntry.

Servlet


UptimeEntryServlet.java

The Uptime Entry Servlet Performs the functions listed below. Source code here.

  1. Create an uptimeCalendar sling:OrderedFolder if needed.
  2. Create or update an uptime entry. The naming convention is YEAR-Month.
  3. Redirect to the location based on :redirect.

Conclusion

In the end, you should have something that resembles the screenshot below. It can seem daunting at first, but if you walk through each piece, creating an admin console can be very similar to a lot of core AEM concepts.

Resources