AEM 6.3 Articles / Blogs / Perficient https://blogs.perficient.com/tag/aem-6-3/ Expert Digital Insights Wed, 27 Mar 2019 17:23:05 +0000 en-US hourly 1 https://blogs.perficient.com/files/favicon-194x194-1-150x150.png AEM 6.3 Articles / Blogs / Perficient https://blogs.perficient.com/tag/aem-6-3/ 32 32 30508587 The AEM Dialog Dark Mode Switcher You Never Knew You Needed https://blogs.perficient.com/2019/03/27/the-aem-dialog-dark-mode-switcher-you-never-knew-you-needed/ https://blogs.perficient.com/2019/03/27/the-aem-dialog-dark-mode-switcher-you-never-knew-you-needed/#respond Wed, 27 Mar 2019 17:23:05 +0000 https://blogs.perficientdigital.com/?p=232263

It seems like dark mode is taking over everything from IDE’s, to various apps and services (including the new MacOs Mojave).

Use Case

The use case is pretty simple. In some dialogs, you have RTE’s and want to author text in white color. But that text becomes invisible against the default, white background of dialog boxes in AEM (I believe 6.3+). See for your self:

 
So I wondered “Why not have a button that toggles between the light and dark mode?” It’s possible because Coral already supports that!

The Dark Mode Switcher

Here is an example:

How I built it:

First, add this code into a clientlib with categories="[cq.authoring.editor.hook]"

(function(){
  function getButton (clickHandler) {
    var btn = new Coral.Button()
    btn.set({
      variant: Coral.Button.variant.MINIMAL,
      size: Coral.Button.size.MEDIUM,
      icon: "wand"
    });
    btn.classList.add("cq-dialog-header-action");
    btn.on("click", function(e){
      e.preventDefault();
      clickHandler(e);
    });
    return btn;
  }
  $(document).on("dialog-loaded", function(event) {
    var $dialog = event.dialog;
    if (!$dialog || !$dialog.length) return;
    var $dialogWrapper = $dialog.closest('coral-dialog');
    var $dialogActions = $dialogWrapper.find(".cq-dialog-actions");
    // add switch button and handler
    var switchButton = getButton(function(){
      $dialogWrapper.toggleClass('coral--dark')
      });
    // add button to dialog actions
    $dialogActions.prepend(switchButton)
  });
})();

 

Please note that this was tested on AEM 6.4. I’m pretty sure it would also wok on AEM 6.3 but have not tested. Please let me know if you did!

There you have it! Dark Mode all the things! The code should be extremely simple and easy to follow. But comment if you have questions or want further assistance.

]]>
https://blogs.perficient.com/2019/03/27/the-aem-dialog-dark-mode-switcher-you-never-knew-you-needed/feed/ 0 269611
Customizing Request for Activation/Deactivation Wizards in AEM 6 https://blogs.perficient.com/2019/02/11/customizing-request-for-activation-deactivation-wizards-aem-6/ https://blogs.perficient.com/2019/02/11/customizing-request-for-activation-deactivation-wizards-aem-6/#comments Tue, 12 Feb 2019 04:29:58 +0000 https://blogs.perficientdigital.com/?p=231197

In AEM 6.3+, there is a feature that allows content authors to “Request Publication” or “Request Unpublication.” The option becomes available when the content author does not have replication permission crx:replicate on the page they are currently authoring. See image below:

Banner Photo by rawpixel on Unsplash


 

The Publish/Unpublish Page Wizard

Once the author clicks on “Request Publication,” the “Publish Page” wizard opens (see image below), and it offers a few options:

  1. It lists all Configs, Policies and Referenced assets so that it also can be requested to be published
  2. It allows you to schedule the publication
  3. It allows you to enter a description for the workflow that will be triggered (more on that below)

 

The “Request Unpublication” wizard is very similar, except it offers the option to unpublish a page but does not look for references.


By default, the OOTB “Request For Activation” workflow will be triggered for the page and all of the references/configs/policies selected in the wizard.

The OOTB “Request For Activation” workflow is located here: /libs/settings/workflow/models/request_for_activation

 

The Manage Publication Wizard

There is a second way to publish/unpublish pages, and that is the “Manage Publication” wizard. Here is how to get to it:

Once you click on “Manage Publication,” the wizard opens:

Once you go through the wizard, again,  the OOTB “Request For Activation” workflow will be triggered.
Customizing All The Wizards

DISCLAIMER: Everything here was discovered and done on AEM 6.4 SP3. Although, I’d think it should be the same on AEM 6.3+. Let me know in the comments if you find otherwise.

There is one small section on how to customize the wizard in the AEM docs here. But it only covers the “Manage Publication” wizard and not the other two wizards: “Publish Page” and “Unpublish Page Wizard.”

Overlaying Manage Publication Wizard:

Location in libs: /libs/wcm/core/content/common/managepublicationwizard but it’s an override of /libs/cq/gui/content/common/managepublicationwizard
Create the same wizard path, but under /apps, so: /apps/cq/gui/content/common/managepublicationwizard

You could also overlay /libs/wcm/core/content/common/managepublicationwizard, I went with /apps/cq/gui/content/common/managepublicationwizard because I already started doing it and did not want to change.

Then you can use this node structure:

Please note the use of sling:resourceSuperType="/libs/cq/gui/content/common/managepublicationwizard"  (See Konrad’s comment below)

<?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:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="nt:unstructured"
    sling:resourceSuperType="/libs/cq/gui/content/common/managepublicationwizard">
    <body jcr:primaryType="nt:unstructured">
        <items jcr:primaryType="nt:unstructured">
            <form jcr:primaryType="nt:unstructured">
                <granite:data jcr:primaryType="nt:unstructured"
                    requestActivationWorkflow="/etc/workflow/models/custom-request-for-activation/jcr:content/model"
                    requestDeactivationWorkflow="/etc/workflow/models/custom-request-for-deactivation/jcr:content/mode"/>
            </form>
        </items>
    </body>
</jcr:root>

 
 

Overlaying Publish Page Wizard:

Location in libs: /libs/wcm/core/content/sites/publishpagewizard
Create the same wizard path, but under /apps, so: /apps/wcm/core/content/sites/publishpagewizard. Then you can use this node structure:

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/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="cq:Page">
    <jcr:content jcr:primaryType="nt:unstructured">
        <body jcr:primaryType="nt:unstructured">
            <items jcr:primaryType="nt:unstructured">
                <form jcr:primaryType="nt:unstructured">
                    <items jcr:primaryType="nt:unstructured">
                        <wizard jcr:primaryType="nt:unstructured"
                            jcr:title="Custom Publish">
                            <items jcr:primaryType="nt:unstructured">
                                <publishstep jcr:primaryType="nt:unstructured">
                                    <items jcr:primaryType="nt:unstructured">
                                        <fixedColumns jcr:primaryType="nt:unstructured">
                                            <items jcr:primaryType="nt:unstructured">
                                                <fixedColumn1 jcr:primaryType="nt:unstructured">
                                                    <items jcr:primaryType="nt:unstructured">
                                                        <references
                                                            jcr:primaryType="nt:unstructured"
                                                            sling:resourceType="cq/gui/components/siteadmin/admin/publishwizard/references"
                                                            requestActivationWorkflow="/etc/workflow/models/custom-request-for-activation/jcr:content/model"/>
                                                    </items>
                                                </fixedColumn1>
                                            </items>
                                        </fixedColumns>
                                    </items>
                                </publishstep>
                            </items>
                        </wizard>
                    </items>
                </form>
            </items>
        </body>
    </jcr:content>
</jcr:root>

 
 

Overlaying Unpublish Page Wizard:

Location in libs: /libs/wcm/core/content/sites/unpublishpagewizard
Create the same wizard path, but under /apps, so: /apps/wcm/core/content/sites/unpublishpagewizard. Then you can use this node structure:

<?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="cq:Page">
    <jcr:content jcr:primaryType="nt:unstructured">
        <body jcr:primaryType="nt:unstructured">
            <items jcr:primaryType="nt:unstructured">
                <form jcr:primaryType="nt:unstructured">
                    <items jcr:primaryType="nt:unstructured">
                        <wizard
                            jcr:primaryType="nt:unstructured"
                            jcr:title="Custom Unpublish">
                            <items jcr:primaryType="nt:unstructured">
                                <unpublishstep jcr:primaryType="nt:unstructured">
                                    <items jcr:primaryType="nt:unstructured">
                                        <fixedColumns jcr:primaryType="nt:unstructured">
                                            <items jcr:primaryType="nt:unstructured">
                                                <fixedColumn1 jcr:primaryType="nt:unstructured">
                                                    <items jcr:primaryType="nt:unstructured">
                                                        <references
                                                            jcr:primaryType="nt:unstructured"
                                                            sling:resourceType="cq/gui/components/siteadmin/admin/unpublishwizard/references"
                                                            requestDeactivationWorkflow="/etc/workflow/models/custom-request-for-deactivation/jcr:content/model"/>
                                                    </items>
                                                </fixedColumn1>
                                            </items>
                                        </fixedColumns>
                                    </items>
                                </unpublishstep>
                            </items>
                        </wizard>
                    </items>
                </form>
            </items>
        </body>
    </jcr:content>
