Expand my Community achievements bar.

Dive into Adobe Summit 2024! Explore curated list of AEM sessions & labs, register, connect with experts, ask questions, engage, and share insights. Don't miss the excitement.
SOLVED

Restrict number of component in parsys

Avatar

Level 4

Hi, I've a project requirement (AEM 6.2) where I need to restrict the specific component in parsys to a limited count? Let's say I've 3 component allowed for a particular section parsys. Let's call it {A, B, C}. The 'A' component can be dragged any number of time in parsys but if component 'B' is dragged on parsys then it shouldn't allow to drag component 'C' and vice-versa. Thanks, Vijay

1 Accepted Solution

Avatar

Correct answer by
Administrator

Here is the working example:- Experiencing Adobe Experience Manager - Day CQ: AEM 61 - Touch UI Limit the Number of Components Add...

But, Editable template has evolved big way now. I would recommend you to use Editable Template for this.



Kautuk Sahni

View solution in original post

7 Replies

Avatar

Level 10

This is where when using a more recent version of AEM Editable Templates and policies are best practice. When setting policies in an editable template - you can control which components are allowed to be used.

Avatar

Level 4

@smacdonald2008 can you help me how to do that?

This is where when using a more recent version of AEM Editable Templates and policies are best practice. When setting policies in an editable template - you can control which components are allowed to be used.

Avatar

Level 4

Hi Rajashankar ,

Thanks for the reply . In the link you have provided , one with reply that is marked as correct seems like those urls are not valid any more.

thanks,

Vijay.

Avatar

Level 10

Hi vijayk87714775,

You will have to write a custom JQuery listener to handle this on the drag and drop event.

Avatar

Correct answer by
Administrator

Here is the working example:- Experiencing Adobe Experience Manager - Day CQ: AEM 61 - Touch UI Limit the Number of Components Add...

But, Editable template has evolved big way now. I would recommend you to use Editable Template for this.



Kautuk Sahni

Avatar

Level 4

I fix follow these steps:

1. Login crx/de and create a custom js folder touchui-limit-parsys

ui.apps/src/main/content/jcr_root/apps/myapp/clientlibs/touchui-limit-parsys

2. add the categories and dependencies for the clientlib folder

categories="[cq.authoring.dialog,cq.compat.authoring.widgets,myapp.components]"
dependencies="underscore"

3. create file ui.apps/src/main/content/jcr_root/apps/myapp/clientlibs/touchui-limit-parsys/js/touchui-limit-parsys.js 

/*
 * #%L
 * ACS AEM Commons Package
 * %%
 * Copyright (C) 2013 Adobe
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 *
 * Extends /libs/foundation/components/parsys to limit the components that be added
 * using drag/drop, copy/paste or insert actions
 * To enable limit feature set the property acsComponentsLimit with required limit on design node
 * eg. to limit the components to 4 on rightpar of /content/geometrixx/en.html
 * set acsComponentsLimit=4 on /etc/designs/geometrixx/jcr:content/homepage/rightpar
 */
