Select All and De-Select All fields in Dropdown using Coral UI JavaScript in AEM

In this article we will try out a custom Coral UI Javascript in AEM and all a functionality to a dropdown where, dropdown will have n number of values and will have Select All and De-select All option as well in the dropdown. This is really helpful when you have lot’s of options in a dropdown and you have to select all and de-select all.

First create a simple AEM dropdown field with multiselect functionality and add some 5-6 options in the dropdown and then add two option with following text and value combination which will help us select all fields in dropdown as well as de-select all fields in dropdown.


NOTE: The name property used in this article is ./country for the dropdown.



Select All option:

text: Select All (You can keep any name as it just shows on the UI).
value: all (We are keeping all here because we will be using the same value in JS)

Deselect All option:

text: De-Select All (You can keep any name as it just shows on the UI).
value: none (We are keeping none here because we will be using the same value in JS)

Now create a clientlibrary in AEM with category name as cq.myproject.select-deselect. And then attach this clientlib at the cq:dialog node of the component by adding the following property at cq:dialog node.

extraClientlibs – String – cq.myproject.select-deselect

Once we are done setting up everything then paste the following code in your JS file of the clientlibs and checkout the functionality in the dropdown.

(function (document, $){
"use strict";

$(document).on("foundation-field-change", "coral-select[name='./country']", function (e) {
let postSelect = $("coral-tag[aria-selected='true']").val();
let allDropdownselect=($(this).Find("coral-selectlist-item(value='all']")).is(':selected');
let nodropdownSelect = ($(this).find("coral-selectlist-item[value='none']")).is(':selected');
        if (!!allDropdownselect  && !postSelect) {
            $(this) .find("coral-selectlist-item[value! ='none']").attr("selected", true);
            $(this). find("coral-tag[value='all']"). remove();
        }
        if (!!nodropdownSelect  && !postSelect) {
            $(this).find("coral-selectlist-item[value!='none']").attr("selected", false);
            $(this).find("coral-tag[value='none' ]") .remove();
        }
    });
}) (document, Granite.$);



Also if you want to customise the above Coral UI JS as per your name property and dropdown options then you need to update the following props.

Field NamePurpose
./countryUsed as the name property of the dropdown field
allUsed as the value of Select All dropdown field
noneUsed as the value of De-Select All dropdown field

Extending Core Component’s Model and Creating it’s new Implementation | AEM

The Core Components implement several patterns that allow easy customization, from simple styling to advanced functionality reuse.

The Core Components were designed from the beginning to be flexible and extensible. A look at an overview of their architecture reveals where customizations can be made.

Now let’s see how we can customise our AEM Core components business logic. We will try to customise the OOTB image core component. And the customisation that we are going to do is that, if image alt text is not authored, we will pick up the image alt text from the image name.

@Model(adaptables = SlingHttpServletRequest.class,
       adapters = Image.class,
       resourceType = "myproject/components/image")
public class ExtendedImageComponent implements Image {
   

   String alt;

   @ValueMapValue
   String altText;

   @ValueMapValue
   String fileReference;

    @Self @Via(type = ResourceSuperType.class)
    private Image image;

    @Override 
    public String getAlt() {
        return alt;

    }

    public Image getImage(){
        return image;
    }

    @PostConstruct
    protected void init(){

        if(null == altText && null!= fileReference){
            String [] imgPathArray = fileReference.split("/");
            alt = imgPathArray[imgPathArray-1];
        }

    }
  
}

In above java class we have used the adapter for Image.class and implemented the OOTB functionality for Image Component and using @Self annotation we have created a object of Image class so that we will go ahead and update the image.{{variable}} with model.image.{{variable}} to load the untouched functionality of the OOTB java class. whereas the customized altText here can be referred by model.alt.

No we can go ahead and replace the data-sly-use.image=”com.adobe.cq.wcm.core.components.models.Image” with data-sly-use.model=”com.myproject.core.components.models.ImageComponent”.