</jcr:root>

 

Very Important Notes

  1. I am using the same structure as the path I’m overlaying, but only updating the properties that I want to update. Namely: requestActivationWorkflow requestDeactivationWorkflow
  2. You could explore the structure under libs, you can override titles and button texts, you can also override the OOTB schedule activation/deactivation. Did not need to in my case.
  3. The path I am using for the workflow is /etc/workflow/models/custom-request-for-deactivation/jcr:content/mode even though my actual workflow model is under path /conf/global/settings/workflow/models/custom-request-for-activation I am not sure why this is, but I found it to work. If you know the answer to this, let me know in the comments!

 

The Cherry on Top

A package with all the overlays above: Download Package
 

]]>
https://blogs.perficient.com/2019/02/11/customizing-request-for-activation-deactivation-wizards-aem-6/feed/ 5 269544
Disabling the Submit Button Until All Fields Are Valid https://blogs.perficient.com/2018/10/25/disabling-the-submit-button-until-all-fields-are-valid/ https://blogs.perficient.com/2018/10/25/disabling-the-submit-button-until-all-fields-are-valid/#respond Thu, 25 Oct 2018 17:35:51 +0000 https://blogs.perficientdigital.com/?p=224089

We all run into instances where it is necessary to make some dialog fields mandatory. A myriad of cases come to mind, but one of the most popular scenarios is to require Alt text for images.
In a previous post, one of my Perficient Digital colleagues shared best practices on how to validate authoring fields. In this post, I am going to add a small extension that will allow you to disable the submit button until all the fields are valid.

How To Do This

Overlay the following code into your /apps folder:

/libs/cq/gui/components/authoring/dialog/dialog.jsp


From here you can edit the dialog.jsp by adding the class <foundation-valid-bind> to the dialog submit button as shown below:

<div class="cq-dialog-actions u-coral-pullRight">
              <button is="coral-button" icon="helpCircle" variant="minimal" <%= getHelpAttrs(slingRequest, cfg, xssAPI, i18n).build() %>></button>
              <button is="coral-button" icon="close" variant="minimal" class="cq-dialog-header-action cq-dialog-cancel" title="<%= i18n.get("Cancel")%>"></button>
              <button is="coral-button" icon="check" variant="minimal" class="cq-dialog-header-action cq-dialog-submit foundation-validation-bind" title="<%= i18n.get("Done") %>"></button>
                        </div>

Let’s Try An Example

Add the required (boolean) = true property to the following code:

/apps/weretail/components/content/heroimage/cq:dialog/content/items/column/items/title1

Try adding it to any retail site page and author the dialog. You’ll notice that when the title is missing, the dialog submit button will be disabled.

An Alternative Method

There is also an alternative way to accomplish this without overlaying the dialog by listening for a ‘foundation-content-loaded’ event on your listener script. Query for the submit button and mark the attribute as disabled.

Congrats!

Now you know how to disable the submit button with foundation API.
 

]]>
https://blogs.perficient.com/2018/10/25/disabling-the-submit-button-until-all-fields-are-valid/feed/ 0 269420
Customized Logging using SLF4J / MDC in AEM https://blogs.perficient.com/2018/09/24/customized-logging-using-slf4j-mdc-in-aem/ https://blogs.perficient.com/2018/09/24/customized-logging-using-slf4j-mdc-in-aem/#comments Mon, 24 Sep 2018 21:02:54 +0000 https://blogs.perficientdigital.com/?p=223611

Out of the box, AEM provides a pattern based logging system which comes pre-configured with an MessageFormat pattern for logging.  This is a somewhat legacy messaging format, which, for most applications, has been updated with a “Logback” implementation. The main reason for said replacement is the flexibility that logback techniques can offer, including a more customized log pattern support, in addition to the ability to add in mapped variables, or in logback terminology, Mapped Diagnostic Context, or MDC. AEM comes bundled with SLF4J, and therefore already has support for logback patterns, and more importantly, MDC.  What is missing here is how to populate the variable which needs to be logged on each request, and how to manipulate your logging patterns to accommodate said variable.

Step 1:  Create a Sling Filter

 
To utilize MDC, there are two main steps:

  • Populating the MDC Object (Map)
  • Referencing the MDC Properties (Logging Pattern)

In AEM, every request flows through a filter chain, meaning we need to populate the MDC variable early enough in the filter that we can successfully log those variables later.  The easiest way to do this is to set the scope of a custom filter to be at the request level, order 0:

/**
 * The SlingFilterScope.REQUEST is important as MDC needs to be configured prior
 * to any logger being executed in the page rendering.
 */
@SlingFilter(
        label = "Sample MDC Filter",
        description = "Sample implementation of custom MDC properties.",
        order = 0,
        scope = SlingFilterScope.REQUEST)

Then you need to import the MDC object into your class:

import org.slf4j.MDC;

The rest of the logic is up to you!  The request object gives you direct access to the following:

  • ResourceResolver (Access to JCR)
  • Request Headers
    • You can use a RequestWrapper to customize
  • Request Cookies
  • Request Parameters

And of course, you can always use a customized service user for more targeted JCR access. For sake of simplicity, this example includes logic to read a request parameter named “appId”.

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                     FilterChain filterChain) throws IOException, ServletException {
    final SlingHttpServletRequest request = (SlingHttpServletRequest) servletRequest;
    try {
        insertIntoMDC(request);
        filterChain.doFilter(request, servletResponse);
    } finally {
        clearMDC();
    }
}
private void clearMDC() {
    for (String key : MDC_CUSTOM_KEYS) {
        MDC.remove(key);
    }
}
private void insertIntoMDC(SlingHttpServletRequest request) {
    //logic can go here to take value from request object.
    // Can also utilize ResourceResolver from same request.
    // If needed, can also write a generic OSGi Configuration
    // (String Array Properties) to read from standard request objects,
    // i.e cookies, headers, and parameters.
       MDC.put(APPLICATION_ID,request.getParameter(APPLICATION_ID));
}

That is it!  Deploy the filter and we will have (the ability) to log a passed in request parameter into your log.
Here’s the full class for those copy/pasters:

package com.perficient.commons.core.filters;
import org.apache.felix.scr.annotations.sling.SlingFilter;
import org.apache.felix.scr.annotations.sling.SlingFilterScope;
import org.apache.sling.api.SlingHttpServletRequest;
import org.slf4j.MDC;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
/**
 * The SlingFilterScope.REQUEST is important as MDC needs to be configured prior
 * to any logger being executed in the page rendering.
 */
@SlingFilter(
        label = "Sample MDC Filter",
        description = "Sample implementation of custom MDC properties.",
        order = 0,
        scope = SlingFilterScope.REQUEST)
public class MDCLoggerFilter implements javax.servlet.Filter{
    public static final String APPLICATION_ID = "appId";
    private static final String[] MDC_CUSTOM_KEYS = {
            APPLICATION_ID,
    };
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
                         FilterChain filterChain) throws IOException, ServletException {
        final SlingHttpServletRequest request = (SlingHttpServletRequest) servletRequest;
        try {
            insertIntoMDC(request);
            filterChain.doFilter(request, servletResponse);
        } finally {
            clearMDC();
        }
    }
    private void clearMDC() {
        for (String key : MDC_CUSTOM_KEYS) {
            MDC.remove(key);
        }
    }
    private void insertIntoMDC(SlingHttpServletRequest request) {
        //logic can go here to take value from request object.
        // Can also utilize ResourceResolver from same request.
        // If needed, can also write a generic OSGi Configuration
        // (String Array Properties) to read from standard request objects,
        // i.e cookies, headers, and parameters.
           MDC.put(APPLICATION_ID,request.getParameter(APPLICATION_ID));
    }
    public void destroy() {
    }
}

 

Step 2: Updating the Logging Pattern

