Take Control of AEM Action Menus with Render Conditions

June 10, 2020
By Patrick Kent,
Lead Architect

Has this happened to you?

You’re going about your day, working in Adobe Experience Manager (AEM) when a request comes in. Your teammate or client wants five custom user groups, and requires a different complement of authoring options for each of them. Certain menu items and selection actions must only show up for members of designated groups.

screenshot of the AEM menu

Setting RMCDR permissions via /useradmin doesn’t get the whole job done. You need to restrict Quick Publish to an Asset Manager group without causing the Manage Publication action to disappear for non-members. Denying Replicate hides them both. That won’t meet requirements.

screenshot of RMCDR permission Settings

On top of that, you have custom menu actions not tied to RMCDR in any way. They may only display based upon various user, file disposition, and state criteria.

How are you possibly going to be able to complete this request?

Granite Render Conditions 

AEM provides a flexible way to hide or show almost any user interface (UI) element. Render conditions provide this control, and AEM comes with a huge number of them available for your use.

A render condition is a mechanism that decides if a component should be displayed or not. It will drill-down through sub-conditions, evaluating them recursively, and return true or false. The Granite UI Foundation provides a set of built-in render conditions. 

When coding rules of this type, consider the two implementation choices:

  1. Expression properties—placed directly on the granite:rendercondition node.
  2. Stackable references to render condition components—placed underneath the granite:rendercondition node.

Expression properties can only draw from the HTL Global Object context, which does not expose the user’s groups for evaluation. Another gap is the challenge in building up equivalent nested sub-conditions inside EL statements. To address the stated business problem, this post will focus on the second approach.

One of the built-in render conditions appears to do just what we need. It is a component that validates against a group property, and is located at: /libs/fd/fm/gui/components/admin/renderconditions/groups

Step One: Create an Overlay of the Action

In /libs, locate the action that will receive display rules, and overlay it in /apps.

Let’s change the display behavior for the Quick Publish selection action. This controls when Quick Publish will show up after a user has selected one or more DAM assets or pages. It already has a couple of render conditions. We will add a third, restricting display to members of the asset-admin group.

Here is our overlay:

screenshot of Bounteous overlay in AEM

Step Two: Add a Render Condition

Under granite:rendercondition, add:

+ and
  - sling:resourceType = "granite/ui/components/coral/foundation/renderconditions/and"
  + isInGroup
    - sling:resourceType = "/libs/fd/fm/gui/components/admin/renderconditions/groups"
    - group = "asset-admin"

Try it out, and you’ll see that Quick Publish only shows up if you are a member of the asset-admin group.

Step Three: Overlay the Group Render Condition

Next, we’ll want to make another change. This render condition component is located in the /admin section, and won’t run unless you are an admin. We want to move it out of there.

Overlay it in your project folder, and change the sling:resourceType to match. For example: "/apps/<yourproject>/components/renderconditions/groups"

Step Four: Improve the Group Matching Logic

Now our menu element will only show up for members of a single group. But the OOB component does not recognize an array of groups. Wouldn’t that be better? Then it would be more broadly re-usable.

Edit groups.jsp, and change it to:

<%@page session="false"
          import="com.adobe.granite.ui.components.Config,
                  com.adobe.granite.ui.components.rendercondition.RenderCondition,
                  com.adobe.granite.ui.components.rendercondition.SimpleRenderCondition,
                  org.apache.jackrabbit.api.security.user.UserManager,
                  com.adobe.aem.formsndocuments.util.FMUtils" %><%

Config cfg = cmp.getConfig();
UserManager um = resourceResolver.adaptTo(UserManager.class);
boolean isAllowed = false;

String[] groups = cfg.get("groups", String[].class);

for (String group : groups) {
    if( FMUtils.isUserPartOfGroup(request.getUserPrincipal(), um, group) ) {
        isAllowed = true;
        break;
    }
}

request.setAttribute(RenderCondition.class.getName(), new SimpleRenderCondition(isAllowed));
%>

Rename the ‘group’ property to ‘groups’, convert it to String[], and add more groups.

  + isInGroup
    - sling:resourceType = "/apps/myproject/components/renderconditions/groups"
    - groups = "asset-admin,asset-manager"

