Suraj Kamdi, Author at Perficient Blogs https://blogs.perficient.com/author/skamdi/ Expert Digital Insights Wed, 29 May 2024 08:07:00 +0000 en-US hourly 1 https://blogs.perficient.com/files/favicon-194x194-1-150x150.png Suraj Kamdi, Author at Perficient Blogs https://blogs.perficient.com/author/skamdi/ 32 32 30508587 Enabling Dynamic Media Feature For AEM Custom Components https://blogs.perficient.com/2024/05/28/enabling-dynamic-media-feature-aem-custom-components/ https://blogs.perficient.com/2024/05/28/enabling-dynamic-media-feature-aem-custom-components/#comments Tue, 28 May 2024 11:50:17 +0000 https://blogs.perficient.com/?p=363473

Dynamic Media is one of the versatile tools provided by Adobe. Previously, it was known as Scene7. It has strong Asset editing and authoring tools capabilities. It is based on a Cache delivery network, which loads the contents to improve page load time and renders the right image renditions correctly resized and optimized. Dynamic Media allows requesting devices to request exactly what type of rendition, version, and exact image size is needed by that device at the moment of request.  

Have you wondered about the benefits of integrating the Dynamic Media feature in custom components?  In this blog, I will demonstrate how to integrate Dynamic Media with custom components and the benefits it provides by doing so.

When we integrate dynamic media with AEM, it enables various Dynamic Media functionalities like image/video profiles, DM metadata, DM workflows, and some set of out-of-the-box Dynamic Media Components. Also, some core components like Image provide Dynamic Media functionality. 

Integrate Dynamic Media With AEM

Integrate Dynamic Media With AEM New Component

If you have not integrated Dynamic Media with AEM, please go through the documentation provided by Adobe on how to Configure the Dynamic Media for AEM.

Loading Dynamic Media Library

First, we need to load dynamic media clientlibs on every page to render Dynamic Media Assets through AEM Components.

Paste the following code into the head section page component i.e. customheaderlibs.html 

<!--/* Dynamic Media Integration */--> 

<sly data-sly-use.clientLib="${'/libs/granite/sightly/templates/clientlib.html'}" 

data-sly-call="${clientLib.all @ categories='cq.dam.components.scene7.dynamicmedia,cq.dam.components.scene7.common.i18n'}"></sly>

Custom Sling Model Implementation 

Then create one custom sling model common utility for reading Dynamic Media assets metadata properties which is required to load any AEM assets through dynamic media functionality. 

Sling model Implementation 

public interface DynamicMedia extends BaseModel { 

String getCurrentPagePath(); 

String getPageLocale(); 

String getAssetName(); 

String getAssetPath(); 

String getDynamicMediaViewerPath(); 

String getDynamicMediaImageServerPath(); 

boolean isWcmModeDisabled(); 

@Slf4j 

@Model( 

adaptables = SlingHttpServletRequest.class, 

adapters = DynamicMedia.class, 

defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL 

) 

public class DynamicMediaImpl implements DynamicMedia { 

private static final String DM_IMAGE_SERVER_PATH = "is/image/";  

private static final String DM_VIEWERS_SERVER_PATH = "s7viewers/"; 

@SlingObject 

private SlingHttpServletRequest request; 

@ScriptVariable 

private Page currentPage; 

@RequestAttribute 

private String imageReference; 

@Getter 

private String assetName; 

@Getter 

private String assetPath; 

@Getter 

private String dynamicMediaViewerPath; 

@Getter 

private String dynamicMediaImageServerPath; 

@PostConstruct 

protected void init() { 

if (StringUtils.isNotEmpty(imageReference)) { 

final Resource assetResource = request.getResourceResolver() 

.getResource(imageReference); 

if (assetResource != null) { 

final Asset asset = assetResource.adaptTo(Asset.class); 

if (asset != null) { 

this.id = getId() + "-" + asset.getID(); 

assetName = asset.getName(); 

assetPath = asset.getMetadataValue(Scene7Constants.PN_S7_FILE); 

final String dynamicMediaServer = asset.getMetadataValue( 

Scene7Constants.PN_S7_DOMAIN); 

dynamicMediaImageServerPath = dynamicMediaServer + DM_IMAGE_SERVER_PATH; 

dynamicMediaViewerPath = dynamicMediaServer + DM_VIEWERS_SERVER_PATH; 

} 

} 

} 

} 

@Override 

public String getCurrentPagePath() { 

return currentPage.getPath(); 

} 

@Override 

public String getPageLocale() { 

return LocaleUtils.generateLocaleFromPath(getCurrentPagePath()); 

} 

@Override 

public boolean isWcmModeDisabled() { 

return (WCMMode.fromRequest(request) == WCMMode.DISABLED); 

} 

}

Once we are done with the Sling model implementation part then create one HTL template inside directory /apps/<Project Driectory>/components/atoms/dynamicmedia/image.html  to provide reusability within the component whenever we want to load any image using dynamic media for a specific component. Paste the following code in the image.html file. 

<sly data-sly-template.dynamicMediaImageTemplate="${ @ imageReference, aspectRatio, altText, suffixId}"> 

<div data-sly-use.dynamicMediaImage="${'com.perficientblogs.core.models.commons.DynamicMediaModel' @ imageReference = imageReference}" 

id="${suffixId || dynamicMediaImage.id}" 

data-current-page="${dynamicMediaImage.currentPagePath}" 

data-page-locale="${dynamicMediaImage.pageLocale}" 

data-asset-path="${dynamicMediaImage.assetPath}" 

data-asset-name="${dynamicMediaImage.assetName}" 

data-asset-type="image" 

data-viewer-path="${dynamicMediaImage.dynamicMediaViewerPath}" 

data-imageserver="${dynamicMediaImage.dynamicMediaImageServerPath}" 

data-wcmdisabled="${dynamicMediaImage.wcmModeDisabled}" 

data-dms7="" 

data-mode="smartcrop" //Note: We can set the image preset mode here 

data-enablehd="always" 

data-aspectratio="${aspectRatio @context='scriptString'}" 

data-title="${altText}" 

data-alt="${altText}" 

class="s7dm-dynamic-media"> 

</div> 

</sly>

Now refer to this HTL template file anywhere in the component HTML where we want to load any authored Image through dynamic media CDN. 

<sly data-sly-use.image=" /apps/<Project Driectory>/components/atoms/dynamicmedia/image.html" 

data-sly-call="${image.dynamicMediaImageTemplate @ imageReference= modeloject.fileReference, aspectRatio= false, altText= 'Hello world Component'}"></sly>

Make sure to pass a value for imageReferenceproperty which is mandatory. We can also pass the values with other objects named aspectRatio, altTextas per the need or extend the functionality as per the requirement. 

Image Url Rendered By Custom Component

See the image above for the final output.  Look at the URL of the image rendered by the custom component.  It is delivered from Dynamic Media provided CDN.  That is the benefit you achieve with this. This way the images will be served much faster to the user based on the region they are in.   

If you have any questions or comments, drop them in the chat below! 

 

]]>
https://blogs.perficient.com/2024/05/28/enabling-dynamic-media-feature-aem-custom-components/feed/ 1 363473
AEM Customization: How to Add a Custom Action Button to the AEM Assets Action Bar Menu https://blogs.perficient.com/2023/10/06/aem-customization-how-to-add-a-custom-action-button-to-the-aem-assets-action-bar-menu/ https://blogs.perficient.com/2023/10/06/aem-customization-how-to-add-a-custom-action-button-to-the-aem-assets-action-bar-menu/#comments Fri, 06 Oct 2023 05:01:53 +0000 https://blogs.perficient.com/?p=346183

