Tech Insights

Classic to Touch UI Migration for AEM: Page Properties

In our previous post on Classic to TouchUI migration we described some “quirks” that we have encountered. In this new post, we focus on the issue of migrating Page Properties, which must be done upfront since AEM doesn’t provide a compatibility mode for them, as it does for other dialogs.

Adobe Experience Manager provides Page Properties that store a page’s meta information, as well as some other page-related information. For most projects, customization of page properties across multiple templates is very common, so an approach to migrating these customizations to Touch UI in a large-scale AEM project is critical—in addition to the component dialogs themselves.

Why? If your plan is to switch the default AEM authoring experience to Touch UI, your publishers may be frustrated having to switch back to the Classic UI only to modify a page’s properties. There’s a switching cost here that needs to be addressed.

Page properties are defined in the dialog of the page rendering component which also must be migrated to TouchUI. But while AEM provides a so-called compatibility mode for component dialogs, there isn’t one for the page properties. Instead, the dialog of a default page (under /libs) is used.

Here are some tips that we’ve learned from migrating a large project’s page properties to TouchUI.

Prior to migration, collect all the information about all the customizations across all the templates

This might sound obvious, but it’s not so simple! Consider the following example: if we need to change a single field label in a page properties dialog in ClassicUI, we end up with this code:

<?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:Dialog"
   height="400"
   stateful="false"
   xtype="dialog">
   <items jcr:primaryType="cq:WidgetCollection">
       <tabs jcr:primaryType="cq:TabPanel">
           <items jcr:primaryType="cq:WidgetCollection">
               <basic
                   jcr:primaryType="cq:Widget"
                   path="/apps/my-project/components/page/tab_basic.infinity.json"
                   xtype="cqinclude"/>
               <common
                   jcr:primaryType="cq:Widget"
                   path="/libs/foundation/components/page/tab_common.infinity.json"
                   xtype="cqinclude"/>
                        …
<!--all other tabs are not changed but still need to be defined!-->
            </items>
       </tabs>
   </items>
</jcr:root>
See more See less
<?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="cq:Panel"
   title="Basic">
   <items jcr:primaryType="cq:WidgetCollection">
       <basic
           jcr:primaryType="cq:Widget"
           collapsed="{Boolean}false"
           collapsible="{Boolean}false"
           xtype="dialogfieldset">
           <items jcr:primaryType="cq:WidgetCollection">
               <title
                   jcr:primaryType="cq:Widget"
                   fieldLabel="My awesome title"
                   name="./jcr:title"
                   xtype="textfield"/>
              <!--the rest of the tab is the exact copy of tab_basic from libs-->
See more See less

As you can see, in ClassicUI even a small customization results in a significant amount of overrides. Luckily, for TouchUI, Sling Resource Merger resolves this problem (more on that later). So, listing all new tabs as well as the customizations made to out-of-the-box tabs will help you to reduce the amount of code (and overall work) that has to be done.

Consider using the Dialog Conversion Tool

The Dialog Conversion Tool might not solve all of your problems, but it’s a good starting point. We used it initially to migrate a couple of dialogs and then made some tweaks and fixed some bugs. Here are some issues that we’ve encountered along the way:

  • Select fields with ACS lists as values required some special attention, because in TouchUI their configuration differs from Classic. Of course, for such special cases, a custom Rewrite Rule can be created, but we decided to fix these cases manually.
  • Default values on select fields are also a little bit tricky. In Coral3 select fields, the preselected option is specified by setting selected=true to the option itself, while in ExtJS the preselected option is defined in the defaultValue attribute. Unfortunately, the Dialog Conversion Tool does not cover this case, so some special attention should be paid to these fields.
  • Also, special attention should be paid to dialog field inheritance. The Dialog Conversion Tool does not add inheritance ‘locks’ to custom fields, so if the custom field page property is subject to roll out, the cq-msm-lockable attribute should be configured for such properties.
  • The Tab’s column container is for some reason migrated to Coral2 version instead of Coral3.

Are you in IT? Join our team!

Consider reorganizing the cq:dialog file.

The output of the Dialog Conversion Tool is a single cq:dialog file containing all the tabs, even if they were included via the ‘include’ xtype. You can leave it as is, but consider moving all the tabs into a separate folder and include them into cq:dialog (as is done with default page properties).

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root fileName="_cq_dialog.xml" jcr:primaryType="nt:unstructured"
         sling:resourceType="cq/gui/components/authoring/dialog" xmlns:jcr="http://www.jcp.org/jcr/1.0"
         xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
         extraClientlibs="[cq.common.wcm,core.wcm.page.properties,cq.wcm.msm.properties]"
         xmlns:nt="http://www.jcp.org/jcr/nt/1.0">
   <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">
                   <basic jcr:primaryType="nt:unstructured"
                         path="/mnt/override/apps/my-project/components/page/tabs/basic"/>
                        <!--to override an existing tab-->
                   <advanced
                           jcr:primaryType="nt:unstructured"
                           sling:orderBefore="thumbnail"
                           sling:resourceType="granite/ui/components/coral/foundation/include"
                           path="/apps/my-project/components/page/tabs/tabAdvanced"/>
                        <!--to add a new tab-->
               </items>
           </tabs>
       </items>
   </content>
</jcr:root>
See more See less

This will significantly increase the readability and reusability of the code.

Use the Sling Resource Merger mechanism as much as possible

Our last trick is using Sling Resource Merger to significantly reduce the number of overrides. With this powerful mechanism we can provide only the “diff” (what is actually changed), instead of copying the whole structure. Here is what the Basic Tab from the previous example will look like:

<basic jcr:primaryType="nt:unstructured">
   <items jcr:primaryType="nt:unstructured">
           <column jcr:primaryType="nt:unstructured">
               <items jcr:primaryType="nt:unstructured">
                   <title jcr:primaryType="nt:unstructured">
                       <items jcr:primaryType="nt:unstructured">
                           <title jcr:primaryType="nt:unstructured"
                                   fieldLabel="My Awesome Title"/>
                       </items>
                   </title>
               </items>
           </column>
   </items>
</basic>
See more See less

As you can see, with Sling Resource Merger we only need to provide the JCR structure and the actual changes (i.e. diff) of the nodes. Besides a much cleaner structure and a smaller number of overrides, this approach also simplifies the maintenance of overridden code.

Author: Liubou Masiuk