I recently wrote an article called, “Reusing Tabs or Fields From an Existing Dialog” showing the benefits and steps to include previously defined dialog tabs and fields in another component for reuse. The article gained some traction when it was shared on one of Adobe’s thought leadership sites. It turns out, however, I may have taken a Joe Gunchy (Read more about Joe here. He’s an idiot) approach to this solution myself and did not consider what would happen if the parent tab or widget changed and how it would affect its children.
Tomek Niedźwiedź, an experienced AEM developer from the WPP Adobe Alliance partner Cognifide, commented on the post with a very smart and elegant solution to avoid the pitfall of reusing parts of a dialog when the parent itself is changed. I thought it would be a great guest post, and asked Tomek if he would like to contribute his solution. Below is a wonderfully written article which not only fits nicely into the type of technical content I want to share, but it’s also written in a voice that reads like a great story, which makes learning the material fun.
Please enjoy his article and be sure to thank him in the comments for sharing his expertise and his time!
Reusing Tabs or Fields in Multiple Dialogs
Hi, my name is Tomek and I’m a Java/AEM developer at Cognifide. I was invited by Brad to write a follow-up on his article about tab and field reuse in AEM dialogs. If you haven’t read the original post and for some reason do not feel like reading the whole thing now, here’s a short recap.
We’re shown a working example of a component reusing a field already defined in another component.
There is a Carousel component that has a play speed configuration option which authors can use to set the amount of time, in milliseconds, between slide transitions. A playSpeed widget defined in the component’s dialog is then reused by a new component that happens to require the exact same configuration. Let’s say, for the sake of argument, that the new component shows an animation based on a set of images.
It all works fine when the new component is developed. The widget definition is read properly and the right interface is displayed to the user. Both components work like a charm. Everybody’s happy. But there’s something we’ve done that should be avoided. What is it and what are the consequences?
Right, there’s no way the user-friendly hint about the play speed in seconds that he added to the Carousel’s dialog would make sense in this context. This needs to be fixed.
I recently happened to be the new guy on one of my projects so I decided to leave little cautionary comment under the original post. Brad kindly invited me to write a whole guest post on the subject.
There’s a saying often quoted among software developers and I’m not sure who used it originally (according this answer on Stack Overflow, it may have been used for the first time as far back as 1991) but it goes something like this:
Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live. Code for readability.
Fortunately, I’m not the guy from this quote, nor have I ever worked with anyone fitting this description. In the worst-case scenario, I just write a blog about what I’ve run into. Given my writing skill, this is bad enough for the world so I think there’s no need for violence.
Anyway, the thing wrong with cross-referencing dialogs is that it makes code terse as opposed to concise. We avoided duplication but we fell into the trap of making the code so short as to hide important information.
Let’s see how we can structure a dialog for maximum readability. Think in terms of dependencies.
Here’s what we did. The relationship between the two components is simple but it’s only explicit when you look at Animation. There’s nothing in the Carousel component to tell us that parts of it are reused elsewhere. The only way to find out about this is to perform an explicit search across the entire codebase.
In Java, most developers would instinctively opt to implement a common interface and extract the common functionality to a separate class. Then use it by means of composition.
Can a similar thing be done with AEM dialogs? Yes it can. The cqinclude widget does not require us to link to a dialog specifically. In general, a path to literally anything that’s capable of returning a properly formatted JSON document should be sufficient. It’s just a piece of content that the dialog will retrieve by means of an AJAX call and use to render the right inputs.
We can easily do something like this:
When extracted, documented and placed in the right spot in the content tree, the play speed widget’s definition becomes a lot easier to reason about.
The same pattern can be easily followed in case of tabs, widgets or even arbitrary configuration nodes.
Let’s take a look at another simple, real life example to demonstrate just how flexible we can be with cqinclude.
One of the most commonly used widgets in the Classic UI is the Rich Text Editor. It’s quite powerful and highly configurable. It can appear in various components, in different contexts. Sometimes it will be customized to cater for the specific needs of a component, sometimes the configurations used across components will be very similar but minute details will differ (such as the label or size of the widget or the like). However, regardless of how many distinct components use it, it’s generally a good idea to provide a consistent authoring experience. Reusing configuration nodes can help us maintain a familiar feel across dialogs.
Let’s say that we want every Rich Text to have the same default paste behaviour and available formats and styles. We will define a common configuration in a single place in the components folder. In order to do that, create a node at:
A couple of things to note:
- a commons node was created in the components folder to keep all common definitions in one place. This is not a commonly-accepted convention but a rather a loose suggestion made for the sake of this example. Feel free to call it otherwise or place it elsewhere, it doesn’t make a difference to AEM. Whatever path can be handled by Sling will be just fine as long as you find it intuitive.
Here’s an example definition. As opposed to screenshots from CRXDE, I’m going to use an XML format that the Maven Vault Plugin uses when content is exported to a local file system (usually, to be made part of a codebase and put under version control together with the rest of the code).
In your IDE, you could see a file called richTextSettings.xml sitting under /apps/example/components/commons. Here’s what it might look like:
<?xml version="1.0" encoding="UTF-8"?> <jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" jcr:primaryType="nt:unstructured"> <edit jcr:primaryType="nt:unstructured" features="[cut,copy,delete]" defaultPasteMode="plaintext"/> <paraformat jcr:primaryType="nt:unstructured" features="*"> <formats jcr:primaryType="cq:WidgetCollection"> <p jcr:primaryType="nt:unstructured" tag="p" description="Paragraph"/> <h1 jcr:primaryType="nt:unstructured" tag="h1" description="Heading 1"/> <h2 jcr:primaryType="nt:unstructured" tag="h2" description="Heading 2"/> <h3 jcr:primaryType="nt:unstructured" tag="h3" description="Heading 3"/> <h4 jcr:primaryType="nt:unstructured" tag="h4" description="Heading 4"/> </formats> </paraformat> <styles jcr:primaryType="nt:unstructured" features="*"> <styles jcr:primaryType="cq:WidgetCollection"> <successButton jcr:primaryType="nt:unstructured" cssName="btn btn-success" text="Success Button"/> <defaultButton jcr:primaryType="nt:unstructured" cssName="btn btn-default" text="Default Button"/> <infoButton jcr:primaryType="nt:unstructured" cssName="btn btn-info" text="Info Button"/> <primaryButton jcr:primaryType="nt:unstructured" cssName="btn btn-primary" text="Primary Button"/> </styles> </styles> </jcr:root>
It’s all very simple. Just a number of RTE plugin settings to define available text formats, the paste behaviour and some custom styles, in case if the authors wished to inline some nice looking buttons.
Note that the jcr:nodeType of the node defined here is just nt:unstructured. This can be different if you want to define a tab (cq:Panel) or a widget (cq:Widget).
Here’s how the configuration can be referenced by a Rich Text Edit defined in a component dialog:
<?xml version="1.0"?> <productDescription jcr:primaryType="cq:Widget" allowBlank="true" fieldLabel="Product Description" name="./productDescription" xtype="richtext"> <rtePlugins jcr:primaryType="nt:unstructured" path="/apps/example/components/commons/richTextSettings.infinity.json" xtype="cqinclude"/> </productDescription>
All that the productDescription Rich Text Editor widget requires is a node called rtePlugins containing the right configurations. We can use the cqinclude xtype to reference our central configuration.
It doesn’t matter to the cqinclude if the node being included is of the type nt:unstructured, cq:Panel or cq:Widget. This allows us to flexibly mix pieces of functionality while maintaining the right level of granularity.
At the same time, by keeping this configuration separate, we avoid cross-referencing components. Wherever the common part is used, it is very clear to the developer that it is a shared piece of content and that multiple components may be affected. It’s also easy to find these components by searching for the occurrences of the shared bit’s path across dialogs.
About Tomek Niedźwiedź
Tomek is a Java/AEM developer with experience in CQ and AEM projects utilizing both the Cognifide technology stack (Slice, Zen Garden) and out of the box solutions provided by AEM (such as Sightly or Sling Models). A strong believer in the importance of communities and knowledge sharing in the world of software development, he’s an active user of Stack Exchange sites and is part of Cognifide’s internal training team introducing new developers into the basics of AEM development. Eager to go on-site, he works closely with Cognifide’s clients, providing them with technical knowledge and helping overcome their uncertainties.
About the WPP Adobe Alliance
The WPP Alliance is a subset of the network’s digital agencies and an Adobe partner in implementing Adobe Marketing Cloud solutions. With a proven track record of collaboration and delivering implementation and marketing services, the WPP Alliance allows clients to leverage the network’s strengths when client needs go beyond the expertise or footprint of a single agency. Included in the WPP Alliance are VML, Inc, Cognifide, Acceleration, Mirum, KBM Group, DT, and Wunderman.