After this refactor, the component will iterate through all values in the groups array, returning true if any match. It can fulfill all our group matching directives.

Step Five: More Complex Requirements

Now that our teammate and/or client know what we can do, they’re getting more demanding. They’ve added the requirement that regular authors should not be able to request publication of videos. Only video authors should be allowed to do this. But managers and admins are allowed to publish anything. That would mean restricting the Manage Publication contextual action based upon multiple criteria. Here’s how we can model the behavior.

After searching through the JCR for applicable components, we stumble across this one:
/libs/dam/gui/coral/components/commons/renderconditions/videoasset.

It should do nicely. Let’s combine it with our new groups component and leverage the not ‘operator.’ This one of six base render condition components. See the Granite UI Foundation docs.

+ managepublication
  - sling:resourceType = "granite/ui/components/coral/foundation/collection/action"
  + data
    - href.uritemplate = "/mnt/overlay/dam/gui/content/commons/managepublicationwizard.html{?item*}"
  + granite:rendercondition
    - sling:resourceType = "granite/ui/components/coral/foundation/renderconditions/or"
    + condition1
      - sling:resourceType = "granite/ui/components/coral/foundation/renderconditions/and"
      + isInGroup
        - sling:resourceType = "/apps/myproject/components/renderconditions/groups"
        - groups = "asset-video-author,asset-manager,asset-admin"
      + isVideo
        - sling:resourceType = "/libs/dam/gui/coral/components/commons/renderconditions/videoasset"
    + condition2
      - sling:resourceType = "granite/ui/components/coral/foundation/renderconditions/and"
      + isInGroup
        - sling:resourceType = "/apps/myproject/components/renderconditions/groups"
        - groups = "asset-author,asset-manager,asset-admin"
      + not
        - sling:resourceType = "granite/ui/components/coral/foundation/renderconditions/not"
        + isVideo
          - sling:resourceType = "/libs/dam/gui/coral/components/commons/renderconditions/videoasset"

Notice how in some cases we can reference OOB render condition components from /libs without doing an overlay?

Tip: When customizing DAM actions behavior, you can freely reference everything in /libs/dam/gui/coral/components/commons.

Step Six: Sweep Away the Clutter

The DAM menu is a little too busy for some user types, wouldn’t you say?

For example—why should users who only have read on /content/dam be able to see the Copy contextual menu option? Where can they copy to? This action is enabled by default for all users in AEM 6.5. No longer.

+ copyasset
  - sling:resourceType = "granite/ui/components/coral/foundation/collection/action"
  + granite:rendercondition
    - sling:resourceType = "granite/ui/components/coral/foundation/renderconditions/and"
    + mainasset
      - sling:resourceType = "dam/gui/coral/components/commons/renderconditions/mainasset"
    + not
      - sling:resourceType = "granite/ui/components/coral/foundation/renderconditions/not" 
      + isInGroup
        - sling:resourceType = "/apps/myproject/components/renderconditions/groups"
        - groups = "asset-viewer"

Also, why do read-only users see a Manage Tags option? Tags are already viewable via Properties. I know they can’t actually save the tags, but come on. Read-Only can’t ‘manage’ anything. Hasta la vista, Baby.

+ managetags
  - sling:resourceType = "granite/ui/components/coral/foundation/collection/action"
  + granite:rendercondition
    - sling:resourceType = "granite/ui/components/coral/foundation/renderconditions/and"
    + data
      - href.uritemplate = "/aem/managetags.html{+item}"
    + not
      - sling:resourceType = "granite/ui/components/coral/foundation/renderconditions/not" 
      + isInGroup
        - sling:resourceType = "/apps/myproject/components/renderconditions/groups"
        - groups = "asset-viewer"

AEM’s OOB Render Conditions

And there you have it! Now, you can create custom user groups with specific authoring options for each—hiding or showing menu items and selection actions for each of the designated groups as needed.

Below you will find a list of all the render conditions that ship with AEM 6.5. Many of these can be referenced from libs. Others can serve as inspiration for overlays. The sky is really the limit here. Adobe has given us a great head start and a large library to draw from.