Recently, a client approached us with a requirement to update content fragment elements data, Assets metadata, and Folder metadata via an Azure-based API. Our initial solution was to implement a JMX script, which could be started by an admin user group with valid parameters. However, this was a rather limiting solution, as normal AEM authors did not have the necessary permissions to perform these operations.

To solve this problem, the client requested us to provide a custom action button in the AEM Assets Menu. This button would enable AEM authors who have permission to modify the AEM Asset metadata to update the AEM assets by triggering the action button for the selected AEM assets. This custom implementation proved to be very helpful to the client, as they could easily access the AEM Assets Menu and trigger the custom action button.

Preview Custom Action Button

AEM Asset – Preview Custom Action Button

This blog post will discuss how we implemented this custom action button.

Overlay the Node

  1. Go to AEM’s CRXDE.
  2. Jump on /libs/dam/gui/content/assets/jcr:content/actions/selection Path
  3. Right-click on the Selection node and select the Overlay Node option from the pop-up menu.
    Overlay Node Pop Up Menu Custom Action Button

    AEM – Overlay Node Pop-Up Menu

    Overlay Node Pop Up Setting Custom Action Button

    AEM – Overlay Node setup

  4. Make sure the Overlay location is under /apps/ folder, and the Match Node Types option should be checked.
  5. This will look as follows.

    Overlayed Node Custom Action Button

    AEM- Overlay Path location in CRDEX

Add Custom Action Bar Node

To add a custom action button in the Action menu, we need to create one action bar child node below Selection Now. Let’s do that!

  1. Create a node named custom-action-btn (you can put any name here) and set the following properties on a node.
    Custom Action Btn Node Custom Action Button

    AEM CRXDE – Custom Action Button Node

    XML Format

    <custom-action-btn
        granite:class="foundation-collection-action suraj-custom-action-btn"
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/collection/action"
        activeSelectionCount="single"
        icon="refresh"
        text="Refresh Asset Data"
        title="Refresh Asset Data"
        variant="actionBar">
        <granite:rendercondition
            jcr:primaryType="nt:unstructured"
            sling:resourceType="granite/ui/components/coral/foundation/renderconditions/and">
            <haspermission
                jcr:primaryType="nt:unstructured"
                sling:resourceType="dam/gui/coral/components/commons/renderconditions/haspermissions"
                path="${requestPathInfo.suffix}"
                privileges="[modify_property]"/>
        </granite:rendercondition>
    </custom-action-btn>
    
  2. You can also set granite:rendercondition if you want to handle specific scenarios in order to display this custom action button (example Provided in the above code to display this action button to the AEM user who has modify_property permission)

we are done with the overlay part. The next step is to write a custom action using Ajax call, or else you can also use custom wizards to perform any operation using this action button. But here, I will be handling custom actions using Ajax calls.

To show the action button for specific conditions, let’s consider action button should only display when the AEM author selects the Content Fragment; in that case, we need to write a custom JS code to enable and disable the visibility for the action button.

