The Story of DependsOn

Read the whole Closer Look at AEM Authoring Toolkit series

In our previous publications here and here, we presented our brand new contribution to the ever-growing AEM world – the AEM Authoring Toolkit. AEM Authoring Toolkit is aimed at bringing more fun and comfort to both back-end AEM development and the creation of attractive, high converting media resources on AEM-powered websites.

While we have spoken a lot about the API, there has so far only been a murmur about the DependsOn microframework and the dark sorcery it does. Now it’s time to shed some l/ight on it.

Intrigued? How about we start with a behind-the-scenes story to get things going?

Episode I – The Phantom Menace

Long long time not so long ago, it all began with a commercial AEM project. Not a little one, it was — densely packed with a bunch of AEM components, rather complex, demanding in maintenance, and leaving no chance for a simple migration to Touch UI.

It’s no surprise then that a pertinent question arose: who exactly should handle the authoring logic of the AEM components — the back-end or the front-end part of the team?

On the one hand, the UI folk were saying, “This is the AEM mainlands, and this is the backstage far from the end user. Hence we neither want nor need to mess with it”.

On the other hand, the Java guys obsessed with things like OSGi and thread pooling had to struggle through the dense thicket of ExtJS-boundClassic UI dialogs – all to get things neatly done with the components.

The author of these lines has always been looking toward the fullstack side of development. It was me who used to spend days sewing the parts together – a stitch here and there in the Java code base, a stitch in the clientlibs – to make the stubborn authoring dialogs surrender and obey.

Given the fact that Classic UI has a fairly big JavaScript framework under the hood, working with various field types caused a lot of trouble. And however difficult the dialogs grew, the set of scripted actions had to stay quite simplistic. Usually they only provided the possibility to hide/unhide a field or a group depending on some other field being turned on or off.

It was obvious that the effort spent to get the job done outweighed the benefits on the UX side. Clearly, we needed to take a step forward — bringing better interactivity while preserving a comparable level of effort, so that the developer team could cope with more sophisticated dialogs.

As time went by, the project grew, with the prospects to migrate to the Touch UI interface looming on the horizon. The devs started discussing the ways to automate the migration tasks (this is when the Toolkit was born). And what about me? I finally had a chance to think over the way to simplify the process of creating and maintaining the most complex authoring dialogs with greater interactivity.

The Force Awakens

To lay the foundation for the UI force, two approaches were proposed. We went on to include them in the Toolkit.
First, we needed a carcass – the plugin instance that could hook to the target field and do something to it (at least, to show or hide it). Although Adobe’s Granite framework uses a modern UI stack and generally complies with the UI design standards, it still has a lot of peculiarities. There was no chance we could get by with a simple show/hide method or applying a “hidden” class. We needed a magic key, a universal identifier that would make it possible to run something like setVisible($target, false) to hide any target (and not to render the whole dialog inoperable at the same time).

We knew that, sooner or later, we would need to handle such targets as the RichTextEditor widget with all of its bells and whistles, as well as other elements of comparable complexity. In Touch UI, we have nothing similar to the itemId identifier once offered by the ExtJS framework. Therefore we decided to look beyond the default Touch UI tools and stick to HTML elements’ data attributes. Luckily, we managed to employ the granite:data nodes we had discovered in the manuals. This produced the desired effect on dialogs. We also toyed with the idea of using extra CSS classes, but decided to pass on it because the approach did not offer sufficient flexibility.

Second, we had to ginger up the most “mysterious” part of the job – relations and conditioning (what had naturally brought to life the DependsOn motto). To that end, we needed to identify not only the element to manipulate, but also the item exposing the state to check (i.e., the “condition”). We definitely wanted to implement both simple and complex conditions, like those embracing several elements or employing some predicate logic. It was a point to stumble upon… and then the good old eval -like trick came to the rescue! Why not make it possible to express a condition by a JavaScript statement that would include references to other dialog fields? After some deliberation, we chose @ for reference definition. This looked quite natural: no interference with the ECMAScript standard definitions, and in line with the way we reference “others” in mailing or social networks.

Later, the fields exposing properties evaluated by conditions were named “references”, and the conditions themselves – the “queries”.

But this is not where our story ends. After the “proof of concept” version of DependsOn was presented to the team, we proceeded with avoiding pitfalls, discovering surprises (both pleasant and unpleasant) of Granite UI, and designing dialogs whose complexity is sometimes comparable to that of a space shuttle dashboard.

DependsOn Strikes Back

Now, let us see what the DependsOn microframework has grown to be and what issues it helps address.
A simple thing to start with. We are going to hide a single field, say, a TextField, unless Switch is on (that is, turning on the switch unhides the field).

The Java code behind the scene will go like this:

At first, we need to specially mark the referenced field – the one that will be polled by a condition checker. We’ve got the @DependsOnRef for that:

Second, we need to mark the dependent field and set the display condition for it. In our basic case, the condition will correspond to the Switch value. All we need to do is refer to it with a query.

Now, the dependent fields are up and running. That’s not much of a trick, you would say. AEM itself is capable of doing this, if you assign some (though a bit looney;) CSS classes to certain fields.

Well…okay. Let us make the situation more complicated. What if we depend on not one but two different fields? Say we’ve got some kind of a delimiter between two optional sections that needs to be displayed only if both of the sections are on. As for pure AEM, the issue is unresolvable unless we write a custom script for it. But with DependsOn, we can just reuse our previous approach without having to add or change much.

Here are the two section switches:

And now the delimiter is marked with an updated @DependsOn condition. Both section references are included, and they are joined by JS rules with a Boolean operator:

It’s worth mentioning that the annotations will be translated to granite:data attributes of the corresponding XML nodes. (Technically, you can create the same attributes directly in the XML markup if you opt to dismiss the annotations approach.) Further on, these will become the regular data-attributes of the HTML entities DependsOn will hook to. Then the DependsOn engine processes conditions, subscribes to references, and uses the calculated query result as an input for an action over the target element. Thus the cogwheels are kept turning in a proper way.

Consider another example. Say we need to implement background setup for an AEM component supporting either a plain color or an image. When the “background image” option is selected, ImagePicker must appear.

DependsOn will nicely handle both the string arguments and the value of the Select widget.

Attack of the [cloned] References

You think you’ve seen it all. But… let us take one more step! What if one of the samples above were a part of a repetitive setup – let’s say, we had two similar fieldsets, or a multifield containing mutually related widgets? This is the point where even Classic UI and its clientlibs would give way, because itemId-s cannot be repeated.

On the contrary, the DependsOnRef setting allows us to duplicate values. However, if we implemented it in a straightforward manner, only the first reference would be processed. This is because references belong to the “dialog scope” by default.

To change this, one needs to instruct DependsOn on what scope to use when searching for a reference. To that end, we can apply a regular CSS selector specifying what dialog it should search through. If we, for instance, employ coral-multifield-item as a selector, the searching scope is narrowed down to the closest multifield element.

This is not over yet 😉 What if we need all available references with the same name for a query computation? E.g., we have a multifield containing an arbitrary number of checkboxes and need to make sure that only one checkbox is marked. This makes things a great deal more complicated because we have to collect several values and not to merge them but merely validate that only one of these values has been set to a truthy value.

However, this is also easily solvable with DependsOn! We can refer to a set of elements at once and receive an array of their values. For the group reference, we have a specific syntax feature — i.e., the @@ token.

With a group reference and some basic JavaScript knowledge, we can easily define whether no more than one checkbox is selected in a group: @@default.filter((val) => val).length <= 1 (we have excluded false-s from the references array and made sure that no more than one is left).

Episode Next – Isn’t This an Action Movie?

We have learned a lot about what DependsOn references and queries are capable of. Knowing this, we can reiterate one of the starting points: DependsOn does allow you to run a multitude of actions on elements. Including your own custom actions!

The default action is the visibility change. This one does not need to be specified in @DependsOn annotation, while the others do. Still, this is quite easy. For instance, if we want to solve the above-mentioned validation task in a developed manner, we need to employ the validate action.

What has just happened in the picture above? We checked multiple “default” boxes and triggered the validation error using the validate action, the grouping reference, and a query which is only a little bit more complex than usual.

This sample also shows us that some actions can consume predefined parameters, like literal constants.

Moreover, the DependsOn conditions allow us to use the @this keyword representing self-reference. Using @this in combination with the validate action, we can effectively implement nearly any type of validation logic without resorting to the somewhat intricate internal detail of dialog validation in out-of-the-box Touch UI interfaces.

That’s how it may look like with a TextArea widget:

As our story draws to a close, let us slightly improve the UX part in our “single default item” task so that the dialog, instead of complaining about too many boxes checked, just prevents us from checking an extra one. And here’s where the disabled action comes into play.

As you can see, we just swapped the validate action in our previous sample for disabled. We also modified the query to make it disable the spare boxes in the event that any of the boxes were checked (instead of issuing an error message like before).

That being said, the story ends for now, but there’s certainly more to tell about the DependsOn capabilities. In our next article, we will dive a little deeper in the intrinsic architecture of the microframework and the possibilities it provides.

Authors: Aliaksei Stsefanovich, Stepan Miakchilo

How can we help you?
Contact Us

Recent Posts

Appery.io

New Website and Customizable Plans for Appery.io Appery.io, Exadel's low-code development platform launched a new website with added Plan Customization Options. The updates are designed to help low code developers get the most out of the development process.