It’s no doubt that AEM’s Style System for Components has become more popular over the past few years, and is used by almost every author in this industry. However, there are some feature flaws in the product for the authoring experience, where sometimes we would like to show or hide Touch UI tabs depending on which style system option has been selected for a particular AEM Component.
In this blog article, I have created a solution for this. I have created a custom fix to the issue of hiding or showing Touch UI tabs for a particular component based on which style system has been selected for a given component; so the selected style system controls the hide or show the Touch UI tabs.
How the solution works:
The AEM developer will add code configurations to the cq_dialog.xml which targets one or more tabs with some special properties and nodes like the “granite:class” and the “granite:data”. After configuring these properties and nodes within the cq_dialog.xml, all tabs will be hidden as the initial rendering phase (during configuration of the component), Upon selection of a Style System, the tabs will be visible based on the matched configuration that is set under the granite:data node properties, based on the StyleSystem CSS class that was set.
The Steps
In this section, I will share with you the steps I have used to make this work as expected. In this section you will be:
- Pre Requirements.
- Creating a new component named Example Component.
- Creating Sling Model for the Example Component.
- Creating the Touch UI custom JavaScript implementation.
- Testing it out.
- Configuration Explanation of the Touch UI Granite Tab Fields.
0. Pre Requirements
The creation of this solution to hide or show Touch UI tabs for a particular component based on which style system is selected is based on the best practices of creating AEM components, following the guidelines of the AEM Core Components. This means that when a component is created, it requires a sling model to extends the com.adobe.cq.wcm.core.components.util.AbstractComponentImpl class. In turn the extension of the com.adobe.cq.wcm.core.components.util.AbstractComponentImpl will provide helpers that will expose the appliedCssClassNames property when the component is called with model.json.
Next, the sling model must declare the org.apache.sling.models.annotations.Exporter annotation, so model.json can be called upon for the particular component.
1 2 3 4 5 6 7 8 9 10 11 12 | path: http://localhost:4502/content/sourcedcode/test/_jcr_content/root/container/container/examplecomponent_536346544.model.json { "id":"examplecomponent-d4c74a60c1", "dataLayer":{ "examplecomponent-d4c74a60c1":{ "@type":"sourcedcode/components/examplecomponent", "repo:modifyDate":"2023-04-15T23:49:42Z" } }, ":type":"sourcedcode/components/examplecomponent", "appliedCssClassNames":"show-tab-two-now" } |
Module B within the article will show you how to setup the sling model to export JSON as mentioned above.
A. Create a new component as Example Component
You will be creating a new component as Example Component where I will install a simple example of the sightly component content’s a simple Touch UI configuration, and also a simple _cq_design_dialog, so you can actually configure the style system for your given component.
Please note that all files created should be placed under: /ui.apps/src/main/content/jcr_root/apps/sourcedcode/components/examplecomponent
- Create a new folder /apps/sourcedcode/components/examplecomponent
- Create an “examplecomponent.html” file under /apps/sourcedcode/components/examplecomponent with the file contents of:
1Example Component
- Create a “.content.xml” file under /apps/sourcedcode/components/examplecomponent with the file contents of
1
2
3
4
5<?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"
jcr:primaryType="cq:Component"
jcr:title="Example Component"
componentGroup="SourcedCode - Content"/> - Create a “_cq_editConfig.xml” file under /apps/sourcedcode/components/examplecomponent with the file contents of
1
2
3<?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"
jcr:primaryType="cq:EditConfig"/> - Create a “_cq_dialog” folder under /apps/sourcedcode/components/examplecomponent
- Create a “.content.xml” file under /apps/sourcedcode/components/examplecomponent/_cq_dialog with the file contents below.
We have included custom granite properties to this file in order for this feature to work. If you would like to understand exactly what as added, please click here to read it in the last section.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" 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"
jcr:title="Example Component"
sling:resourceType="cq/gui/components/authoring/dialog">
<content
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<tabs
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/tabs">
<items jcr:primaryType="nt:unstructured">
<tab1
jcr:primaryType="nt:unstructured"
jcr:title="Tab 1"
sling:resourceType="granite/ui/components/coral/foundation/container"
margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<columns
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"
margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<graniteExample1
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldDescription="Just an example"
fieldLabel="graniteExample1"
name="./graniteExample1"
required="{Boolean}false"/>
</items>
</column>
</items>
</columns>
</items>
</tab1>
<tab2
granite:class="cq-dialog-stylesystem-tab-showhide"
jcr:primaryType="nt:unstructured"
jcr:title="Tab 2"
sling:resourceType="granite/ui/components/coral/foundation/container"
margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<columns
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"
margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<graniteExample2
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldDescription="Just an example"
fieldLabel="graniteExample2"
name="./graniteExample2"
required="{Boolean}false"/>
</items>
</column>
</items>
</columns>
</items>
<granite:data
jcr:primaryType="nt:unstructured"
showTabOnAppliedCssClass="show-tab-two-now"
tabTitleName="Tab 2"/>
</tab2>
<tab3
jcr:primaryType="nt:unstructured"
jcr:title="Tab 3"
sling:resourceType="granite/ui/components/coral/foundation/container"
margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<columns
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"
margin="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<column
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<graniteExample3
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
fieldDescription="Just an example"
fieldLabel="graniteExample3"
name="./graniteExample3"
required="{Boolean}false"/>
</items>
</column>
</items>
</columns>
</items>
</tab3>
<styletab
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/include"
path="/mnt/overlay/cq/gui/components/authoring/dialog/style/tab_edit/styletab"/>
</items>
</tabs>
</items>
</content>
</jcr:root> - Create a “_cq_design_dialog” folder under /apps/sourcedcode/components/examplecomponent
- Create a “.content.xml” file under /apps/sourcedcode/components/examplecomponent/_cq_design_dialog with the file contents below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" 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"
jcr:title="Example Component"
sling:resourceType="cq/gui/components/authoring/dialog">
<content
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/container">
<items jcr:primaryType="nt:unstructured">
<tabs
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/tabs"
maximized="{Boolean}true">
<items jcr:primaryType="nt:unstructured">
<styletab
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/coral/foundation/include"
path="/mnt/overlay/cq/gui/components/authoring/dialog/style/tab_design/styletab"/>
</items>
</tabs>
</items>
</content>
</jcr:root> - You are now done with the creation of the example component.
B. Create Sling Model for the Example Component
This is a very simple implementation for a sling model for the Example Component. The reason why we need to setup the Sling Model backend for our component because we need to obtain the “appliedCssClassNames” property from the configured component, which is configured by the content authors. If you have not already, please take a look at the pre-requirements section of this website, where calling the model.json of this particular component should expose the “appliedCssClassNames” which is vital for this solution to work. Implementing com.adobe.cq.wcm.core.components.util.AbstractComponentImpl is a best practice of the WCM Core Components where it promotes new features like the data-layer, and should be apart of your development tool kit when developing more modern AEM components.
Next, the sling model must declare the org.apache.sling.models.annotations.Exporter annotation, so model.json can be called upon for the particular component; like http://localhost:4502/content/sourcedcode/test/_jcr_content/root/container/container/examplecomponent_536346544.model.json.
Being able to resolve .model.json is vital for the hide or show Touch UI tabs for a particular component based on which style system is selected to work.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package com.sourcedcode.core.internal.models; import com.adobe.cq.export.json.ExporterConstants; import com.adobe.cq.wcm.core.components.util.AbstractComponentImpl; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.models.annotations.DefaultInjectionStrategy; import org.apache.sling.models.annotations.Exporter; import org.apache.sling.models.annotations.Model; @Model(adaptables = SlingHttpServletRequest.class, resourceType = ExampleComponentImpl.RESOURCE_TYPE, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL) @Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION) public class ExampleComponentImpl extends AbstractComponentImpl { public static final String RESOURCE_TYPE = "sourcedcode/components/examplecomponent"; } |
C. Create the Touch UI custom JavaScript implementation
You will be creating a clientlib-author Client Library which will be executed inthe editor.html authoring page. The Javascript should be executed upon opening up a Touch UI dialog.
Please note that all files created should be placed under: /ui.apps/src/main/content/jcr_root/apps/sourcedcode/clientlibs/clientlib-author
- Create the “clientlib-author” folder under /ui.apps/src/main/content/jcr_root/apps/sourcedcode/clientlibs
- Create a “.content.xml” file under /ui.apps/src/main/content/jcr_root/apps/sourcedcode/clientlibs with the file contents of:
To understand how to add a Custom Client Library for AEM Author Editor Touch UI, click here.1
2
3
4
5<?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"
jcr:primaryType="cq:ClientLibraryFolder"
categories="[cq.authoring.editor.sites.page.hook]"
dependencies="[cq.authoring.editor.sites.page]"/> - Create the “js” folder under /ui.apps/src/main/content/jcr_root/apps/sourcedcode/clientlibs/clientlib-author
- Create a file named “stylesystem-tab-showhide.js” with the contents inside:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76/**
* Author: Brian Kasing Li @ https://www.linkedin.com/in/briankasingli
* Documentation: https://sourcedcode.com/blog/aem/aem-touch-ui-show-hide-tabs-by-style-system-configurgation
**/
(function (document, $) {
"use strict";
var CONST = {
TARGET_GRANITE_CLASS: '.cq-dialog-stylesystem-tab-showhide',
GRANITE_DATA_PROP_TAB_TITLE_NAME: 'tabtitlename',
GRANITE_DATA_PROP_SHOW_TAB_ON_APPLIED_CSS_CLASS: 'showtabonappliedcssclass',
APPLIED_CSS_CLASS_NAMES: 'appliedCssClassNames',
}
/**
* foundation-contentloaded
* adding event listener to the Touch UI popup, so when the Touch UI is envoked, the code in will be executed.
* - initially, any TouchUI tabs which includes granite:class="cq-dialog-stylesystem-tab-showhide" will be hidden.
* - upon response from the .model.json ajax request, we will showTabs based on what's configured for "tabtitlename" and "showTabOnAppliedCssClass".
*
* @returns void
*/
$(document).on("foundation-contentloaded", function (e) {
if ($(e.target).prop('tagName') === 'CORAL-DIALOG') {
hideTabs($(e.target));
var url = $(e.target).find('.cq-dialog').attr('action') + ".model.json";
$.getJSON(url, function(data) {
if (data[CONST.APPLIED_CSS_CLASS_NAMES]) {
showTabs($(e.target), data[CONST.APPLIED_CSS_CLASS_NAMES]);
}
});
}
});
/**
* this finds all Touch UI fields with the cssClass .cq-dialog-stylesystem-tab-showhide, which should be found as an attribute
* under sling:resourceType="granite/ui/components/coral/foundation/container", which "hides" all tab options from the authors.
* @returns void
*/
function hideTabs($parentEle) {
$parentEle.find(CONST.TARGET_GRANITE_CLASS).each(function() {
var $this = $(this);
var tabtitlename = $this.data(CONST.GRANITE_DATA_PROP_TAB_TITLE_NAME);
$('._coral-Tabs-itemLabel').each(function(i) {
if ($(this).html() === tabtitlename) {
$(this).closest('coral-tab').hide();
}
})
});
}
/**
* after a successful response of the .model.json ajax request,
* this finds all Touch UI fields with the cssClass .cq-dialog-stylesystem-tab-showhide, which should be found as an attribute
* under sling:resourceType="granite/ui/components/coral/foundation/container". Based on the set properties of
* granite:data values set for "tabtitlename" and "showTabOnAppliedCssClass", the matched tabs will be "visible" from the authors.
*
* @returns void
*/
function showTabs($parentEle, appliedCssClassNames) {
$parentEle.find(CONST.TARGET_GRANITE_CLASS).each(function() {
var $this = $(this);
var tabtitlename = $this.data(CONST.GRANITE_DATA_PROP_TAB_TITLE_NAME);
var targetappliedcssclass = $this.data(CONST.GRANITE_DATA_PROP_SHOW_TAB_ON_APPLIED_CSS_CLASS) || 'INVALID_APPLIED_CSS_CLASS';
if (!appliedCssClassNames.split(' ').includes(targetappliedcssclass)) {
$('._coral-Tabs-itemLabel').each(function(i) {
if ($(this).html() === tabtitlename){
$(this).closest('coral-tab').show();
}
})
}
});
}
})(document, Granite.$); - Create a “js.txt” file under /ui.apps/src/main/content/jcr_root/apps/sourcedcode/clientlibs with the file contents of:
1
2#base=js
stylesystem-tab-showhide.js - You are now finished creating the clientlib-author client library.
D. Finally, test it out.
- Edit the component policies within the editable template, to include a style system class of “show-tab-two-now”
- Edit a page of your choice, and drag the Example Component into the page
- Configure the component to add style system “show-tab-two-now” for the given component
- Note: if “show-tab-two-now” is not an option as a Style System for your current component, then you must add some policies for your component.
- Now you should see Tab 2 visible.
- Done
E. Configuration Explanation of the Touch UI Granite Tab Fields
After the installation of the client library custom code, you should be able to start adding properties to your Touch UI code, for this feature, like the screenshot below:
Indicating in the screenshot above, you have realized that we are introducing granite:class=”cq-dialog-stylesystem-tab-showhide”line:43 which will be placed at the root level of the tab XML structure; when this property is placed, when author initially call the TouchUI popup, all tabs with this granite:class will be hidden. Inside of the tab XML structure, we introduced a new node configuration, line:70,as granite:data, which introduced two new properties.
The granite:data has two properties that will determine which tab to show, and also which option of style system has been configured, so we can show the tab as expected.
Note:, since we are using the granite namespace, you need to ensure your XML declaration for the Touch UI includes: xmlns:granite=”http://www.adobe.com/jcr/granite/1.0″. You can see this example from the Touch UI code configuration that we have installed line:2 in our code examples.
properties:
- tabTitleName: the exact name of the tab (jcr:title). Taking a look at the screenshot above line:73 this property matches exactly what is under line:43, the jcr:title.
- showTabOnAppliedCssClass: this is the CSS class name that has been configured by the content authors via AEM’s style system; this field must match exactly to the style system CSS class that has been configured by the component policies. The CSS class set here is the target’s the Style System CSS class name that should make the table visible when matched.
Summary
Please understand that even if the tabs are hidden from the Touch UI, the existing properties that are already content authored without this feature will not be removed as expected, and Sightly may continue to render content on the page; this means that… if for example, I have the style system enable or disable the Touch UI checkbox to “open new tab”. if this field was already content authored… and this custom code hide’s the field… since the already configured component’s property is still persisted, your Sightly may be still opening the button in a new tab.
To ensure that if a style system is enabled, and you do not want Sightly to render some items on the page, you will need to make some changes to the code itself.
This is one of the most helpful guides I’ve found all year. I personally made some updates to the JS file to show/hide in real time when selecting and deselecting the style from the Styles tab.
Thanks for those kind words!