screens/dcc/components/renderconditions/hasProperty
cq/workflow/admin/console/components/rendercondition/overriddenmodel
cq/workflow/admin/console/components/rendercondition/emptyproperty
cq/personalization/touch-ui/components/renderconditions/localoractivetargetactivity
cq/personalization/touch-ui/components/renderconditions/activetargetactivity
cq/personalization/touch-ui/components/renderconditions/nondeactivatedactivity
cq/personalization/touch-ui/components/renderconditions/inactivetargetactivity
cq/personalization/touch-ui/components/renderconditions/hastargetconfig
cq/personalization/touch-ui/components/renderconditions/cancreateaudience
cq/personalization/touch-ui/components/renderconditions/targetactivity
cq/gui/components/renderconditions/canwriteworkflow
cq/gui/components/renderconditions/notfound
cq/gui/components/renderconditions/policy
cq/gui/components/renderconditions/canmodify
cq/gui/components/renderconditions/enablefragmentidentifier
cq/gui/components/renderconditions/isinapps
cq/gui/components/renderconditions/islocked
cq/gui/components/renderconditions/islaunchresource
cq/gui/components/renderconditions/canreplicate
cq/gui/components/renderconditions/ismobileresource
cq/gui/components/renderconditions/hasanalytics
cq/gui/components/renderconditions/canreadworkflowmodels
cq/gui/components/workflow/editor/rendercondition/workflow
cq/gui/components/projects/admin/renderconditions/noMasterSpecified
cq/gui/components/projects/admin/renderconditions/parentInMasterFolder
cq/gui/components/projects/admin/renderconditions/taskwriteaccess
cq/gui/components/projects/admin/renderconditions/withoutteamreference
cq/gui/components/projects/admin/renderconditions/workflowinproject
cq/gui/components/projects/admin/translation/renderconditions/addtranslationobjectaccess
cq/gui/components/siteadmin/admin/properties/renderconditions/canedit
cq/gui/components/coral/common/admin/renderconditions/mediaportal-config
cq/gui/components/authoring/editors/rendercondition/template
cq/gui/components/authoring/editors/rendercondition/page
cq/gui/components/authoring/pageinfo/renderconditions/replicate
cq/translation/cloudservices/rendercondition/isProjectAdmin
cq/translation/cloudservices/rendercondition/isWorkflowUser
cq/inbox/gui/components/inbox/itemdetails/tabs/rendercondition/isworkitem
cq/inbox/gui/components/inbox/itemdetails/tabs/rendercondition/istask
cq/inbox/gui/components/inbox/itemdetails/tabs/rendercondition/hasprojectinfo
cq/inbox/gui/components/inbox/itemdetails/tabs/rendercondition/isfailureitem
cq/inbox/gui/components/inbox/itemdetails/tabs/rendercondition/hasworkflowinfo
fd/af/dor/rendercondition/tab
fd/af/authoring/editors/rendercondition/theme
fd/af/authoring/editors/rendercondition/form
fd/af/authoring/editors/rendercondition/template
fd/fm/gui/components/admin/renderconditions/groups
commerce/gui/components/admin/collections/renderconditions/productbasedcollection
commerce/gui/components/admin/collections/renderconditions/collectionbasedcollection
commerce/gui/components/admin/collections/renderconditions/querybasedcollection
social/console/components/renderconditions/createtenant
social/console/components/renderconditions/createGroupBtnRenderCondition
social/console/components/renderconditions/createsite
dam/components/configurations/dm/youtube/edit/channellist/renderconditions/channels
dam/cfm/admin/components/renderconditions/associatedcontent
dam/cfm/admin/components/renderconditions/fragment
dam/cfm/admin/components/renderconditions/contentfragment
dam/cfm/components/renderconditions/hasfragment
dam/cfm/models/console/components/renderconditions/displayInNav
dam/gui/components/s7dam/sets/datasources/singleassetdatasource/renderconditions/asset
dam/gui/components/s7dam/common/rendercondition/dm
dam/gui/components/s7dam/common/rendercondition/dms7
dam/gui/components/s7dam/dmrenderconditions/dmasset
dam/gui/components/s7dam/dmrenderconditions/assettyperendercondition
dam/gui/components/s7dam/dmrenderconditions/remoteasset
dam/gui/components/s7dam/dmrenderconditions/s7assetready
dam/gui/components/s7dam/dmrenderconditions/canwrite
dam/gui/components/admin/renderconditions/cancheckout
dam/gui/components/admin/renderconditions/isparentassetcheckedout
dam/gui/components/admin/renderconditions/cancheckin
dam/gui/components/admin/renderconditions/isviewablebycurrentuserandhasnotexpired
dam/gui/components/admin/renderconditions/fragmentsenabled
dam/gui/components/admin/renderconditions/islivecopysource
dam/gui/components/admin/renderconditions/scene7
dam/gui/components/admin/renderconditions/ischeckedout
dam/gui/components/admin/renderconditions/dynamicmedia
dam/gui/components/admin/renderconditions/contentfragment
dam/gui/components/admin/renderconditions/isparentassetcheckedoutbycurrentuser
dam/gui/components/admin/renderconditions/checkedout
dam/gui/components/admin/renderconditions/iscurrentuseradmin
dam/gui/components/admin/renderconditions/ischeckedoutbycurrentuser
dam/gui/coral/components/commons/renderconditions/propertyValue
dam/gui/coral/components/commons/renderconditions/isimage
dam/gui/coral/components/commons/renderconditions/mainasset
dam/gui/coral/components/commons/renderconditions/stockaccessible
dam/gui/coral/components/commons/renderconditions/haspages
dam/gui/coral/components/commons/renderconditions/isdiskusagereport
dam/gui/coral/components/commons/renderconditions/stockassetlicensed
dam/gui/coral/components/commons/renderconditions/propertypagepermission
dam/gui/coral/components/commons/renderconditions/hasreviewstatus
dam/gui/coral/components/commons/renderconditions/videoasset
dam/gui/coral/components/commons/renderconditions/isnotlinksharereport
dam/gui/coral/components/commons/renderconditions/onpage
dam/gui/coral/components/commons/renderconditions/canextract
dam/gui/coral/components/commons/renderconditions/isfolder
dam/gui/coral/components/commons/renderconditions/haspermissions
dam/gui/coral/components/commons/renderconditions/removeprivaterendercondition
dam/gui/coral/components/commons/renderconditions/isassetexpired
dam/gui/coral/components/commons/renderconditions/istextasset
dam/gui/coral/components/commons/renderconditions/stockasseteditorial
dam/gui/coral/components/commons/renderconditions/hasannotations
dam/gui/coral/components/commons/renderconditions/singleitem
dam/gui/coral/components/commons/renderconditions/hassubassets
dam/gui/coral/components/commons/renderconditions/stockasset
dam/gui/coral/components/commons/ui/shell/datasources/assetsdatasource/renderconditions/directory
dam/gui/coral/components/commons/ui/shell/datasources/assetsdatasource/renderconditions/asset
dam/gui/coral/components/admin/renderconditions/userprop
dam/gui/coral/components/admin/renderconditions/macshare
dam/gui/coral/components/admin/renderconditions/locked
dam/gui/coral/components/admin/renderconditions/sync-config
dam/gui/coral/components/admin/foldershare/renderconditions/hasmetadataschema
dam/gui/coral/components/admin/foldershare/renderconditions/foldermetadataschema
dam/gui/coral/components/admin/collections/collectiondatasource/renderconditions/collection
dam/gui/coral/components/admin/stock/renderconditions/stockassetlicensed
wcm/msm/components/touch-ui/renderconditions/isblueprint
wcm/msm/components/touch-ui/renderconditions/islivecopy
wcm/msm/gui/components/renderconditions/capability
wcm/designimporter/components/touch-ui/renderconditions/isimporterpage
wcm/designimporter/components/touch-ui/renderconditions/hascanvas
granite/ui/components/foundation/renderconditions/feature
granite/ui/components/foundation/renderconditions/or
granite/ui/components/foundation/renderconditions/privilege
granite/ui/components/foundation/renderconditions/simple
granite/ui/components/foundation/renderconditions/not
granite/ui/components/foundation/renderconditions/and
granite/ui/components/coral/foundation/renderconditions/feature
granite/ui/components/coral/foundation/renderconditions/or
granite/ui/components/coral/foundation/renderconditions/privilege
granite/ui/components/coral/foundation/renderconditions/simple
granite/ui/components/coral/foundation/renderconditions/not
granite/ui/components/coral/foundation/renderconditions/and
granite/oauth/components/renderconditions/revocationactive