Create a JS file inside the client library folder by specifying clientlib categories as dam.gui.actions.coral

  • Asset-editor-action-btn-visibility.js
    (function(document, $) {
        "use strict";
    
        //initially hide the refresh asset button
        $(document).trigger("foundation-contentloaded");
        $(".suraj-custom-action-btn").addClass("foundation-collection-action-hidden");
    
        // hide/show refresh asset btn when author select CF asset.
        $(document).on("foundation-selections-change", ".foundation-collection", function(e) {
            let view = $("body").data("shell-collectionpage-view-layoutid");
            if (view === "list" || view === "column") {
                let selectedItem = $(this).find(".foundation-collection-item.foundation-selections-item");
                let itemType = $(selectedItem).data("editorkey");
                if (itemType?.trim() === "contentfragment") {
                    var contentFragmentPath = $(selectedItem).data("foundation-collection-item-id");
                    if(contentFragmentPath.includes("/sample-cf/")){
                        $(".suraj-custom-action-btn").removeClass("foundation-collection-action-hidden");
                    }
                }
            } else if (view === "card") {
                let selectedItem = $(this).find(".foundation-collection-item.foundation-selections-item");
                let itemType = $(selectedItem).find("[data-editorkey]").data("editorkey");
                if (itemType?.trim() === "contentfragment") {
                    var contentFragmentPath = $(selectedItem).data("foundation-collection-item-id");
                    if(contentFragmentPath.includes("/projects/")){
                        $(".suraj-custom-action-btn").removeClass("foundation-collection-action-hidden");
                    }
                }
            }
        });
    })(document, Granite.$);
    

    Now create another JS file to handle the custom action when the Author triggers the custom action button.

  • Trigger-custom-action-btn.js
    (function(document, $) {
        "use strict";
    
        // On click of Refresh Asset button update the selected CF
        $(document).on("click", ".foundation-collection-action.suraj-custom-action-btn", function(e) {
            let selectedElements = $(".foundation-collection-item.foundation-selections-item.is-selected");
            var items = [];
            $(selectedElements).each(function(e) {
                let data = $(this).data("foundation-collection-item-id");
                items.push(data);
            });
            let message = "<h3>Updating data for following Selected CF</h3>" + items.toString().replaceAll(",", "<br>");
            //Show wait ticker untill response is received from ajax call.
            const $UI = $(window).adaptTo("foundation-ui");
            var $waitTicker = $UI.waitTicker("Updating Asset", message);
    
            // Ajax to update selected CF.
            $.ajax({
                type: 'POST',
                url: '<Enter servlet path or API Path which will return the JSON output>',
                data: JSON.stringify(items),
                contentType: "application/json",
                dataType: "json",
                success: function(response) {
                    if (response.status === 200) {
                 // do some additional stuff with response as per requirement.
                        $UI.alert("Success", response.message, "success");
                        $waitTicker.clear();
                    } else {
                        $UI.alert("Error", "Failed to update selected Content Fragments", "error");
                        $waitTicker.clear();
                    }
                },
                error: function() {
                    $UI.alert("Error", "Failed to update selected Content Fragments", "error");
                    $waitTicker.clear();
                }
            });
        });
    })(document, Granite.$);
    

    Include both the JS files in the js.txt file of your client library folder.

We are done with the implementation part. Now go to the Asset folder and select any Content Fragment. The action button named Refresh Asset Data item appears in the top action bar asset Menu Navigation. Click the Refresh Asset data button to see the following pop-up window for trigger action!

Final Output Custom Action Button

AEM – Final Output of Custom Action Button

Checkout my Adobe AEM-related perficient blogs here

]]>
https://blogs.perficient.com/2023/10/06/aem-customization-how-to-add-a-custom-action-button-to-the-aem-assets-action-bar-menu/feed/ 1 346183
Beyond Embed Component: Integrate Web Code Editor https://blogs.perficient.com/2021/08/09/beyond-embed-component-integrate-web-code-editor/ https://blogs.perficient.com/2021/08/09/beyond-embed-component-integrate-web-code-editor/#respond Mon, 09 Aug 2021 17:02:14 +0000 https://blogs.perficient.com/?p=295511

After the introduction of AEM Core Components, custom component development has been drastically changed. In some use cases, there is zero customization and core components can be directly used for authoring. Even in those situations, the custom component is needed. It can be quickly built by extending the existing core components. Thus, the development cost and time to production are reduced.

Embed Component

In this era of social networking, we have faced the use case of embedding media or HTML from other social media sites. For these instances, OOTB AEM Core – Embed Component comes in handy.

Embed Component helps authors to embed three types of external content within a page:

  • URL-based – This feature supports the URL-based resource which is as per oEmbed Standards. This is used for use cases like embedding Facebook, Twitter, Instagram, etc.
  • HTML – This feature allows the author to directly add HTML content to the AEM page.
  • Embeddable – This feature allows customization of embedded resources like YouTube. Whereas, it provides flexibility for the author to provide additional information and parameters to embed the resource to the page. This functionality uses a backend pre-processor.

All the above-mentioned options can be configured using component design dialog for enabling or disabling the feature.

AEM Core Embed Component Dialog

Security