This step actually helped uncover an issue in the latest AEM versions.  At time of writing, AEM “Logging Logger Configurations” do not properly leverage the “Apache Sling Logging Logger” pattern variable.  Instead, it references the “default” pattern, found in the configuration for org.apache.sling.commons.log.LogManager. As a work-around, we will update the “default” LogManager instead of updating the application-specific “Logging Logger” pattern. You can perform this update directly in AEM:
org.apache.sling.commons.log.LogManager Configuration Example
Or, my recommendation, create an associated OSGi configuration within your code repository’s associated config folder ( /apps/<project>/config) named  org.apache.sling.commons.log.LogManager.xmlwith the following contents:

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/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="sling:OsgiConfig"
      org.apache.sling.commons.log.pattern="%d{dd.MM.yyyy HH:mm:ss.SSS} *%level* [%X{appId:-NoAppId}] [%thread] %logger %msg %ex%n"
      org.apache.sling.commons.log.file.size="'.'yyyy-MM-dd"
      org.apache.sling.commons.log.file="logs/error.log"
      org.apache.sling.commons.log.file.number="7"
      org.apache.sling.commons.log.level="info"
      org.apache.sling.commons.log.maxOldFileCountInDump="3"
      org.apache.sling.commons.log.numOfLines="10000"
      org.apache.sling.commons.log.maxCallerDataDepth="7"
      org.apache.sling.commons.log.packagingDataEnabled="{Boolean}false"/>

You will notice that in the above example I use a different format for the pattern string.  One that may look more familiar is the default MessagePattern format:

{0,date,yyyy-MM-dd HH:mm:ss.SSS} {4} [{3}] {5}

In this default pattern, each number corresponds with a given data point, as described in the official docs. In our case, you see variables prefixed with a percentage sign.  Odd!  Well, fortunately, there is also a mapping for these, which we can also map using the official logback documentation.
The most interesting of the group is the %X variable.  This is what exposes your custom MDC variables.  In the above example, %X{appId:-NoAppID}” defines a placeholder for a property named “appId” as well as its default text, “NoAppId”.  The standard for supplying defaults for any MDC object is to use the “:-” delimeter followed by the default text.  Therefore, %X{appId} would output an empty string if null, whereas %X{appId:-NoAppId} would output “NoAppId” if null.
Similarly, to output all of the configured MDC variables, simply do not specify the variable you want to output: %X. If configured this way, the entire MDC map would be output as a comma separated list.
For a much deeper look on all possible variable mappings, I would highly suggest taking a look at the official logback documentation, which I will link to again here:  https://logback.qos.ch/manual/layouts.html#conversionWord

Step 3: Check out the results

Once you’ve pushed the updated configuration and your custom sling filter, the changes should be active!  If you followed the exact steps above, you’ll see a lot of “NoAppID” messages. Do not fret – this is because we never requested a page with the “appId” request parameter.  For example, a simple we-retail request ( http://localhost:4502/content/we-retail/ca/en/experience.html ) should result in:

24.09.2018 12:41:36.819 *WARN* [NoAppId] [0:0:0:0:0:0:0:1 [1537818095824] GET /content/we-retail/ca/en/experience.html HTTP/1.1] com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl No library configured at /etc/designs/we-retail
24.09.2018 12:41:36.915 *INFO* [NoAppId] [0:0:0:0:0:0:0:1 [1537818095824] GET /content/we-retail/ca/en/experience.html HTTP/1.1] com.day.cq.wcm.core.impl.designer.SystemDesign Initialized system design at /etc/designs/default in 13ms
24.09.2018 12:41:38.432 *WARN* [NoAppId] [0:0:0:0:0:0:0:1 [1537818095824] GET /content/we-retail/ca/en/experience.html HTTP/1.1] com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl No library configured at /apps/clientlibs/granite/jquery-ui
24.09.2018 12:41:40.303 *INFO* [NoAppId] [0:0:0:0:0:0:0:1 [1537818100301] GET /home/users/Z/ZxNe0kWWedMwcd2ZAFhp.infinity.json HTTP/1.1] org.apache.sling.engine.impl.SlingRequestProcessorImpl service: Resource /home/users/Z/ZxNe0kWWedMwcd2ZAFhp.infinity.json not found

However, if we change the URL to add our appId, http://localhost:4502/content/we-retail/ca/en/experience.html?kp.appId=RMAPP, we get the following:

24.09.2018 12:43:39.305 *WARN* [RMAPP] [0:0:0:0:0:0:0:1 [1537818219292] GET /content/we-retail/ca/en/experience.html HTTP/1.1] com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl No library configured at /apps/clientlibs/granite/jquery-ui
24.09.2018 12:43:39.305 *WARN* [RMAPP] [0:0:0:0:0:0:0:1 [1537818219292] GET /content/we-retail/ca/en/experience.html HTTP/1.1] com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl No library configured at /etc/cloudsettings/default/contexthub.kernel
24.09.2018 12:43:39.309 *WARN* [RMAPP] [0:0:0:0:0:0:0:1 [1537818219292] GET /content/we-retail/ca/en/experience.html HTTP/1.1] com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl No library configured at /etc/designs/we-retail
24.09.2018 12:43:39.436 *WARN* [RMAPP] [0:0:0:0:0:0:0:1 [1537818219292] GET /content/we-retail/ca/en/experience.html HTTP/1.1] com.adobe.granite.ui.clientlibs.impl.HtmlLibraryManagerImpl No library configured at /apps/clientlibs/granite/jquery-ui

Obviously, the true power of these entries will show when leveraging JCR and the Resource Resolver. Potentially a topic for another day.  Have fun and good luck tinkering!

Kudos

I want to give appropriate recognition to the initial implementation in which this was derived: https://github.com/chetanmeh/sling-logback.  Unfortunately, this package no longer functions without modification on an 6.3 instance. The logic for setting OSGi configuration properties for header, cookie, and parameters could be mirrored in this example if desired.

]]>
https://blogs.perficient.com/2018/09/24/customized-logging-using-slf4j-mdc-in-aem/feed/ 1 269360
Using Sling Models With Nested Composite Mulitifields in AEM 6.3+ https://blogs.perficient.com/2018/08/24/using-sling-models-with-nested-composite-mulitifields-in-aem-6-3/ https://blogs.perficient.com/2018/08/24/using-sling-models-with-nested-composite-mulitifields-in-aem-6-3/#comments Fri, 24 Aug 2018 18:40:35 +0000 https://blogs.perficientdigital.com/?p=223224

You probably already know that Adobe added support for composite multifield in 6.3. Adobe also added support for nested multifield and nested composite multifield in AEM 6.4.
 

Nested? Composite? Huh?

Take a look at the AEM 6.4 docs for multifield

Let’s define them
 

Multifiled:

Allows authors the ability to add a list of items, each item let’s call it a fieldset, has only one field. Example, a list of emails.

Composite Multifield

Same as a normal multifield, but can handle multiple fields in the fieldset. Example, a list of addresses where each address has multiple fields: street, city, state and zip.

As of 6.3+ Composite multifields are denoted with composite="true" attribute

Nested Multifield

A composite multifield that contains a multifield as one of the items in the fieldset. Example,  a list of users where each user has a name and a list of social media links.

Nested Composite Multified

A composite multified that contains another composite multifield as one of the items in the fieldset. Example, a list companies that contain a list of departments, where each department has a name and manager.

+ companies
   + company1
     - name
     + department1
       - name
       - manager
     + department2
       - name
       - manager
     ...
     + departmentN
   + company2
     - name
     + department1
       - name
       - manager
     ...
     + departmentN
  ...
  +companyN

 

Building a Companies Component

Let’s look at how we can build a component that allows authoring and displaying the companies as described above:
Consider the following dialog XML:

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/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="Companies"
          sling:resourceType="cq/gui/components/authoring/dialog">
  <content jcr:primaryType="nt:unstructured"
           sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns">
    <items jcr:primaryType="nt:unstructured">
      <column jcr:primaryType="nt:unstructured"
              sling:resourceType="granite/ui/components/coral/foundation/container">
        <items jcr:primaryType="nt:unstructured">
          <companies
            jcr:primaryType="nt:unstructured"
            sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
            fieldDescription="Click 'Add Field' to add a new company."
            composite="{Boolean}true">
            <field
              jcr:primaryType="nt:unstructured"
              sling:resourceType="granite/ui/components/coral/foundation/container"
              name="./companies">
              <items jcr:primaryType="nt:unstructured">
                <name
                  jcr:primaryType="nt:unstructured"
                  sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                  fieldLabel="Comp. Name"
                  name="name"/>
                <departments
                  jcr:primaryType="nt:unstructured"
                  sling:resourceType="granite/ui/components/coral/foundation/form/multifield"
                  fieldDescription="Click 'Add Field' to add a new department."
                  fieldLabel="Departments"
                  composite="{Boolean}true">
                  <field
                    jcr:primaryType="nt:unstructured"
                    sling:resourceType="granite/ui/components/coral/foundation/container"
                    name="./departments">
                    <items jcr:primaryType="nt:unstructured">
                      <name
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                        fieldLabel="Dep. Name"
                        name="name"/>
                      <manager
                        jcr:primaryType="nt:unstructured"
                        sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                        fieldLabel="Manager"
                        name="manager"/>
                    </items>
                  </field>
                </departments>
              </items>
            </field>
          </companies>
        </items>
      </column>
    </items>
  </content>
