AEM Touch UI Show Hide Tabs by Style System Configuration

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.

This implementation was tested on Experience Manager 6.5 Service Pack 12 and Experience Manager as a Cloud Service 2022.10.0, but in my opinion, if your AEM environment supports the style system, AEM 6.3 SP1 + Feature Pack 20593, it should be okay.

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:

  1. Pre Requirements.
  2. Creating a new component named Example Component.
  3. Creating Sling Model for the Example Component.
  4. Creating the Touch UI custom JavaScript implementation.
  5. Testing it out.
  6. 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

  1. Create a new folder /apps/sourcedcode/components/examplecomponent
  2. Create an “examplecomponent.html” file under /apps/sourcedcode/components/examplecomponent with the file contents of:
    1
    Example Component
  3. 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"/>
  4. 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"/>
  5. Create a “_cq_dialog” folder under /apps/sourcedcode/components/examplecomponent
  6. 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>
  7. Create a “_cq_design_dialog” folder under /apps/sourcedcode/components/examplecomponent
  8. 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>
  9. 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

  1. Create the “clientlib-author” folder under /ui.apps/src/main/content/jcr_root/apps/sourcedcode/clientlibs
  2. 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]"/>
  3. Create the “js” folder under /ui.apps/src/main/content/jcr_root/apps/sourcedcode/clientlibs/clientlib-author
  4. 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.$);
  5. 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
  6. You are now finished creating the clientlib-author client library.

D. Finally, test it out.

  1. Edit the component policies within the editable template, to include a style system class of “show-tab-two-now”
  2. Edit a page of your choice, and drag the Example Component into the page
  3. Configure the component to add style system “show-tab-two-now” for the given component
  4. 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.
  5. Now you should see Tab 2 visible.
  6. 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:
Touch UI Granite UI Code Configuration Example

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.

To learn how to obtain the style system AEM component configuration In the backend code, take a look at this blog, AEM Style System Component Information with Code Sling Models.

Hello, I am an enthusiastic Adobe Community Advisor and a seasoned Lead AEM Developer. I am currently serving as an AEM Technical Lead at MNPDigital.ca, bringing over a decade of extensive web engineering experience and more than eight years of practical AEM experience to the table. My goal is to give back to the AEM Full Stack Development community by sharing my wealth of knowledge with others. You can connect with me on LinkedIn.

2 thoughts on “AEM Touch UI Show Hide Tabs by Style System Configuration

  1. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Back To Top