Embedding content dynamically can lead to security vulnerability.  AEM uses XSS Protection using the Anti Samy.  For more information on XSS Protection, do AEM documentation on Security.

That said, what if embedded content needs custom dynamic behavior using JS or style changes using CSS?  If XSS Protection config.xml file does not have <script> and <style> tag in the proper location, it will be stripped out from the generated HTML.

For the basic use case, AEM Core Component’s Embed Component will be sufficient.   What if the author needs advanced functionality like JS Scripting, adding custom CSS directly on web pages using authoring dialogs?

Integrating Third-Party Editor

Here, I will show how to integrate a web-based code editor into AEM Authoring Dialog.  For this integration, I have chosen Ace Editor.  Ace Editor has an embeddable code editor API that will provide the actual look and feel like a simple text editor. Also, this plugin provides additional supports for HTML, JS, and CSS syntax highlighter and auto-closing tag feature.

AEM Code Embed Component Dialog

The below steps elaborate on how to integrate Ace Editor into a custom component dialog box.

Creating the Component

Let’s create this component in these simple steps:

  1. First, create one component named Code Embed component.
  2. Next, create the cq:dialog for rendering the external HTML contents into the page. cq:dialog code should look like as below:
    <?xml version="1.0" encoding="UTF-8"?>
    <jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
        jcr:primaryType="nt:unstructured"
        jcr:title="Code Embed Component"
        sling:resourceType="cq/gui/components/authoring/dialog"
        extraClientlibs="[surajk.editor.aceeditor]">
        <content
            jcr:primaryType="nt:unstructured"
            sling:resourceType="granite/ui/components/foundation/container">
            <items jcr:primaryType="nt:unstructured">
                <column
                    jcr:primaryType="nt:unstructured"
                    sling:resourceType="granite/ui/components/coral/foundation/container">
                    <items jcr:primaryType="nt:unstructured">
                        <warning
                            granite:class="surajk-warning-heading"
                            jcr:primaryType="nt:unstructured"
                            sling:resourceType="granite/ui/components/coral/foundation/heading"
                            level="6"
                            text="Warning!!! Custom code is not validated. Incorrect code cacould use serious issues with the page or whole site."/>
                        <container
                            granite:id="surajk-ace-container"
                            jcr:primaryType="nt:unstructured"
                            sling:resourceType="granite/ui/components/coral/foundation/container"/>
                        <htmlmarkup
                            granite:class="hide"
                            granite:id="surajk-ace-editor"
                            jcr:primaryType="nt:unstructured"
                            sling:resourceType="granite/ui/components/coral/foundation/form/textarea"
                            fieldLabel=""
                            name="./htmlMarkup"/>
                    </items>
                </column>
            </items>
        </content>
    </jcr:root>
  3. Now, create one client library folder with the following properties.
    <?xml version="1.0" encoding="UTF-8"?>
    <jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0"
        jcr:primaryType="cq:ClientLibraryFolder"
        categories="surajk.editor.aceeditor"/>
    
  4. Next, create two folders JS and CSS. Please Copy and paste the code from this Repository.
    1. JS folder should contain the following files
      jquery-ace.min.js
      ace.min.js
      theme-monokai.js
      mode-markdown.js
      author.js
      
    2. CSS folder should contain the following files
      ace.css
  5. Then, add custom logic in the author.js file. You need to open the file and put the following code in order to render the actual text editor environment in component dialog.
    $(document).on("dialog-ready", function() {
    
       var textarea = $('#surajk-ace-editor');
       $("#surajk-ace-container").html($(textarea).html());
    
       var editor = ace.edit("surajk-ace-container");
       editor.setTheme("ace/theme/monokai");
       editor.getSession().setMode("ace/mode/html");
       editor.getSession().on('change', function () {
           textarea.val(editor.getSession().getValue());
       });
       textarea.val(editor.getSession().getValue());
     });
    
  6. After that, open the code-embed.html file from the component and add the following line of code.
    <sly 
         data-sly-test.empty="${!properties.htmlMarkup}"
         data-sly-use.template="core/wcm/components/commons/v1/templates.html"
         data-sly-call="${template.placeholder @ isEmpty=empty}">
    </sly>
    
    <sly data-sly-test="${!empty}">
      ${properties.htmlMarkup @ context='unsafe'}
    </sly>
    

    Note: For Demo purposes, I am using simple HTL logic. You can also use the Sling model for the same.

  7. Once we are done with all the above steps, the next thing is to check the component functionality. Allow and add this component into the layout container. You will see component dialog will render actual HTML mark-up text on the page. AEM Blog Code Embed Component

You can also integrate your favorite JS-based 3rd party text editor plugin into AEM components cq:dialog.

Resources

Download this package for reference. If you have any questions, please reach out to me.

]]>
https://blogs.perficient.com/2021/08/09/beyond-embed-component-integrate-web-code-editor/feed/ 0 295511
AEM Customization: Show Unpublished Reference Alert for Tagpicker https://blogs.perficient.com/2020/12/18/aem-customization-show-unpublished-reference-alert-for-tagpicker/ https://blogs.perficient.com/2020/12/18/aem-customization-show-unpublished-reference-alert-for-tagpicker/#respond Fri, 18 Dec 2020 14:00:38 +0000 https://blogs.perficient.com/?p=285085