</jcr:root>

When using that dialog, you’ll see something like this:
companies-dialog
When submitting that dialog, it will be stored into the following structure:
companies-node-structure

Let’s get to modelin’

How can we model this structure with sling models? You could write a model where you iterate over the resource tree and create some data structure to make it easier to use in HTL. But that seems like a lot of work, is there really no better way?

Yes! there is!

 
sling models can handle collections so we can write a model like the following:

package com.sample.models;
import java.util.List;
import javax.inject.Inject;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Model;
@Model(
    adaptables = {Resource.class},
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
public interface CompaniesModel {
  @Inject
  List<Company> getCompanies(); // the name `getCompanies` corresponds to the multifield name="./companies"
  /**
   * Company model
   * has a name and a list of departments
   */
  @Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
  interface Company {
    @Inject
    String getName();
    @Inject
    List<Department> getDepartments(); // the name `getDepartments` corresponds to the multifield name="./departments"
  }
  /**
   * Department model
   * has a name and a manager
   */
  @Model(adaptables = Resource.class, defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
  interface Department {
    @Inject
    String getName();
    @Inject
    String getManager();
  }
}

 
I think this is pretty self explanatory, and really easy to reason about.

The Gotchas

  • Make sure the getter method names match the name properties in your dialog xml, see comments in the code.
  • When we do @Inject List<Company> getCompanies(); we are basically telling sling, that under the component resource node, there is a node named companies which has child nodes that should be modeled after (adapted to) Company model.
  • I am using Interface‘s here since there is no need to process any of the user inputs (no business logic), but you can switch that into class‘s, just make sure that if you nest classes they are public or don’t nest them and rather add them to their own class file.

Displaying the companies:

<sly data-sly-use.companiesModel="com.sample.models.CompaniesModel"/>
<sly data-sly-test.empty="${!companiesModel.companies}" />
<div data-sly-test="${wcmmode.edit && empty}" class="cq-placeholder" data-emptytext="${component.title}"></div>
<sly data-sly-test="${!empty}">
  <div>
      <ul data-sly-list.company="${companiesModel.companies}">
        <li>${company.name}
          <ul data-sly-list.department="${company.departments}">
            <li>
              <b>Department:</b> ${department.name} <br/>
              <b>Manager</b>: ${department.manager}
            </li>
          </ul>
        </li>
      </ul>
  </div>
</sly>

 
Which, after being authored, displays the following:
companies-display
Hope this example helps you model complex nested composite multifields.
 
That’s it for now, till the next one!

]]>
https://blogs.perficient.com/2018/08/24/using-sling-models-with-nested-composite-mulitifields-in-aem-6-3/feed/ 8 269338
New From Perficient Digital: Content Reports in AEM https://blogs.perficient.com/2018/01/25/new-perficient-digital-content-reports-aem/ https://blogs.perficient.com/2018/01/25/new-perficient-digital-content-reports-aem/#respond Thu, 25 Jan 2018 16:29:38 +0000 https://blogs.perficient.com/adobe/?p=12851

Last week a new version of ACS AEM Commons, 3.14.0 dropped. This latest release included a new feature for creating configurable reports. These reports allow semi-technical users and administrators to create and execute custom reports and export the results as a Comma-Separated Values (CSV) file.

 

Why do you need Content Reports?

 

Content reports allow authors and content managers to get easy access to critical information such as:

 

  • Which pages are published vs. unpublished
  • What pages have a particular component
  • The status of authoring efforts or workflows
  • List of pages updated by a report process

 

Unfortunately, in AEM OOTB, this cannot be done without developer support. Now, with the ACS AEM Commons Report Builder, all of these reports can be created by any semi-technical author.

 

How do I use ACS AEM Commons Report Builder?

 

The ACS AEM Commons Report Builder is easy and intuitive to use. I wrote up extensive documentation on how to use if you have any questions, but there are essentially three ways to use the tool.

 

#1 – Execute Reports

 

Website users can easily run reports, including providing parameters to configure the results. 

 

 

#2 – Customize Reports

 

Advanced authors and administrators can configure the report columns, parameters, and queries. 

 

#2 – Extend Reports

 

Developers can extend the Report Builder by adding new Report Executors, Column types, and Parameter types.

 

Getting ACS AEM Commons Report Builder

 

Report Builder is available in ACS AEM Commons version 3.14.0+ and is compatible with AEM 6.3+.

 

Download ACS AEM Commons

 

]]>
https://blogs.perficient.com/2018/01/25/new-perficient-digital-content-reports-aem/feed/ 0 269135
AEM Gems: Granite UI Common Attributes https://blogs.perficient.com/2017/12/13/aem-gems-granite-ui-common-attributes/ https://blogs.perficient.com/2017/12/13/aem-gems-granite-ui-common-attributes/#respond Wed, 13 Dec 2017 15:06:11 +0000 https://blogs.perficient.com/adobe/?p=12538

In Adobe Experience Manager (AEM), Granite UI is the foundation UI framework to build touch-enabled UI consoles and component dialogs. It provides a set of out-of-the-box (OOTB) components that you can use to build consoles or component dialogs. In this blog, I want to talk about a small tech gem that a lot of AEM developers are not aware of, Granite UI common attributes. In fact, I firstly encountered them in the AEM WCM core components, then when I was reading the Granite UI migration guide for the dialog conversion from Coral UI 2 to Coral UI 3 (AEM 6.2 to 6.3), I saw that this is listed to streamline the API and replace existing properties.
Note: this was tested on AEM 6.3. All the source code used in this blog can be found in my GitHub project.

Add granite xml namespace

To use Granite UI common attributes, you will add the following xml namespace to your console or dialog content.xml in order for it to build.

xmlns:granite="http://www.adobe.com/jcr/granite/1.0"

Replace HTML global attributes with Granite UI common attributes

In your component development, you may use a lot of class and id attributes to apply styles and/or target your dialog field. Now you should use Granite UI common attributes instead. Below is a list of Granite UI common attributes that I think developer uses a lot in their component dialog:
granite:class
granite:id
granite:title
Note: this is for HTML title attribute. Do not confuse this with jcr:title, which is a JCR property name. This should not replace jcr:title attribute.
granite:hidden
Note: this is for HTML hidden attribute. Do not confuse this with granite:hide, which is another Granite UI attribute that can be used to dynamically render components. (see below section)

Use granite:data to provide data-* attributes for your components

If you are a front-end web developer, you’ve probably known how much you use the HTML5 data-* attribute, either to target specific element, toggle elements, trigger events or to pass data from front end to back end…Similarly, as an AEM developer, you should know that data-* attribute can also be used to pass data for Adobe Analytics, component dialogs…
While you may have been already using data-* attributes in your component dialog by defining a custom unknown attributes, you should replace them with granite:data, in order to follow the best practices.
I have created two examples to demonstrate different use cases of granite:data attribute. They are both in the Icon component in my blog project.
One example of granite:data attribute is that I can use it to pass error message for field validation, continued with the dialog validator from my last blog. In my icon-picker-validation.js, I can simply call the data-error-message attribute that’s populated to the dialog field markup (see below section) and display the message when validator doesn’t pass. This way the error message can be extracted from the logic and set in the field, in case you want to reuse the validator with a different error message for another field.
The second example of granite:data attribute is that I can use it to show and hide dialog fields based on dropdown selection. And this comes OOTB (/libs/cq/gui/components/authoring/dialog/dropdownshowhide/clientlibs/dropdownshowhide/js/dropdownshowhide.js). You can implement this like the example fields below (no more event listeners or long inline extjs widgets api code like what you did in Classic UI).