Now we can replace the custome altText which was image.alt to model.alt and rest of the image variable with model.image.{{variable}}.


Thank you for reading.
Happy Coding!

Create and Use Service User using ACL Scripts | AEMaaCs

In this article we will learn how to create and use Service Users in your AEM code to provide controlled, programmatic access to the AEM repository.

As we all know we used to create the system users using the /crx/explorer and used to provide the required permissions from /useradmin dashboard. But now as we are moving towards AEMaaCs and the recommended way is to push the system users and it’s permissions through code. So let’s see how we can achieve that.

So let’s go ahead and update the OSGI config which comes OOTB as part of recent archetypes i.e, org.apache.sling.jcr.repoinit.RepositoryInitializer-myproject-statistics.config which can be found in you project structure at /ui.config/src/main/content/jcr_root/apps/myproject/osgiconfig.


1. Service User – permission to single path

scripts=["
    create service user myprojectUserService
 
 
    set ACL on /content/dam/myproject
         allow jcr:all for myprojectUserService
    end
"]

2. Service User – permission to multiple paths

scripts=["
    create service user myprojectUserService
 
 
    set ACL on /content/dam/myproject,/conf/myproject
         allow jcr:all for myprojectUserService
    end
"]

We can give different permissions as per our requirement, here I have given jcr:all for the above paths but if you want to give only read/write permissions you can do something like below:

3. Service User – permission to single path with only read permission

scripts=["
    create service user myprojectUserService
 
 
    set ACL on /content/dam/myproject
         allow jcr:read for myprojectUserService
    end
"]

4. Service User – permission to multiple paths with only write permission

scripts=["
    create service user myprojectUserService
 
 
    set ACL on /content/dam/myproject
         allow jcr:write for myprojectUserService
    end
"]

The recommended way is to create the service user under system/cq:services/<my-project> when using the principle ACL. You do it my using the following script:

scripts=["
    create service user myprojectUserService with forced path system/cq:services/my-project

    
    set principal ACL for myprojectUserService
        allow jcr:read on /content/dam/myproject
    end
"]

So once we are done creating the service user we will map it to a subServiceName which will be used in our java code to get the proper resolver to access the AEM content.

We can create our OSGI config i.e, org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended-myproject.cfg.json

{
  "user.mapping": [
    "myproject.core:myprojectUserService=[myprojectUserService]"
  ]
}

So once we are done creating the above configurations we can utilise the above service user to get our resource resolver which will help us access the AEM content.

final Map<String, Object> authInfo = Collections.singletonMap(
                ResourceResolverFactory.SUBSERVICE,
                "myprojectUserService");

    
        try (ResourceResolver serviceResolver = resourceResolverFactory.getServiceResourceResolver(authInfo)) {
            // Do some work with the service user's resource resolver and underlying resources.
        } catch (LoginException e) {
            log.error("Login Exception when obtaining a User for the Bundle Service: {} ", e);
        }


Thank you for reading.
Happy Coding!

Automating/Performing tasks on replication of a page from AEM Author

Today in this article we have a look on how we can basically automate/perform any task/tasks on the publication/replication of a page from our author instance. We are going to make use of Event Handler here to achieve this goal.

So we just need to implement the interface EventHandler in our Java class by adding below import statement:

import org.osgi.service.event.EventHandler;


So your code after implementing the interface will look something like below:

package com.kpmg.core.utils;

import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.osgi.service.component.annotations.*;
import com.day.cq.replication.ReplicationAction;

/**
 * @author Nikhil Kumar
 *
 *         Event Handler that listens to the Sling events
 */
@Component(immediate = true, service = EventHandler.class, property = {
		Constants.SERVICE_DESCRIPTION + "= This event handler listens the events on page activation",
		EventConstants.EVENT_TOPIC + "=" + ReplicationAction.EVENT_TOPIC})
public class OnPublishEvent implements EventHandler {

	private static final Logger log = LoggerFactory.getLogger(OnPublishEvent.class);


	@Reference
	private ResourceResolverFactory resourceResolverFactory;

	@Override
	public void handleEvent(Event event) {

		final ReplicationAction action = ReplicationAction.fromEvent(event);

		if (action != null) {
			log.info("Event Added triggered from: {}", action.getPath());
			
			/*
			
			YOUR AUTOMATED TASKS GOES HERE, WHICH WILL BE TRIGGERED EVERYTIME A PAGE IS PUBLISHED.
			
			*/	
		}

	}

}

Now here in above code if you see, we are using EVENT TOPIC to be of type com/day/cq/replication. Which means we want this Java class to be called when any replication happens from author instance.

As we can see in above Java class, we need to override the handleEvent function to capture the event which has occurred and from that event we can get different information as per our need. For example, in above class we have used it to get the action which happened on the event. And from that action we are getting the path on which the action (i.e, replication/publish) happened. Which will give us the path of the page which was replicated/published.

Now you can use it many use cases where you have to make API calls on publish of a page. For instance, you want to index the data of a published page on some 3rd party search tool, then you can make API calls using Event Handler on page publication.

Thank you for reading.
Happy Coding!


Maxlength custom validator for Richtext using Granite UI Jquery | AEMaaCs

As we all know that when it comes to apply the maxlegth property to a richtext field in AEM, it doesn’t actually work. Because RTE in dialog uses the concept of validation from the Form Validation in Granite UI. And, since, Inline-editing does not behave as a form, it can’t use that validation concept.

So we will make use of Granite UI api to create our custom validator which will help us to add validation of maxlength even on the richtext field in AEM TouchUI.

  1. Create a validator.js in clientlibs with following Jquery: Here we are targeting the richtext-container and inside that container we are targeting a class that we will provide to the richtext field i.e rich-custom using Granite UI Api.

;(function (window, $) {
    'use strict';
 
    var RichTextMaxLengthValidation= function () {
        var CONST = {
            TARGET_GRANITE_UI: '.coral-RichText-editable',
            ERROR_MESSAGE: 'Your text length is {0} but character limit is {1}!',
        };

        function init() {
            // register the validator which includes the validate algorithm
            $(window).adaptTo('foundation-registry').register('foundation.validation.validator', {
                selector: CONST.TARGET_GRANITE_UI,
                validate: function (el) {
                    var $rteField = $(el);
                    var $field = $rteField.closest('.richtext-container').find('input.rich-custom');
                    var maxLength = $field.data('maxlength');
                    var textLength = $rteField.text().trim().length;
                    if (maxLength && textLength > maxLength) {
                        return Granite.I18n.get(CONST.ERROR_MESSAGE, [textLength, maxLength]);
                    }
                    return null;
                }
            });
            // execute Jquery Validation onKeyUp
            $(document).on('keyup', CONST.TARGET_GRANITE_UI, function (e) {
                executeJqueryValidation($(this));
            });
        }

        function executeJqueryValidation(el) {
            var validationApi = el.adaptTo('foundation-validation');
            if (validationApi) {
                validationApi.checkValidity();
                validationApi.updateUI();
            }
        }
       return {
            init: init
        }
    }();
    RichTextMaxLengthValidation.init();
})(window, Granite.$);

2. Now add this validator.js in your js.txt and give the category of your clientlibs as cq.author.maxvalidator

#base=js
validator.js

3. Now once we have our validator.js ready now we will create our AEM dialog which will have a richtext field and we will give two properties to this richtext field in dialog.

  • maxlength – {String} – 60 //This is the maxlength of the richtext text.
  • class – {String} – rich-custom //This is the custom class that is used in validator to target the richtext field.

Also we need to add the extraClientlibs property on the cq:dialog to load the custom validator that we created in Step 1.

Following the dialog.xml of one my component where this custom validator is used to restrict the character count based on maxlength.

<?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="nt:unstructured"
    jcr:title="Richtext Maxlength"
    sling:resourceType="cq/gui/components/authoring/dialog"
    extraClientlibs="[cq.author.maxvalidator]">
    <content
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/container">
        <items jcr:primaryType="nt:unstructured">
            <tabs
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/tabs"
                maximized="{Boolean}true">
                <items jcr:primaryType="nt:unstructured">
                    <properties
                        jcr:primaryType="nt:unstructured"
                        jcr:title="Properties"
                        sling:resourceType="granite/ui/components/coral/foundation/container"
                        margin="{Boolean}true">
                        <items jcr:primaryType="nt:unstructured">
                            <columns
                                jcr:primaryType="nt:unstructured"
                                sling:resourceType="granite/ui/components/coral/foundation/fixedcolumns"
                                margin="{Boolean}true">
                                <items jcr:primaryType="nt:unstructured">
                                    <column
                                        jcr:primaryType="nt:unstructured"
                                        sling:resourceType="granite/ui/components/coral/foundation/container">
                                        <items jcr:primaryType="nt:unstructured">
                                            <caption
                                                jcr:primaryType="nt:unstructured"
                                                sling:resourceType="cq/gui/components/authoring/dialog/richtext"
                                                class="rich-custom"
                                                fieldDescription="A description to display as the subheadline for the teaser."
                                                fieldLabel="Caption"
                                                maxlength="60"
                                                name="./caption"
                                                removeSingleParagraphContainer="{Boolean}true"
                                                useFixedInlineToolbar="{Boolean}true">
                                                <rtePlugins jcr:primaryType="nt:unstructured">
                                                    <format
                                                        jcr:primaryType="nt:unstructured"
                                                        features="bold,italic"/>
                                                    <justify
                                                        jcr:primaryType="nt:unstructured"
                                                        features="-"/>
                                                    <links
                                                        jcr:primaryType="nt:unstructured"
                                                        features="modifylink,unlink"/>
                                                    <lists
                                                        jcr:primaryType="nt:unstructured"
                                                        features="*"/>
                                                    <misctools jcr:primaryType="nt:unstructured">
                                                        <specialCharsConfig jcr:primaryType="nt:unstructured">
                                                            <chars jcr:primaryType="nt:unstructured">
                                                                <default_copyright
                                                                    jcr:primaryType="nt:unstructured"
                                                                    entity="&amp;copy;"
                                                                    name="copyright"/>
                                                                <default_euro
                                                                    jcr:primaryType="nt:unstructured"
                                                                    entity="&amp;euro;"
                                                                    name="euro"/>
                                                                <default_registered
                                                                    jcr:primaryType="nt:unstructured"
                                                                    entity="&amp;reg;"
                                                                    name="registered"/>
                                                                <default_trademark
                                                                    jcr:primaryType="nt:unstructured"
                                                                    entity="&amp;trade;"
                                                                    name="trademark"/>
                                                            </chars>
                                                        </specialCharsConfig>
                                                    </misctools>
                                                    <paraformat
                                                        jcr:primaryType="nt:unstructured"
                                                        features="*">
                                                        <formats jcr:primaryType="nt:unstructured">
                                                            <default_p
                                                                jcr:primaryType="nt:unstructured"
                                                                description="Paragraph"
                                                                tag="p"/>
                                                            <default_h1
                                                                jcr:primaryType="nt:unstructured"
                                                                description="Heading 1"
                                                                tag="h1"/>
                                                            <default_h2
                                                                jcr:primaryType="nt:unstructured"
                                                                description="Heading 2"
                                                                tag="h2"/>
                                                            <default_h3
                                                                jcr:primaryType="nt:unstructured"
                                                                description="Heading 3"
                                                                tag="h3"/>
                                                            <default_h4
                                                                jcr:primaryType="nt:unstructured"
                                                                description="Heading 4"
                                                                tag="h4"/>
                                                            <default_h5
                                                                jcr:primaryType="nt:unstructured"
                                                                description="Heading 5"
                                                                tag="h5"/>
                                                            <default_h6
                                                                jcr:primaryType="nt:unstructured"
                                                                description="Heading 6"
                                                                tag="h6"/>
                                                            <default_blockquote
                                                                jcr:primaryType="nt:unstructured"
                                                                description="Quote"
                                                                tag="blockquote"/>
                                                            <default_pre
                                                                jcr:primaryType="nt:unstructured"
                                                                description="Preformatted"
                                                                tag="pre"/>
                                                        </formats>
                                                    </paraformat>
                                                    <table
                                                        jcr:primaryType="nt:unstructured"
                                                        features="-">
                                                        <hiddenHeaderConfig
                                                            jcr:primaryType="nt:unstructured"
                                                            hiddenHeaderClassName="cq-wcm-foundation-aria-visuallyhidden"
                                                            hiddenHeaderEditingCSS="cq-RichText-hiddenHeader--editing"/>
                                                    </table>
                                                    <tracklinks
                                                        jcr:primaryType="nt:unstructured"
                                                        features="*"/>
                                                </rtePlugins>
                                                <uiSettings jcr:primaryType="nt:unstructured">
                                                    <cui jcr:primaryType="nt:unstructured">
                                                        <inline
                                                            jcr:primaryType="nt:unstructured"
                                                            toolbar="[format#bold,format#italic,format#underline,#justify,#lists,links#modifylink,links#unlink,#paraformat]">
                                                            <popovers jcr:primaryType="nt:unstructured">
                                                                <justify
                                                                    jcr:primaryType="nt:unstructured"
                                                                    items="[justify#justifyleft,justify#justifycenter,justify#justifyright]"
                                                                    ref="justify"/>
                                                                <lists
                                                                    jcr:primaryType="nt:unstructured"
                                                                    items="[lists#unordered,lists#ordered,lists#outdent,lists#indent]"
                                                                    ref="lists"/>
                                                                <paraformat
                                                                    jcr:primaryType="nt:unstructured"
                                                                    items="paraformat:getFormats:paraformat-pulldown"
                                                                    ref="paraformat"/>
                                                            </popovers>
                                                        </inline>
                                                        <dialogFullScreen
                                                            jcr:primaryType="nt:unstructured"
                                                            toolbar="[format#bold,format#italic,format#underline,justify#justifyleft,justify#justifycenter,justify#justifyright,lists#unordered,lists#ordered,lists#outdent,lists#indent,links#modifylink,links#unlink,table#createoredit,#paraformat,image#imageProps]">
                                                            <popovers jcr:primaryType="nt:unstructured">
                                                                <paraformat
                                                                    jcr:primaryType="nt:unstructured"
                                                                    items="paraformat:getFormats:paraformat-pulldown"
                                                                    ref="paraformat"/>
                                                            </popovers>
                                                        </dialogFullScreen>
                                                        <tableEditOptions
                                                            jcr:primaryType="nt:unstructured"
                                                            toolbar="[table#insertcolumn-before,table#insertcolumn-after,table#removecolumn,-,table#insertrow-before,table#insertrow-after,table#removerow,-,table#mergecells-right,table#mergecells-down,table#mergecells,table#splitcell-horizontal,table#splitcell-vertical,-,table#selectrow,table#selectcolumn,-,table#ensureparagraph,-,table#modifytableandcell,table#removetable,-,undo#undo,undo#redo,-,table#exitTableEditing,-]"/>
                                                    </cui>
                                                </uiSettings>
                                            </caption>
                                        </items>
                                    </column>
                                </items>
                            </columns>
                        </items>
                    </properties>
                </items>
            </tabs>
        </items>
    </content>
</jcr:root>
 
 

4. Now go ahead and check the authoring dialog for the maxlength validation for the richtext. It should look something like below:

NOTE: This is tested for AEMaaCs and should work for AEM TouchUI dialogs in other versions too. Also this works perfectly fine with the richtext in multifield too.

Thank you for reading.
Happy Coding!