Recently, I published a blog post regarding AEM Customization: Show Unpublished Reference Alert for Content Path where I showed how to customize the Pathfield and Xffield resourceType. In doing this, I was able to showcase how to create a notification for the content author as to whether the selected content path is published or not. I found it to be a useful feature for one of our clients while they were authoring a number of AEM content pages. After the successful delivery of this feature, the client requested that we implement a similar kind of feature while selecting AEM tags in cq:dialog.

Unfortunately, AEM does not have the OOTB functionality to show unpublished notifications for selected AEM Tags while authoring the components. In this blog, I am going to show how you can extend the functionality of tagspicker resourceType to show unpublished reference alerts while selecting AEM tags.

Steps to extend the functionality of tagspicker resourceType.

  1. First, open CRXDE Lite console i.e. http://localhost:4502/crx/de/index.jsp
  2. Create one node named clientlibs with the following properties. After that, create one folder named js inside clientlibs node and also one file named js.txt.
    <?xml version="1.0" encoding="UTF-8"?>
    <jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" 
         jcr:primaryType="cq:ClientLibraryFolder" 
         categories="[cq.authoring.editor]" />

    The structure should look as follows:Unpublished Reference Alert For Tags Crxde 1

  3. Now, create one file named unpublished-reference-alert-for-tags.js  inside /clientlibs/js  folder and paste the following script:
    (function ($, window) {
        // cq:tag path validation only for cq:dialog
        // Tagfield : cq/gui/components/common/tagspicker
        $(window).adaptTo("foundation-registry").register("foundation.validation.validator", {
            selector: ".js-cq-TagsPickerField .js-coral-pathbrowser-input",
            validate: function (element) {
                var tagPath = element.value;
                if (tagPath !== '' && tagPath != null) {
                    checkReplicationStatus(tagPath);
                }
            }
        });
    
        // Check when coral-Pathbrowser-picker dialog is open.
        $(document).on("click", ".coral-ColumnView-item.is-active", function () {
            var selectedPath = $(this).data("value");
            checkReplicationStatus(selectedPath);
        });
    
        // Check the replication status of the selected tag.
        function checkReplicationStatus(tagPath){
            const CONTENT_PATH_PREFIX = "/content/cq:tags/";
            const INFINITY_JSON = ".infinity.json";
            const CQ_LAST_REPLICATION_ACTION = "cq:lastReplicationAction";
            const ACTIVATE = "Activate";
            if (tagPath.indexOf(CONTENT_PATH_PREFIX)  !== -1) {
                var tagPathUrl = tagPath.concat(INFINITY_JSON);
                $.getJSON(tagPathUrl)
                    .done(function (data) {
                        var lastReplicationAction = data[CQ_LAST_REPLICATION_ACTION];
                        if (lastReplicationAction !== ACTIVATE) {
                            var message = " You have Selected Unpublished Tag : " + tagPath.bold();
                             $(window).adaptTo("foundation-ui").notify('', message, 'notice');
                        }
                    });
            }
        }
    })($, window);

     

  4. Next, modify the js.txt  file as follows:Unpublished Reference Alert For Tags Crxde 2

Once you are done with all the above-mentioned steps, your next step is to test the functionality of tagpicker resourceType.

You can go to any page and drag/add components into a layout container whose cq:dialog has the tagpicker field. Select any tag that has not yet published. For testing purposes, you can create a new tag.

While authoring the component you will get the following result:

Unpublished Reference Alert For Tags Demo 2

To ensure this would help, I tested this extended functionality on AEM 6.5. It will work only work for cq/gui/components/common/tagspicker resourceType.

Download this package for reference, and don’t forget to drop a comment if you need more help on this. In case you missed my previous blog, then please visit AEM Customization: Show Unpublished Reference Alert for Content Path.

]]>
https://blogs.perficient.com/2020/12/18/aem-customization-show-unpublished-reference-alert-for-tagpicker/feed/ 0 285085
AEM Customization: Show Unpublished Reference Alert for Content Path https://blogs.perficient.com/2020/12/08/aem-customization-show-unpublished-reference-alert-for-content-path/ https://blogs.perficient.com/2020/12/08/aem-customization-show-unpublished-reference-alert-for-content-path/#respond Tue, 08 Dec 2020 15:46:51 +0000 https://blogs.perficient.com/?p=284492

Have you ever noticed that while authoring an AEM component, the component dialog box does not provide any hints or alerts for if the selected content path is published or not? This is a confusing scenario for AEM authors, especially when setting or choosing the content path in the component dialog for pathfield and xffield resourceTypes.

I recently encountered the same scenario with one of our clients. They set the unpublished path for a link in the component dialog and published that page. After publishing the page, they found that the component used on the page showed a broken link on the AEM publish instance.

Unfortunately, AEM does not have the functionality to show unpublished notifications for selected content paths while authoring the components. That goes for whether you select page path, experience fragment path, asset path, or content fragment path.

To overcome this scenario, we can extend the functionality of the pathfield and xffield resourceType.