<showhidedropdown
    granite:class="cq-dialog-dropdown-showhide"
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/form/select"
    fieldLabel="Show hide toggle"
    name="./showhidedropdown">
    <granite:data
        jcr:primaryType="nt:unstructured"
        cq-dialog-dropdown-showhide-target=".text-showhide-target"/>
    <items jcr:primaryType="nt:unstructured">
        <hide
            jcr:primaryType="nt:unstructured"
            text="Hide container"
            value="hide"/>
        <show
            jcr:primaryType="nt:unstructured"
            text="Show container"
            value="show"/>
    </items>
</showhidedropdown>
<extrafieldcontainer
    granite:class="hide text-showhide-target"
    jcr:primaryType="nt:unstructured"
    sling:resourceType="granite/ui/components/coral/foundation/container">
    <granite:data
        jcr:primaryType="nt:unstructured"
        showhidetargetvalue="show"/>
    <items jcr:primaryType="nt:unstructured">
        <text
            jcr:primaryType="nt:unstructured"
            sling:resourceType="granite/ui/components/foundation/form/textfield"
            fieldLabel="Text"
            name="./text"/>
    </items>
</extrafieldcontainer>

As the snippet shown above, the data-cq-dialog-dropdown-showhide-target is the hook, with value set to the class of the container, “.text-showhide-target”. The container also has “.hide” initially. This is working on the container level, however, it doesn’t work on individual field.


There’s more advanced things you can do to show and hide resource dynamically with FilteringResourceWrapper or render condition. FilteringResourceWrapper leverages granite:hide attribute with the wrapper class, while render condition uses granite:rendercondition node with script to define the render condition logic.

Use component helper api to populate Granite UI common attributes at the back end

Most of the time you will not develop a Granite UI component since it’s not an open source project to the public. But in case you are doing custom dialog field or extending the OOTB Granite UI component, like the example I have above, icon picker component, it’s good to know that you can use the component helper api to populate Granite UI common attributes in the jsp and render those attributes.

Tag tag = cmp.consumeTag();
AttrBuilder attrs = tag.getAttrs();
cmp.populateCommonAttrs(attrs);

Conclusion

Granite UI common attributes help define component global attributes and provide the capability to apply and encapsulate Granite specific implementations. It’s recommended by Adobe to use these shared intentions in developing new server components. And more common attributes may be added for future releases of Granite UI. You should be aware of these attributes and start incorporating them into your Touch UI development.

]]>
https://blogs.perficient.com/2017/12/13/aem-gems-granite-ui-common-attributes/feed/ 0 269006
Your Pre-Planning Checklist for an AEM Implementation https://blogs.perficient.com/2017/12/04/migration-to-adobe-experience-manager-planning-checklist/ https://blogs.perficient.com/2017/12/04/migration-to-adobe-experience-manager-planning-checklist/#respond Mon, 04 Dec 2017 16:25:55 +0000 https://blogs.perficient.com/adobe/?p=12394

Making a list and checking it twice… 
‘Tis the season for lists. Our children are making lists, and we too are staying organized with lists – both personally and while at work. Whether its all the people you have to buy presents for in your eight-sibling family, or a slightly larger (okay – much, much larger) undertaking such as re-platforming your CMS, there’s a lot to plan and account for to ensure success.
When it comes to the latter, there’s no doubt that designing, building, and implementing top-notch experiences requires a great deal of planning, strategy, and time. You also need to determine what is the right platform for your business.
As mentioned in the recent Forrester Wave for Web Content Management Systems (Q1 2017), features previously considered advanced, such as separation of content and presentation, what-you-see-is-what-you-get (WYSIWYG) authoring with drag-and-drop tools, and configurable approval processes, are now table stakes. Today’s platforms must include these features while also providing semantic insight, APIs, cloud, and continuous delivery.
Among the 15 web CMS vendors evaluated, Forrester recognized Adobe Experience Manager (AEM) as a leader. AEM falls under the Adobe Marketing Cloud, which is part of the larger Adobe Experience Cloud. It enables your organization to deliver experiences that are compelling, personal, useful, and everywhere.
Of course, this post isn’t about Santa coming to town, but we do have a list of our own to help you with your migration to AEM.

Your Pre-Planning Checklist for an AEM Implementation

And if you’re still in the beginning stages of planning for your migration to Adobe Experience Manager, we have a comprehensive guide on the very topic, available below. If you are looking for a qualified partner to assist, feel free to reach out. Cheers!

]]>
https://blogs.perficient.com/2017/12/04/migration-to-adobe-experience-manager-planning-checklist/feed/ 0 269005
AEM Touch UI Dialog Validation New Best Practice: Use Foundation-Validation https://blogs.perficient.com/2017/11/06/aem-touch-ui-dialog-validation-new-best-practice-use-foundation-validation/ https://blogs.perficient.com/2017/11/06/aem-touch-ui-dialog-validation-new-best-practice-use-foundation-validation/#comments Mon, 06 Nov 2017 14:30:19 +0000 https://blogs.perficient.com/adobe/?p=12210

Oftentimes, AEM developers will be asked to develop a validator for the component dialog. Back in the Classic UI dialog days, you would probably write a JavaScript function for dialog before submitting an event. In Touch UI dialog, if you’ve Googled around, you probably found a lot articles/codes to use jQuery based validator, i.e. $.validator.register({}). Recently, I found out that this jQuery-based validator is deprecated starting in AEM 6.2 (see screenshot below), where the new best practice is to use foundation-validation. In this blog, I am going to walk you through foundation-validation, and the things you can do with it, using a sample icon component. You can find all the source codes in my GitHub project.
The sample icon picker validator mentioned in this blog is tested in AEM 6.3.

When Do You Need to Write a Validator?

A lot of the validation business requirements for dialog I’ve seen are for mandatory fields. If you are using Granite UI components, this, for the most part, is already resolved by setting required="{Boolean}true". But, for instance, if you have a multi-field, or RTE (resourceType of cq/gui/components/authoring/dialog/richtext), or an icon picker from ACS commons, or even your custom-developed dialog field, you may need to write a validator for a mandatory field and/or any other custom validation requirements.
If you are looking for an RTE validator that is easy to customize and scalable, we have a good blog post about it, which also uses foundation-validation.

How to Hook Validator to Dialog Field?

There are two ways you can call the validator for your dialog field:
1. Validation/custom data attribute
Most Granite UI components have a validation attribute that you can use to trigger a custom validator. It adds a data-validation attribute to the component markup; then, in your custom validator, you can use the data-validation attribute and value as a selector to trigger the validation.
If you are not using Granite UI components, you can add any custom data attribute to your dialog field node and use that as a selector for your validator. Of course, you can also use the same “validation” attribute, but just to make sure the component is adding that data – or data-validation attribute to a -foundation-submittable element (refer to “What is –foundation-submittable?” section). If not, you can’t use it directly as a selector; instead, you will need to register a new selector to the foundation registry.
These validations will be triggered when the dialog submit button is clicked. It will stop the dialog submit event first, scan through the dialog to look for fields with a validator, validate those fields and show errors, if any. If all fields are valid, it will proceed with the dialog submit event.

<icon
    jcr:primaryType="nt:unstructured"
    sling:resourceType="acs-commons/components/authoring/graphiciconselect"
    fieldDescription="Icon to display"
    fieldLabel="Icon"
    name="./icon"
    class="icon-picker-base"
    validation="icon-picker">
    <datasource
        jcr:primaryType="nt:unstructured"
        sling:resourceType="acs-commons/components/utilities/genericlist/datasource"
        path="/etc/acs-commons/lists/font-awesome-icons"/>
</icon>

2. Foundation validation API
You can also trigger the dialog validation from foundation validation API. This can be tied to any event on the dialog field, and you can toggle the error UI based on the validity of the field. See example in “Even Better” section.

What is –foundation-submittable?

Basically it’s similar to the applicable elements in the jQuery validator, plus some coral and foundation elements (see the OOTB list screenshot below). Only these submittable elements can be used as a selector and trigger the foundation validation. You can still register a new selector if the data attribute is not in a –foundation-submittable field. For example, the data-validation attribute is added to a <span> element in icon picker.

Register Selector

You can register a custom selector if the dialog component is a composite field or is not adding the data-validation attribute to a –foundation-submittable element, i.e. input, select, button, textarea…
The best practice for validation selector is to use fast flat selector, i.e. “[data-validation=icon-picker]”. Below is the code snippet of how you can register a selector:

registry.register("foundation.validation.selector", {
    submittable: "[data-validation=icon-picker]",
    candidate: "[data-validation=icon-picker]:not([disabled]):not([readonly])",
    exclusion: "[data-validation=icon-picker] *"
});

Where is the Source Code of Foundation Validation?