(function ($, $document) {
    "use strict";

    var ACS_COMPONENTS_LIMIT = "acsComponentsLimit";
    /** AEM 6.2 does not have the function resolveProperty in util.js and thus
     * breaks authoring on a supported version. To deal with this we need to detect
     * if the function is available and fallback to 6.2 functions if it is not.
     */
    function correctlyResolveProperty(design, path){
        if ("resolveProperty" in Granite.author.util) {
            //function was found, use it.
            return Granite.author.util.resolveProperty(design, path);
        }else{
            //didn't find the function in util.js, we'll use _discover instead.
            return Granite.author.components._discover(design, path);
        }
    }
    /**
     * mostly taken over from /libs/cq/gui/components/authoring/editors/clientlibs/core/js/storage/components.js _findAllowedComponentsFromPolicy
     */
    function _findPropertyFromPolicy(editable, design, propertyName) {
        var cell = correctlyResolveProperty(design, editable.config.policyPath);

        if (!cell || !cell[propertyName]) {
            // Inherit property also from its parent (if not set in the local policy path)
            var parent = Granite.author.editables.getParent(editable);

            while (parent && !(cell && cell[propertyName])) {
                cell = correctlyResolveProperty(design, parent.config.policyPath);
                parent = Granite.author.editables.getParent(parent);
            }
        }
        if (cell && cell[propertyName]) {
            return cell[propertyName];
        }
        return null;
    }

    /**
     * mostly taken over from /libs/cq/gui/components/authoring/editors/clientlibs/core/js/storage/components.js _findAllowedComponentsFromDesign
     * Returns the value of the given property name extracted from the given design configuration object (also supports content policies)
     */
    function _findPropertyFromDesign(editable, design, propertyName) {
        if (editable && editable.config) {
            if (editable.config.policyPath) {
                return _findPropertyFromPolicy(editable, design, propertyName);
            } else {
                // All cell search paths
                var cellSearchPaths = editable.config.cellSearchPath;

                if (cellSearchPaths) {
                    for (var i = 0; i < cellSearchPaths.length; i++) {
                        var cell = correctlyResolveProperty(design, cellSearchPaths[i]);

                        if (cell && cell[propertyName]) {
                            return cell[propertyName];
                        }
                    }
                }
            }
        }
        return null;
    }

    function showErrorAlert(message, title){
        var fui = $(window).adaptTo("foundation-ui"),
            options = [{
                text: "OK",
                warning: true
            }];

        message = message || "Unknown Error";
        title = title || "Error";

        fui.prompt(title, message, "error", options);
    }

    function getChildEditables(parsys){
        var editables = Granite.author.edit.findEditables(),
            children = [], parent;

        _.each(editables, function(editable){
            parent = editable.getParent();

            if(parent && (parent.path === parsys.path)){
                children.push(editable);
            }
        });

        return children;
    }

    function isWithinLimit(parsysEditable, itemsToAdd){
        var isWithin = true, currentLimit = "";

        currentLimit = _findPropertyFromDesign(parsysEditable, Granite.author.pageDesign, ACS_COMPONENTS_LIMIT);
        if (currentLimit === null) {
            return true;
        }
        var limit = parseInt(currentLimit);
        var children = getChildEditables(parsysEditable);
        itemsToAdd = itemsToAdd ? itemsToAdd : 1;
        isWithin = children.length - 1 + itemsToAdd <= limit;

        if(!isWithin){
            showErrorAlert("Limit of components within this component system exceeded, allowed only up to " + currentLimit + " components.");
        }

        return isWithin;
    }

    function extendComponentDrop(){
        var dropController = Granite.author.ui.dropController,
            compDragDrop;

        if (dropController !== undefined) {
            compDragDrop = dropController.get(Granite.author.Component.prototype.getTypeName());

            //handle drop action
            if (compDragDrop !== undefined) {
                //handle drop action
                compDragDrop.handleDrop = function(dropFn){
                    return function (event) {
                        if(!isWithinLimit(event.currentDropTarget.targetEditable.getParent())){
                            return;
                        }
                        return dropFn.call(this, event);
                    };
                }(compDragDrop.handleDrop);
            }

            //handle paste action
            var pasteAction = Granite.author.edit.Toolbar.defaultActions.PASTE;
            // overwrite both execute and handler as both seem to be used
            pasteAction.execute = pasteAction.handler = function(pasteHandlerFn){
                return function (editableBefore) {
                    // only prevent copy but not move operations (if previous operation was cut)
                    if(!Granite.author.clipboard.shouldCut()) {
                        if(!isWithinLimit(editableBefore.getParent(), Granite.author.clipboard.length)){
                            return;
                        }
                    }
                    return pasteHandlerFn.call(this, editableBefore);
                };
            }(pasteAction.execute);

            // handle insert action
            var insertAction = Granite.author.edit.Toolbar.defaultActions.INSERT;
            // overwrite both execute and handler (for doubleclick and "+" icon click functionality)
            insertAction.execute = insertAction.handler = function(insertHandlerFn){
                return function(editableBefore, param, target){
                    if(!isWithinLimit(editableBefore.getParent())){
                        return;
                    }
                    return insertHandlerFn.call(this, editableBefore, param, target);
                };
            }(insertAction.execute);
        }
    }

    $(function() {
        if (Granite && Granite.author && Granite.author.edit && Granite.author.Component &&
            Granite.author.ui && Granite.author.ui.dropController) {
            extendComponentDrop();
        }
    });
}(jQuery, jQuery(document)));

4. create file ui.apps/src/main/content/jcr_root/apps/myapp/clientlibs/touchui-limit-parsys/js.txt

js/touchui-limit-parsys.js

5. set a limit on the policies

ui.content/src/main/content/jcr_root/conf/myapp/settings/wcm/policies/.content.xml

 

<testcomponent jcr:primaryType="nt:unstructured">
                    <testcomponent-style-system
                        jcr:lastModified="{Date}2021-07-20T09:05:25.344+07:00"
                        jcr:lastModifiedBy="admin"
                        jcr:primaryType="nt:unstructured"
                        jcr:title="Test Component Style System"
                        sling:resourceType="wcm/core/components/policy/policy"
                        acsComponentsLimit="2"
                        autopauseDisabled="false"
                        autoplay="false"
                        components="[/apps/myapp/components/content/test1component]"
                        delay="5000">
                        <jcr:content
                            cq:lastReplicated="{Date}2021-03-08T06:29:29.280Z"
                            cq:lastReplicatedBy="bmdesai"
                            cq:lastReplicationAction="Activate"
                            jcr:mixinTypes="[cq:ReplicationStatus]"
                            jcr:primaryType="nt:unstructured"/>
                    </contentsplit-style-system>
                </testcomponent>

Note: In the testcomponent we can add limit test1component by the config acsComponentsLimit="2"