Steps to extend the functionality of pathfield and xffield resourceType

  1. First, open CRXDE Lite console ie. http://localhost:4502/crx/de/index.jsp
  2. Go to /app/<project-name>
  3. Create one node named clientlibs  with the following properties. After that, you create one folder named js  inside clientlibs node and also one file named js.txt.
    <?xml version="1.0" encoding="UTF-8"?>
    <jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" 
         jcr:primaryType="cq:ClientLibraryFolder" 
         categories="[cq.authoring.editor]" />

    The structure should like as follows:

    Unpublished Reference Alert 1

  4. Then, you create one folder named js  inside clientlibs node and also one file named js.txt.
  5. Now, you create one file named unpublished-reference-alert.js inside /clientlibs/js folder and paste the following script:
    (function ($, window) {
        // Content path validation only for
        // 1. granite/ui/components/coral/foundation/form/pathfield
        // 2. cq/experience-fragments/editor/components/xffield
        $(window).adaptTo("foundation-registry").register("foundation.validation.validator", {
            selector: "foundation-autocomplete",
            validate: function (element) {
                var ctaUrl = element.value;
                // Check if pathfield/xffield value is not null then show notification.
                if (ctaUrl !== '' && ctaUrl != null) {
                    checkIfPublishedUrl(ctaUrl);
                }
            }
        });
    
        // Check whether selected content path is published or not.
        function checkIfPublishedUrl(ctaUrl) {
            const JCR_CONTENT = "jcr:content";
            const CQ_LAST_REPLICATION_ACTION = "cq:lastReplicationAction";
            const ACTIVATE = "Activate";
            const INFINITY_JSON = ".infinity.json";
            var url = ctaUrl.concat(INFINITY_JSON);
            $.getJSON(url)
                .done(function (data) {
                    var lastReplicationAction = data[JCR_CONTENT][CQ_LAST_REPLICATION_ACTION];
                    if (lastReplicationAction !== ACTIVATE) {
                        // Show notification to author if unpublished reference is selected.
                        var message = " You have selected unpublished references. : <br>".concat(ctaUrl.bold());
                        $(window).adaptTo("foundation-ui").notify('', message, 'notice');
                    }
                })
        };
    })($, window);
    
  6. Next, modify js.txt  file as follows:Unpublished Reference Alert 2
  7. Once you are done with the above steps the next thing is to test the functionality.
  8. You can go to any page and drag/add title component into layout container. You can also add the component whose cq:dialog has pathfield/xffield.
  9. Select any path that has not yet published. (Note: for testing, you can create a new page) and you will get the following result.Unpublished Reference Alert 3

To ensure this would help, I tested this extended functionality on AEM 6.5. It will work for the AEM page path, experience fragment path, asset path, and content fragment path.

Download this package for reference, and don’t forget to drop a comment if you need more help on this.

]]>
https://blogs.perficient.com/2020/12/08/aem-customization-show-unpublished-reference-alert-for-content-path/feed/ 0 284492
Adding a Color Picker to the AEM Content Fragment Model Editor https://blogs.perficient.com/2020/10/20/adding-a-color-picker-to-the-aem-content-fragment-model-editor/ https://blogs.perficient.com/2020/10/20/adding-a-color-picker-to-the-aem-content-fragment-model-editor/#comments Tue, 20 Oct 2020 15:07:40 +0000 https://blogs.perficient.com/?p=282290

Recently, one of our clients looked to add a custom field in the Adobe Experience Manager (AEM) Content Fragment Model Editor. If you’re wondering what AEM Content Fragments are, check out Dan Klco’s post, as he does a great job explaining.

Content Fragment Models are built with elements from various out-of-the-box data types, including single-line text, multi-line text, number, boolean (only for checkboxes), date/time, enumeration (only for static dropdown values), tags, and content reference.

Adding a Custom Field to the AEM Content Fragment Model Editor

After investigating the structure of the Content Fragment Model form builder configuration inside CRXDE,  I found that we can easily add most other data types (there are some restrictions for a few datatypes). To extend and customize this form builder configuration of Content Fragment Model Editor, we need to overlay the Content Fragment form builder resource.

In our client’s case, we needed to set the requirement to add the Color Picker field in the Content Fragment Model in AEM 6.5. In this post, I’m going to show you how extended the functionality of the Content Fragment Model to set the Color Picker data type.