This is based on AEM 6.2 and 6.3:
/libs/granite/ui/components/foundation/clientlibs/foundation/js/coral/validations.js
/libs/granite/ui/components/coral/foundation/clientlibs/foundation/js/validation/validation.js

Register Adapter

Foundation validation uses .adaptTo() for adapting elements; it’s the same idea as Sling adapter. You can register an adapter for your element and return custom properties/functions. For example, this is useful when I need to set my icon picker field valid/invalid. I can create an adapter for my icon picker and encapsulate my logic there, so I can reuse it in other places.

function createGenericIsInvalid(el) {
    return function() {
        return el.attr("aria-invalid") === "true";
    };
}
function createGenericSetInvalid(el) {
    return function(value) {
        el.attr("aria-invalid", "" + value).toggleClass("is-invalid", value);
    };
}
registry.register("foundation.adapters", {
    type: "foundation-field",
    selector: "[data-validation=icon-picker]",
    adapter: function(el) {
        var field = $(el);
        var button = field.children(".icons-selector");
        var select = field.children("select");
        return {
            isDisabled: function() {
                return select.prop("disabled");
            },
            setDisabled: function(disabled) {
                select.prop("disabled", disabled);
                input.prop("disabled", disabled);
            },
            isInvalid: createGenericIsInvalid(field),
            setInvalid: createGenericSetInvalid(field)
        };
    }
});

Register Validator

Besides selector, there are validate, show, and clear properties. They are all pretty self explanatory. For validate, if it returns any string value, it means it’s invalid; otherwise it’s valid. Below is the snippet of my icon picker validator. I am validating if any icon other than the empty icon is selected from the icon picker, meaning this is a mandatory field.

registry.register("foundation.validation.validator", {
    selector: "[data-validation=icon-picker]",
    validate: function (element) {
        var field,
            value;
        field = $(element);
        value = $(field).find(".selected-icon>i").attr("class");
        if (value == "fip-icon-block") {
            return "Please select the icon";
        } else {
            return;
        }
    },
    show: function(element, message, ctx) {
        $(element).closest(".icon-picker-base").adaptTo("foundation-field").setInvalid(true);
        ctx.next();
    },
    clear: function(element, ctx) {
        $(element).closest(".icon-picker-base").adaptTo("foundation-field").setInvalid(false);
        ctx.next();
    }
});

Even Better

You can use foundation validation API to check validity and/or update U accordingly. For example, I want to check validity whenever someone selects a new icon from icon picker (instead of when they click dialog submit) and I want to toggle the red error triangle and message based on the validity of the select. I can bind the click event from icon selector, trigger a function to call the foundation validation API. I will find the icon picker element and adapt that to “foundation-validation,” and then I can call the foundation validation API.

var validateHandler = function(e) {
    var iconpicker = $(document).find("[data-validation=icon-picker]");
    var api = $(iconpicker).adaptTo("foundation-validation");
    if (api) {
        api.checkValidity();
        api.updateUI();
    }
};
$(document).on("dialog-ready", function () {
    var container = $(this).find("div.fip-icons-container");
    if (container.length > 0) {
        $('.fip-icons-container').on('click', function(){
            setTimeout(validateHandler, 200);
        });
    }
});

Feel free to comment on your specific questions for foundation-validation. I understand that when you have to go through all the documentations and source codes without any working reference can be very challenging, so I hope this blog gives you pointers and examples to lighten up your Touch UI dialog validation implementation.

]]>
https://blogs.perficient.com/2017/11/06/aem-touch-ui-dialog-validation-new-best-practice-use-foundation-validation/feed/ 9 269003
Things to Look Out for When Upgrading to AEM 6.3 https://blogs.perficient.com/2017/10/16/things-to-look-out-for-when-upgrading-to-aem-6-3/ https://blogs.perficient.com/2017/10/16/things-to-look-out-for-when-upgrading-to-aem-6-3/#respond Mon, 16 Oct 2017 14:22:36 +0000 https://blogs.perficient.com/adobe/?p=12127

If you have been using Adobe Experience Manager (AEM) to host your sites for a while, and are now looking to upgrade to AEM 6.3 (whether considering an in-place upgrade or a fresh new install), I am sure you’ve already found some good public documentation from Adobe. (If you are wondering if you should upgrade, here are some compelling reasons from Adobe, as well as my previous blog post.)
Recently, I worked on accessing the AEM 6.2 to 6.3 upgrade along with my colleagues for our client, and I want to share some additional thoughts you may find useful when upgrading to 6.3. Although this blog is for 6.3, the pointers and idea should really be applicable for an AEM upgrade in general.

Plan for The Release

Thorough planning and assessment are critical when upgrading the AEM platform for a large enterprise with a multi-tenant project, like my client. It should involve architects, project manager, dev manager, infrastructure, deployment teams and Adobe professionals to understand the scope, the impact, the approaches, the things that may break after the upgrade and the codes that need to be updated for each tenant, etc. A dedicated dev environment is recommended to set up for the upgrade in order to access and test the environment and codes. Upgrade learning and findings should be well documented and shared with all parties. After planning and assessment, your dev manager should decide if a dedicated technical release is required for the upgrade, assuming tenant teams are continuing new feature development for their products.

POM File Updates

No matter how you created your project structure at first, probably from Adobe’s AEM maven archetype or ACS common’s lazybones AEM multi module project, you should update your POM files to use the latest and newest files, and to get deployed properly in AEM 6.3. Below is a list of dependencies and plugins that I found during my assessment that can be updated:

  1. uber-jar (It’s recommended to place Uber jar at the bottom of the dependency list so that Maven will honor any newer versions of dependencies that is explicitly declared inside the project POM.)
  2. maven-enforcer-plugin to use Java 1.8 (Although Java 1.7 still works in AEM 6.3, it is not supported by Adobe.)
  3. maven-compiler-plugin to use Java 1.8
  4. maven-surefire-plugin
  5. maven-failsafe-plugin
  6. maven-sling-plugin
  7. content-package-maven-plugin
  8. maven-bundle-plugin
  9. maven-dependency-plugin
  10. build-helper-maven-plugin
  11. OSGI dependencies (osgi.core, osgi.cmpn, osgi.annotation) version
  12. slf4j-simple and slf4j-api
  13. junit
  14. servlet-api
  15. apache.sling.models.api (select the version you need since some are introduced in a later version)
  16. commons-lang

You may want to ask to which of the above dependencies and plugins needs to be updated. Great question. Good news, Adobe just released AEM project archetype 12 for 6.3 a couple days ago! AEM project archetype 12 is created to generate a project structure template for AEM 6.3 and above. You can create a new AEM project and reference to the POM files generated from archetype 12 to see exactly what you need to update. That’s what I did for this blog’s purpose and I pushed my code to my Github project.
After the update, run the maven build and make sure the build is successful and bundles are active state. Fix any issues.
Also, a side note: WCM IO has an AEM dependencies tooling that will provide you all maven dependencies (including those that are not defined by Uber Jar) for a specific AEM version. This can provide a safety net for your project in case you miss any dependencies in your POM, but it will not overwrite the ones that you explicitly declare in your POM. Maybe it’s a good idea for Adobe/developer to write an automatic tool for POM updates?

Bundle Status

Even though your build might be successful, sometimes you can still find your application bundles in “resolved” status, most likely due to some of your bundles’ imported packages are outside of the supported range by OOTB AEM. While you are scratching your head to figure out why your application is not working, it’s always a good practice to validate your bundles status is at “active” state.

These Things May Break After the Upgrade

Below is a list of common components/features that break after we deploy our code to AEM 6.3:

  1. RTE and RTE plugins: plugins in dialog are covering up text editing area, RTE box shrinks, inline editing is messed up. (please reference to this link for the fix)
  2. Multifield: can’t load content, add field button doesn’t work. (please reference to this link for the fix)
  3. Classic UI page properties: can’t open (deprecated in April 2018, removed in April 2019)
  4. Page comparison feature in AEM may not work properly with your custom component/CSS/JS

Deprecated APIs

As part of your technical upgrade assessment, you should look into your project codes and see if you are using any deprecated APIs in AEM 6.3.
Full deprecated API list can be referenced here: https://docs.adobe.com/docs/en/aem/6-3/develop/ref/javadoc/deprecated-list.html
API changes between AEM 6.2 and 6.3 can be referenced here: https://docs.adobe.com/docs/en/aem/6-3/develop/ref/diff-previous/changes.html
Deprecated and removed features list can be referenced here: https://docs.adobe.com/docs/en/aem/6-3/release-notes/deprecated-removed-features.html
Be aware that WCMUse has been deprecated since AEM6.1, so for component classes that need updating, it’s recommended to implement Sling Models instead of WCMUsePojo. How? You should reference to my How To Switch From WCMUsePojo To Sling Models blog series.

