DependsOn: The Cutting-Edge Tech Giving Wings to AEM Authoring Toolkit

This is the fourth installment in our series on AEM Authoring Toolkit — an open-source project by Exadel, Inc. The solution is an accumulation of our dev team expertise in orchestrating complex AEM installations that bring together superior browsing experience for end users and an extensive set of tools for site authors. A great part of the new and shiny features offered by the Toolkit originate from the DependsOn subsystem — a JavaScript library implemented on top of the Adobe Granite framework that provides a highly interactive user-friendly authoring interface.

We have already introduced DependsOn to the dev community in our previous article. We took a story-like approach last time, but this time we’ll take a closer look at the architecture of the library to see what advantages it brings to AEM developers.

A Journey to the Core…

Below is a diagram showing the various parts of DependsOn. Though a bit simplified, it displays an early prototype of the library and shows how it has evolved.

DependsOn structure

The left side of the diagram depicts the starting point of our project, which forms its core. Here in particular you see ObservableReference — an abstract entity implementing the observable pattern. The ObservableReference has a built-in cache intended to reduce the number of calls to the real widgets and only notify the observers when the tracked widget values change.

ObservableReference runs two kinds of references supported by DependsOn. The first one is ElementReference — an entity that helps track a single field. This kind of reference is capable of handling the “real” HTML form elements, and monitoring and retrieving their values. The other one is GroupReference. The library uses it for processing element groups that are referred to with the @@name syntax. Inside the group reference, there’s a tracking mechanism for a set of named ElementReference-s matching the provided scope (the entire dialog or one of its nested containers). The value of a GroupReference is, therefore, an array; each array item is tracked by its own ElementReference.

Having said that, we need to mention several features that allow developers to debug pages powered by DependsOn from inside the browser.

DependsOn assigns itself to the global Granite object that exists within the browser page context. Inside the DependsOn object itself, there are reference registries. To learn what references are managed right now and what exact values they expose, you need to insert this code into the browser console:

Granite.DependsOnPlugin.ElementReferenceRegistry.refs

or

Granite.DependsOnPlugin.GroupReferenceRegistry.refs

Below you can see a sample output:

Browser debug console output

It’s important to know that all ElementReference-s are automatically registered regardless of their “mentioning” in DependsOn queries. An exclusion is the “loopback” @this reference. This one can be nameless by nature and is registered as soon as it actually appears in a query. On the contrary, all the GroupReference-s are only registered upon mentioning.

All the references are assigned an “internal” ID that is used under the hood as a variable name to manage calls to references from the query.

Another important component of the DependsOn core is the QueryObserver instances. QueryObserver is essentially the link between the target element, the processed query statement, and the action to be run. During the QueryObserver initialization, the query is processed by extracting and registering literal references and replacing them with variable identifiers. As the query is processed, QueryObserver makes sure it will be notified about changes in all the recognized dependencies. The query result is recalculated both at the QueryObserver start and after the value of the referenced element changes. Such a change triggers an action over an HTML element.

A single dialog element can be a target for several QueryObserver instances.

… And a Turn to the Outside World

Now let us get back to the scheme and take a look at the right part. There are two registries that define how DependsOn interacts with the outer world. They allow users to further extend the library’s capabilities. Both being user-manageable, they ensure that the library is truly flexible, potentially even more multipurpose than when it’s out of the box.

AccessorsRegistry is a collection of simple manipulations that DependsOn should perform. They include set visibility, set value, get value, set disabled state, and a couple more. These functions can be applied to the majority of elements on a web page. The accessors are used both in DependsOn actions and in retrieving values via DependsOn references. Each of the accessors contains a selector and a definition of the mentioned actions and properties.

AccessorsRegistry operates like a stack. When a search for an appropriate accessor is under way, the registry is traversed from “top” to “bottom” until an entry with an appropriate selector and action is found. At the deepest layer of the stack, there are entries possessing default implementations of all actions and the “universal” selector. But if we define new custom accessors, they are placed at the top of the registry and hence are assigned the greatest priority.

Here’s an example of code that can be used to initialize a new custom accessor:

Custom actions are stored in ActionRegistry. Although ActionRegistry is fully interoperable with AccessorsRegistry, its functionality is different. The size of AccessorsRegistry depends on the number of web elements we target. The content of ActionRegistry, on the contrary, depends on actions implemented by the DependsOn observer.

Initially, ActionRegistry contains rather simplistic action definitions that directly match the out-of-box accessors. These actions include: set, set-if-blank, visibility, disable, readonly, and validate.

But as you might have already guessed, ActionRegistry can be supplemented with user-defined actions. It should be noted that these actions can be synchronous or asynchronous, which would allow dialog interfaces to run much more smoothly.

So, Do You Need a Millennium Falcon in Your Garage?

AEM Authoring Toolkit keeps growing, and so does DependsOn. In the most recent Toolkit version, for instance, you can find a brand new async action — i.e., fetch. The feature allows developers to search for an arbitrary JCR node property following a relative path. The result is returned in a non-blocking asynchronous fashion.

To use fetch, you can for instance define an additional field in your Java class that will declare an @Hidden widget in a component dialog. Here’s how you can do it:

Notably, parentProp will not have a value from the start — it will be assigned asynchronously and stored to a hidden HTML form field. After this, all Observers subscribed to the hidden field reference will be refreshed.

Judging from the descriptions above, you could have thought that DependsOn is a space shuttle constructed to fly to the nearest grocery store! But actually, this is just a comparatively simple and flexible architecture that fits well in the day-to-day tasks arising in our company’s projects. This library is a welcome addition to AEM developers’ toolbox.

We have full confidence that it will be capable of streamlining and easing your AEM component development tasks.

Best wishes to you, and keep an eye out for our forthcoming articles!

Authors: Aliaksei Stsefanovich, Stepan Miakchilo

Originally published at https://exadel.com on September 9, 2020.

--

--