How to Set the Color Picker Data Type:

  1. First, open the CRXDE Lite console http://localhost:4502/crx/de/index.jsp
  2. Enter the following path in the search bar and hit enter. /libs/settings/dam/cfm/models/formbuilderconfig/datatypes/items
  3. Right-click on the items node. This will open a pop-up menu.
  4. Click on Overlay Node Option from the pop-up menu. This will open another model dialog. Then click on select the Match Node Types option. Make sure the model dialog contains the following values.
    Path: /libs/settings/dam/cfm/models/formbuilderconfig/datatypes/items
    Overlay Location: /apps/
    Match Node Types: checkedSk Content Fragment FormbuilderAfter verifying the above properties and values, click “OK.” This will overlay the location of /libs/settings/dam/cfm/models/formbuilderconfig/datatypes/items path inside /apps/ folder.
  5. Now go to /apps/settings/dam/cfm/models/formbuilderconfig/datatypes/items.
  6. Create one child node inside the items node with the name color-picker.
  7. Set the following properties on /apps/settings/dam/cfm/models/formbuilderconfig/datatypes/items/color-picker node.
    fieldIcon (String)=”colorPalette”
    fieldProperties (String [])=”[labelfield,maptopropertyfield,placeholderfield,textvaluefield,requiredfield]”
    fieldPropResourceType (String)=”dam/cfm/models/editor/components/datatypes/field”
    fieldResourceType (String [])=”[granite/ui/components/coral/foundation/form/colorfield]”
    fieldTitle (String)=”Color Picker”
    listOrder (String)=”9″
    renderType (String)=”text”
    valueType (String [])=”[string]”
  8. Color-picker Node xml should look like as follows
    <?xml version="1.0" encoding="UTF-8"?>
    <jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="nt:unstructured">
    <items jcr:primaryType="nt:unstructured">
    <color-picker
    jcr:primaryType="nt:unstructured"
    fieldIcon="colorPalette"
    fieldProperties="[labelfield,maptopropertyfield,placeholderfield,textvaluefield,requiredfield]"
    fieldPropResourceType="dam/cfm/models/editor/components/datatypes/field"
    fieldResourceType="[granite/ui/components/coral/foundation/form/colorfield]"
    fieldTitle="Color Picker"
    listOrder="9"
    renderType="text"
    valueType="[string]"/>
    </items>
    </jcr:root>

    Sk CF Formbuilder Node OverlayNow you are successfully done with the node overlay and AEM customization process.

  9. In order to see the changes, we need to create a configuration for the Content Fragment Model. Go to AEM Start Menu > Tools > General > Configuration Browser and create a configuration for the Content Fragment Model. You can also use the OOTB global configuration if you don’t want to create a custom configuration (it depends upon your requirement). Here I am using AEM OOTB configuration with the name “Global.”
  10. Once you are done with the configuration steps, go to AEM Start Menu > Tools > Assets > Content Fragment Model > Global (or go to your custom configuration).
    Then create a new custom Content Fragment Model (In this case, I have created a model with the name “Color Picker CFM“) and click on open. Now, take a look at Data Types inside the sidebar. There you will see a new Data type is appearing with the name “Color Picker.
  11. To create your custom Content Fragment Model, drag and drop color picker data type in editor block, and set some properties on the color picker field. I set the following properties.
    Field Label: Set Color
    Property Name: colorSk Cf Model EditorOnce you are done with this click on Save  Button.
  12. Now go to AEM Start Menu > Navigations > Assets > <any Folder> and Create a Content Fragment using Color Picker CFM model. Once it is created, click on the Open button. You will notice the Content Fragment has the color picker field with the label “Set Color.” Now you can select any predefined color from the color palette, or you can create your own color using a color mixer.

Sk Cf With Color Picker Data Type

You can use these same steps to extend the functionality of the Content Fragment Model whenever you get such a requirement for other data types as well!

Download this package for reference, and don’t forget to drop a comment if you need more help on this.

]]>
https://blogs.perficient.com/2020/10/20/adding-a-color-picker-to-the-aem-content-fragment-model-editor/feed/ 8 282290
Creating Your First Custom AEM Component Using React https://blogs.perficient.com/2019/09/17/creating-your-first-custom-aem-component-using-react-part-1/ https://blogs.perficient.com/2019/09/17/creating-your-first-custom-aem-component-using-react-part-1/#comments Tue, 17 Sep 2019 17:28:41 +0000 https://blogs.perficientdigital.com/?p=238696

Recently, I went through an article about integrating React JS and Angular JS with AEM. In this blog, I am going to show you how to create a custom component that includes a cq:dialog and one that does not include a cq:dialog. Before building the components, clone the repository, which is a sample project based on React JS.
First, we will deploy this project in AEM 6.5. Then, we will create one sample component called custom-heading.
The component structure should look like this.
1. Component node (<project root>/apps/my-aem-project/components/content/custom-heading)

<?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"
    jcr:primaryType="cq:Component"
    jcr:title="Custom Heading"
    componentGroup="My AEM Project"/>

2. cq:dialog

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
    xmlns:granite="http://www.adobe.com/jcr/granite/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0"
    xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0"
    jcr:primaryType="nt:unstructured"
    jcr:title="Custom Heading"
    sling:resourceType="cq/gui/components/authoring/dialog">
    <content
        jcr:primaryType="nt:unstructured"
        sling:resourceType="granite/ui/components/coral/foundation/container">
        <items jcr:primaryType="nt:unstructured">
            <tabs
                jcr:primaryType="nt:unstructured"
                sling:resourceType="granite/ui/components/coral/foundation/tabs"
                maximized="{Boolean}true">
                <items jcr:primaryType="nt:unstructured">
                    <general
                        jcr:primaryType="nt:unstructured"
                        jcr:title="General"
                        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">
                                            <heading
                                                jcr:primaryType="nt:unstructured"
                                                sling:resourceType="granite/ui/components/coral/foundation/form/textfield"
                                                fieldLabel="Heading"
                                                name="./heading"
                                                required="{Boolean}true"/>
                                            <heading-type
                                                jcr:primaryType="nt:unstructured"
                                                sling:resourceType="granite/ui/components/coral/foundation/form/select"
                                                fieldLabel="Heading Type"
                                                name="./headingType">
                                                <items jcr:primaryType="nt:unstructured">
                                                    <h1
                                                        jcr:primaryType="nt:unstructured"
                                                        text="H1"
                                                        value="h1"/>
                                                    <h2
                                                        jcr:primaryType="nt:unstructured"
                                                        text="H2"
                                                        value="h2"/>
                                                    <h3
                                                        jcr:primaryType="nt:unstructured"
                                                        text="H3"
                                                        value="h3"/>
                                                </items>
                                            </heading-type>
                                            <heading-color
                                                jcr:primaryType="nt:unstructured"
                                                sling:resourceType="granite/ui/components/coral/foundation/form/select"
                                                fieldLabel="Heading Color"
                                                name="./headingColor">
                                                <items jcr:primaryType="nt:unstructured">
                                                    <red
                                                        jcr:primaryType="nt:unstructured"
                                                        text="Red"
                                                        value="red-color"/>
                                                    <green
                                                        jcr:primaryType="nt:unstructured"
                                                        text="Green"
                                                        value="green-color"/>
                                                    <blue
                                                        jcr:primaryType="nt:unstructured"
                                                        text="Blue"
                                                        value="blue-color"/>
                                                </items>
                                            </heading-color>
                                        </items>
                                    </column>
                                </items>
                            </columns>
                        </items>
                    </general>
                </items>
            </tabs>
        </items>
    </content>