Upgrade Tasks

To make your AEM upgrade a more seamless project, Adobe’s developed pre-upgrade and post-upgrade tasks that you can run, just follow the instructions from Adobe. We tried to run the pre-upgrade task on AEM6.2, but it had this error below:

*ERROR* [sling-threadpool-3a1e6c9e-47ac-4e73-8445-85babb95cf17-(apache-sling-job-thread-pool)-33-Pre Upgrade Tasks Queue(com/adobe/aem/preupgrade/job/RunAllPreUpgradeTasks)] com.adobe.aem.upgrade.prechecks.tasks.impl.RunAllPreUpgradeTasksImpl RunAllPreUpgradeTasks task failed.

Solution: configure workflow purge configuration and auditlog configuration in configuration manager. (For more detail, see Prakash Venkatesh’s blog post.)

Crx2oak Tool

This tool was firstly developed by Adobe to upgrade the CRX from Apache Jackrabbit 2 base (CRX2) to Apache Jackrabbit Oak base (CRX3). But don’t let the name fool you, it can actually be run as a standalone tool to migrate content from one repository to another, even they are the same version.
This tool is run in command line, either as a new option when you start the 6.3 quickstart jar file, or as a standalone Java program. There are many parameters you can pass in to customize content migration. The latest full list of parameters can be found here. One advantage of using crx2oak to migrate content, versus a content package, is that you can keep the version history data, even orphaned history data.
Sounds great, right? However, this tool is not that easy to use, with limited documentation and help pages. I’ve tried to use the tool (crx2oak-1.6.8-all-in-one.jar) as a standalone and migrate content from 6.3 to 6.3 and from 6.2 to 6.3, and both had different kinds of exceptions (see below). I have seen the same issues reported by different users in Adobe forum, so be mindful of that, look for updates of the tool and consult with Adobe support team when you had difficulty using the tool on AEM 6.3.
I have seen the same issues reported by different users in the Adobe forum, so be mindful of that, and look for updates to the tool and consult with the Adobe support team if you have difficulty using the tool on AEM 6.3.

ERROR  c.a.g.c.c.MigrationSpecGenerator: I/O error during processing migration specification: org.apache.jackrabbit.oak.segment.file.InvalidFileStoreVersionException: Using oak-segment-tar, but oak-segment should be used
ERROR  c.a.g.c.e.MigrationEngine: The internal error occurred during migration. The migration process has been aborted and it is not complete!
java.lang.RuntimeException: javax.jcr.RepositoryException: Failed to copy content

After many trials and errors, I finally found a combination of commands that worked and had a successful run to migrate content from my local AEM 6.2 to AEM 6.3. Thanks to this post from Adobe help and this post in Adobe forum.


This is the command I use, in case you need to reference: (and I place crx2oak-1.6.8-all-in-one.jar and logback.xml in the same directory as AEM 6.3 quickstart jar)

java -Dlogback.configurationFile=logback.xml \
     -Xmx10g \
     -jar crx2oak-1.6.8-all-in-one.jar \
     segment-old:/Users/eddieyao/Projects/perficient/blog/author-aem-6.2/crx-quickstart/repository /Users/eddieyao/Projects/perficient/blog/author-aem-6.3-new/crx-quickstart/repository \
     --include-paths=/content \
     --datastore /Users/eddieyao/Projects/perficient/blog/author-aem-6.3-new/crx-quickstart/repository/datastore \
     --copy-versions=true \
     --copy-orphaned-versions=false

Overall, an AEM upgrade is not an easy task, especially when you have a multi-tenant project, many custom implementations, integrated with many solutions and configured Mongo DB or node and data stores. It requires extensive assessment, strategic planning, thorough testing, strong collaboration and technical implementation. It’s always a good idea to have your AEM experts around, form your team with a trusted Adobe solution partner and consult with Adobe for custom recommendations when doing the upgrade.
 
 
 

]]>
https://blogs.perficient.com/2017/10/16/things-to-look-out-for-when-upgrading-to-aem-6-3/feed/ 0 268948
Features That Will Make You Love AEM 6.3 Workflows https://blogs.perficient.com/2017/09/11/features-that-will-make-you-love-aem-6-3-workflows/ https://blogs.perficient.com/2017/09/11/features-that-will-make-you-love-aem-6-3-workflows/#respond Mon, 11 Sep 2017 15:07:07 +0000 https://blogs.perficient.com/adobe/?p=11903

In earlier releases of Adobe Experience Manager (AEM), there’s no way of knowing whether the editing page is in the workflow process. Authors need to go into workflow inbox to look and see if they have any pages/items assigned as part of the workflow. With the release of AEM 6.3, Adobe has increased productivity with faster workflow-related tasks and the ability to notify authors about the workflow during page editing. Whether the page is added to the workflow through launcher, workflow payload, or start workflow, editing authors will see the workflow notifications on the page (until the workflow completes all the stages).
Once an author opens the page during workflow execution, they may see a blue bar on the page with the following message:

 
This notification indicates this page is under workflow and there are actions that need to be performed by authors (or any other user group) who are part of this workflow participant steps. On the right side of the notification, there will be a number with arrows for navigation if this page is subject to more than one workflow. Authors can navigate using arrows and perform the actions on the selected workflow.
On the notifications, authors may see links that require actions to be completed for workflow. Those actions include:
Complete: Authors can complete an item to allow the workflow to progress to the next step. In a normal workflow process, authors will use this action to go to next step.
On this action you can indicate:

  1. Next Step: the next step to be taken; you can select from a list provided
  2. Comment: if required

Step Back: This step is optional; not all the workflows will have this step. If you discover that a step, or series of steps, needs to be repeated you can step back. This allows you to select a step that occurred earlier in the workflow for reprocessing. The workflow returns to the step you specify, then proceeds from there.
On this action you can indicate:

  1. Previous Step: the step to be returned to; you can select from a list provided
  2. Comment: if required

Delegate: If a step has been assigned to you, but for any reason you are unable to take action, you can delegate the step to another user or group.
On this action you can indicate:

  1. User: the user you want to delegate to; you can select from a list provided
  2. Comment: if required

View Details: View details of the workflow work item and take appropriate actions. The workflow details are shown in tabs and appropriate actions are available in the toolbar. One cool feature of workflow info tab is stages. If workflow stages have been configured for the model, you can view the progress according to these in workflow info tab.
I will demonstrate how to create stages as a workflow modeler and display to the participant users in the workflow. Let’s take a simple OOTB workflow request for activation and add stages.

  1. Edit this workflow from Workflow -> Models -> Select Request for Activation
  2. Click Page Properties from the sidekick
  3. Click Stages tab and add stages Approve Content, Create Version, Waiting for activation, and Activate page using Add item
  4. Click OK
  5. Click on Each participant or process step and set the stage to appropriate value in Common -> Workflow Stage Field 
  6. Save workflow
  7. Log in to sites console
  8. Open any page and Select Start Workflow from Side Kick
  9. Select Request for Activation Workflow and click Start Workflow
  10. You may see a notification with the following message:
  11. Click on View details ->Workflow info tab
  12. You can see different stages of the workflow. The stage you are in is highlighted in blue, and from lines from the start to current stage are displayed as bold. Stages that need to be completed are shown as dotted line.

Are you also loving the new workflow features of AEM 6.3? What feature is your favorite? Drop us a comment below and let us know.

]]>
https://blogs.perficient.com/2017/09/11/features-that-will-make-you-love-aem-6-3-workflows/feed/ 0 268941
Understanding and Trying Out the New AEM Dialog Conversion Tool Version 2 https://blogs.perficient.com/2017/08/14/understanding-and-trying-out-the-new-aem-dialog-conversion-tool-version-2/ https://blogs.perficient.com/2017/08/14/understanding-and-trying-out-the-new-aem-dialog-conversion-tool-version-2/#respond Mon, 14 Aug 2017 12:00:26 +0000 https://blogs.perficient.com/adobe/?p=11666

You may have been on Adobe Experience Manager (AEM) for more than three years, no matter if you are an admin, developer or author, you probably got used to the legacy Classic UI interface and dialogs. Personally, I tend to use the Classic UI site admin console to view/manage content pages, because I like how it easily lets me navigate through the tree and give me a lot of page information in one panel. However, with release of AEM 6.3, Adobe officially announced that Classic UI will be deprecated in April 2018, and completely removed from AEM in April 2019. This means that Adobe will explicitly display the deprecation message in Classic UI in next year’s release, and it will be completely gone from OOTB AEM in 2 years. With that said, it may be sooner than you thought if you don’t put it in your roadmap and take action for architecture, development and trainings, especially if you are still on or heavily rely on Classic UI. Related to this topic, I am going to try out the new dialog conversion tool (version 2) Adobe developed and released in June this year in this blog, and let you know the enhancements, concepts, steps, test result and pros and cons.

