Expand my Community achievements bar.

Don’t miss the AEM Skill Exchange in SF on Nov 14—hear from industry leaders, learn best practices, and enhance your AEM strategy with practical tips.
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"