</jcr:root>

Now the question is why have I not created custom-heading.html? For a React-based component, we are not going to create any custom-heading.html. Also, we are not going to use HTL/Sightly to render logic. Surprising, right? We will render the component HTML using React. Therefore, I have created a React component called CustomHeading.
In the project you’ve cloned, create the CustomHeading (<project root>/react-app/src/components/CustomHeading) folder and create the following two files.
The react component structure should look like this.
1. CustomHeading.js

import React, {Component} from 'react';
import {MapTo} from '@adobe/cq-react-editable-components';
require('./CustomHeading.scss');
const CustomHeadingEditConfig = {
    emptyLabel: 'Custom Heading',
    isEmpty: function(props) {
        return !props || props.heading.trim().length < 1;
    }
};
export default class CustomHeading extends Component {
    render() {
            return (<div className="heading"> First Component </div>);
    }
}
MapTo('my-aem-project/components/content/custom-heading')(CustomHeading, CustomHeadingEditConfig);

2. CustomHeading.scss

.red-color{
  color: #fc0b03;
}
.green-color{
color : #39fc03;
}
.blue-color{
color : #2403fc;
}

Now, the question is still how do you read the authored value in a React component? For this, we need a Sling Model exporter. I have created one Sling Model exporter class.

The Sling Model will look like this.
CustomHeadingModel.java

package com.surajkamdi.core.models;
import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import javax.annotation.Nonnull;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.ValueMapValue;
@Model(adaptables = SlingHttpServletRequest.class,
    resourceType = CustomHeadingModel.RESOURCE_TYPE,
    adapters = {CustomHeadingModel.class, ComponentExporter.class},
    defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
@Exporter(name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION)
public class CustomHeadingModel implements ComponentExporter {
  protected static final String RESOURCE_TYPE = "my-aem-project/components/content/custom-heading";
  @ValueMapValue(name = "heading")
  private String heading;
  @ValueMapValue(name = "headingType")
  private String headingType;
  @ValueMapValue(name = "headingColor")
  private String headingColor;
  public String getHeading() {
    return heading;
  }
  public String getHeadingType() {
    return headingType;
  }
  public String getHeadingColor() {
    return headingColor;
  }
  @Nonnull
  @Override
  public String getExportedType() {
    return RESOURCE_TYPE;
  }
}

Run the build and deploy using the following Maven commands:

mvn clean install -PautoInstallPackage -Padobe-public

Now let’s add this component into the page and do some authoring, then get the resource path of component. Open a new tab in the browser window and paste the following URL constructed using resource path in the following format:
http://localhost:4502/<content_root_path>/custom_heading.model.json 

CustomheadercomponentjsonYou will notice authored values are also present here in the above structure. This is the output rendered by the above Sling Model exporter class.
In order to add this component into the page created in AEM, we need to import the component inside file called <project root>/react-app/src/ Index.js. If you are using the above-mentioned repository structure, then you need to include a component path inside MappedComponents.js. Otherwise, you will not able to add a component into the page.
MappedComponets.js

require('./CustomHeading/CustomHeading');

Finally, the last step is to read the above JSON in the React component and render the logic to achieve component functionality.
CustomHeading.js

import React, {Component} from 'react';
import {MapTo} from '@adobe/cq-react-editable-components';
require('./CustomHeading.scss');
const CustomHeadingEditConfig = {
    emptyLabel: 'Custom Heading',
    isEmpty: function(props) {
        return !props.heading || props.heading.trim().length < 1;
    }
};
export default class CustomHeading extends Component {
    render() {
      let headingElement = this.props.headingType ? React.createElement(this.props.headingType, {className: this.props.headingColor},this.props.heading) : '';
      return (<div className="heading"> {headingElement} </div>);
    }
}
MapTo('my-aem-project/components/content/custom-heading')(CustomHeading, CustomHeadingEditConfig);

Now, deploy your code using Maven commands.

mvn clean install -PautoInstallPackage -Padobe-public

Custom Aem React Component Surajkamdi
I hope this blog post was helpful. In the next blog, I will create a component without using cq:dialog.
If you are still confused about working with the React component, visit here or comment below.

]]>
https://blogs.perficient.com/2019/09/17/creating-your-first-custom-aem-component-using-react-part-1/feed/ 54 269754