(dialog conversion tool v2 user interface)

Version 2 Enhancements

First of all, AEM dialog conversion tool v2.0.0 can convert both Classic UI dialogs and Granite UI/CoralUI 2 dialogs to Granite UI/CoralUI 3 dialogs. If you have not already known, AEM’s touch-enabled UI is built with Adobe’s Granite UI and CoralUI. CoralUI is the front-end implementation (HTML, CSS, JS) of Adobe’s visual style for touch enabled UI. It is developed to provide consistent UX among different Adobe products (like many of Adobe Experience Cloud products). Granite UI components are server side components that are built with CoralUI, CRX, Apache Felix and Apache Sling technologies. They are used in Granite-based platform like AEM. They are also reusable and modular building blocks for most Touch UI dialogs and consoles in AEM. So if you want to write a custom Touch UI console or dialog that has the same look and feel as the OOTB AEM environment, you can either assemble and configure different Granite UI components, or write CoralUI html and define custom action and services.
CoralUI 3 is the latest version of CoralUI that comes with AEM 6.3. If you are on AEM 6.2, it has partially CoralUI 2 and 3, as it was transitioning to 3. AEM 6.1 has CoralUI 2. To check version of CoralUI, see your AEM developing reference materials and Touch UI documentation.
Now you may wonder what is the difference between CoralUI 3 and 2? Answer is not much. Except some platform and component changes, one of the major changes I found is that CoralUI 3 uses web component’s custom elements. This encapsulates the detailed presentation markups and behavior into custom HTML tag, so developers can write coral elements in the HTML file and don’t need to worry about how it works behind the scene. For example, if I need a checkbox in my page, I’ll just write:

<coral-checkbox value="kittens">
CoralUI Rocks
</coral-checkbox>

On CoralUI 2, it uses generic HTML tags with CoralUI classes and some with data attributes. For example, the same checkbox will look like this:

<label class="coral-Checkbox">
  <input class="coral-Checkbox-input" type="checkbox" name="c2" value="2">
  <span class="coral-Checkbox-checkmark"></span>
  <span class="coral-Checkbox-description">Unchecked</span>
</label>

Another noticeable change is that on AEM 6.2, the component Touch UI dialog has dark grey background color, and on AEM 6.3, it’s white.

 
 
 
 
 
 
                      (touch ui dialog in aem 6.3)                                                                          (touch ui dialog in aem 6.2)
For the complete CoralUI 3 enhancements, see the CoralUI 3 documentation.

Compatible AEM versions: 6.3

I have tested and installed the tool on AEM 6.1-6.3 instances. Unfortunately, it only works with 6.3. There are different errors on 6.1 and 6.2. On 6.2, you won’t be able to see the path field to search for dialogs, and didn’t show any dialogs when click on show dialogs button. On 6.1, there’s a package dependency error that requires a higher version of AEM product codes.

                (path field not shown in AEM 6.2)                                               (package dependency issue in AEM 6.1)
 

Process to Use Tool

Use of the tool is simple, go to Global Navigation -> Tools -> Operations -> Dialog Conversion. Put in the path of your component root folder and it will list all dialogs with type classic or coral 2 (Granite UI/CoralUI 3 dialogs will not be listed). Then just select the ones you want to convert and click convert button. If success, it will prompt you to another screen with converted dialog information. If not, it will show you the error message.
After that, you can simply use FileVault (VLT), either command line or the VLT plugin in your IDE, to get the converted dialog from CRX to your code.

Behind The Scene and Custom Rewrite Rules

I understand that your Classic UI dialog, or even Granite UI/CoralUI 2 dialog may have custom dialog fields (or properties). These fields may be extended from OOTB Ext JS widgets, Granite UI components, or custom developed scripts. Custom xtype or sling:resourceType will be processed by the conversion tool as it is unless you have a custom rewrite rule associated with it. For that, I think it’s necessary to know more about the dialog conversion tool behind the scene.
The dialog conversion tool is built on the concept of graph rewriting and rewrite rule, which is a paring of a pattern and a replacement graph.
The tool will take all the selected dialog paths from request parameter and send them to a dialog rewriter class, in which it’s performing the node rewriting algorithm. The basics of the algorithm is that it will traverse all the dialog nodes from top level, all the way to bottom, see if there’s a matching rule for the subtree rooted at each level, then overwrite the node based on matching rule, and start the traversal from top node again; if no match is found, it will keep the original node as it is; traversal will stop until all nodes are final (each node that is processed will be put into a linked hash set, so it will be skipped by the tool in the next traversal).
There are two types of rewrite rules: JCR node based and Java class based.
The dialog conversion tool already provides good amount of OOTB rewrite rules: 3 Java based, 21 JCR node based for Classic UI and 35 JCR node based for Granite UI/CoralUI 2.
Properties of the xtype or sling:resourceType can also be defined in the rewrite rules, otherwise they will be omitted by the tool. The replacement tree can define mapped properties that will inherit the value of a property in the original tree. Here’s an example: /libs/cq/dialogconversion/rules/classic/textfield/replacement/text. If your Classic UI dialog text field has a field label property named “./fieldLabel”, the value you defined for that field in Classic will be copied to the converted Granite UI/CoralUI 3 dialog.
You can copy /libs/cq/dialogconversion/rules to /apps to modify existing and/or add new rules to this new instance. You can also implement com.adobe.cq.dialogconversion.DialogRewriteRule interface or extend com.adobe.cq.dialogconversion.AbstractDialogRewriteRule class.
I will try not to go over complicated with custom rewrite rules. In my opinion, if you spend a lot of time configuring custom rewrite rules or even write a custom rewrite rule class, it’s better to let the tool convert the xtype/sling:resourceType as it is and then spend the time to develop the Granite UI/CoralUI 3 dialog directly, as the tool is supposed to aid your dialog conversion process, it does not and can not take full control of the conversion.

Test Result

I have tested the tool with Classic UI dialog, design dialog and a Granite UI/CoralUI 2 dialog. The result is that all three were successful. The tool is able to convert most of the OOTB xtype and sling:resourceType. I just found that it omits the fields inside a multifield. For custom xtype and sling:resourceType, it kept as it is. The Granite UI/CoralUI 2 dialog node will be appended with “.coral2” in name after the conversion.
I have put the sample component dialogs I used to test and the converted dialogs onto my Blog GitHub project: https://github.com/guangweiyao/blog. Feel free to check them out.

(converted touch ui dialog)

Pros and Cons

Pros:

  1. Automate dialog conversion process, this will be very helpful when you have a lot of components
  2. Able to provide basic shell (if not fully converted) for Touch UI dialog development
  3. Make sure your dialog code align with latest Adobe technology
  4. Reduce Touch UI dialog conversion and development time
  5. Provide flexibility with custom rewrite rule
  6. Tool is straight forward and easy to use
  7. Tool source code is open source and hosted on GitHub

Cons:

  1. Doesn’t work on older versions of AEM
  2. The show link is not able to show Touch UI dialogs
  3. It just tells you whether the process is successful or not, but didn’t provide the granular level information of which field is not able to be converted, or which property is omitted, so developer/admin have a record
  4. To modify existing and/or add new rules, you have to copy and paste the whole /libs/cq/dialogconversion/rules to /apps, it doesn’t support the newer overlay mechanism

Tips and Tricks

  1. If you have both Classic UI and Granite UI/CoralUI 2 dialog for a component, the tool will only display Granite UI/CoralUI 2 dialog
  2. Copy and paste whole /libs/cq/dialogconversion/rules to /apps in order to modify existing and/or add new rules
  3. Sample custom rewrite rules reference to AEM Dialog Conversion tool project
  4. AEM development references is a good place to find all AEM related APIs and documentation

Conclusion

Despite some space for improvement, I think the new AEM Dialog Conversion Tool Version 2 can help you or your client accelerate the component dialog conversion process and align with Adobe’s Classic UI deprecation timeline, so you can plan for paying down your technical debt and building beautiful and touch-enabled UI component dialogs.

]]>
https://blogs.perficient.com/2017/08/14/understanding-and-trying-out-the-new-aem-dialog-conversion-tool-version-2/feed/ 0 257835