Expand my Community achievements bar.

SOLVED

Customize Images list in Side rail Panel in Authoring Mode

Avatar

Level 2

Hello Fellow AEM'ers

 

I am actually looking for approaches to customize the side rail panel in page authoring mode so that I do not want to populate the images from a particular folder in DAM.

 

For example in below image, you can see all the images in highlighted screen. I would like to restrict that by default for a particular folder in DAM. I know we can use Filter path, but authors will everytime have to go and select their appropriate folder. Really appreciate any help or advice here.

 

shyamr2b1_0-1664546408261.png

 

 

 

1 Accepted Solution

Avatar

Correct answer by
Community Advisor

@samr2b1 There is no other way to achieve your goal without customization. AEM OOTB is not providing any option to configure the default path, either specific or dedicated endpoint you could use. Of course it can be done many ways, my first post shows probably the easiest customization. Here is an alternative, backend only solution that is using using  Sling Filter.

package com.example.filters;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.engine.EngineConstants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import java.io.IOException;

@Component(service = Filter.class,
           property = {
                   EngineConstants.SLING_FILTER_SCOPE + "=" + EngineConstants.FILTER_SCOPE_REQUEST,
           })
public class AssetsFinderFilter implements Filter {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response,
                         final FilterChain filterChain) throws IOException, ServletException {
        final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;

        if (slingRequest.getRequestPathInfo().getResourcePath().startsWith("/bin/wcm/contentfinder/asset/view")) {
            filterChain.doFilter(new AssetsFinderSlingHttpServletRequest(slingRequest), response);
        }
        else {
            filterChain.doFilter(request, response);
        }
    }

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }
}
package com.example.filters;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.request.RequestPathInfo;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class AssetsFinderSlingHttpServletRequest extends SlingHttpServletRequestWrapper {

    protected RequestPathInfo requestPathInfo;

    public AssetsFinderSlingHttpServletRequest(SlingHttpServletRequest wrappedRequest) {
        super(wrappedRequest);
        WrappedRequestPathInfo wrappedRequestPathInfo = createWrappedRequestPathInfo();
        RequestPathInfo wrappedRequestInfo = (RequestPathInfo) Proxy.newProxyInstance(
                RequestPathInfo.class.getClassLoader(),
                new Class[] { RequestPathInfo.class},
                wrappedRequestPathInfo);
        requestPathInfo = wrappedRequestInfo;
    }

    public WrappedRequestPathInfo createWrappedRequestPathInfo() {
        return new WrappedRequestPathInfo();
    }

    @Override
    public RequestPathInfo getRequestPathInfo() {
        return requestPathInfo;
    }

    class WrappedRequestPathInfo implements InvocationHandler {

        private final static String DEFAULT_SUFFIX = "/content/dam/we-retail";

        private RequestPathInfo getOriginal() {
            return getSlingRequest().getRequestPathInfo();
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            switch (methodName) {
                case "getResourcePath":
                    return getResourcePath();
                case "getExtension":
                    return getExtension();
                case "getSelectorString":
                    return getSelectorString();
                case "getSelectors":
                    return getSelectors();
                case "getSuffix":
                    return getSuffix();
                case "getSuffixResource":
                    return getSuffixResource();
                default:
                    throw new UnsupportedOperationException("REQUESTPATHINFOWRAPPER >> NO IMPLEMENTATION FOR " + methodName);
            }
        }

        public String getResourcePath() {
            return getOriginal().getResourcePath();
        }

        public String getExtension() {
            return getOriginal().getExtension();
        }

        public String getSelectorString() {
            return getOriginal().getSelectorString();
        }

        public String[] getSelectors() {
            return getOriginal().getSelectors();
        }

        public String getSuffix() {
            return StringUtils.defaultIfBlank(getOriginal().getSuffix(), DEFAULT_SUFFIX);
        }

        public Resource getSuffixResource() {
            return getOriginal().getSuffixResource();
        }
    }
}

Summarizing, if you do not want to write custom code, then the only OOTB option is that authors will always set path manually. In other case customization is needed, you can choose which customization way will be the best from your perspective.

 

View solution in original post

3 Replies

Avatar

Community Advisor

Hi @samr2b1,

You should be able to achieve your goal by overlaying /libs/cq/gui/components/authoring/editors/clientlibs/core/js/assetController/image/ImageAssetPanel.js this file contains method that builds query that is sent to /bin/wcm/contentfinder/asset/view.html which returns list of assets that are later visible in side panel base on given criteria e.g. path to specific folder to DAM that can be set by author. Below steps has been done on AEM 6.5.12, but should be applicable for other AEM versions as well.

  1. Overlay /libs/cq/gui/components/authoring/editors/clientlibs/core/js/assetController/image/ImageAssetPanel.js, as a result you should have following structure under apps /apps/cq/gui/components/authoring/editors/clientlibs/core/js/assetController/image/ImageAssetPanel.js
    overaly-1.jpg
  2. Edit ImageAssetPanel.js under apps, and modify line 35 by setting dam path you would like to became new default path, below I have changed /content/dam into /content/dam/we-retail. This means authors will see by default assets stored under /content/dam/we-retail.
  3. Modify line 88, change searchPath = spath; to searchPath = spath || searchPath;

As a result ImageAssetPanel.js will look like this:

(function ($, ns, channel, window, undefined) {

    /**
     * Asset Controller for the extended image type
     *
     * All assets that are related to the notion of image
     *
     * @memberOf Granite.author.ui.assetFinder
     * @inner
     * @alias imageAssetController
     * @ignore
     * @type {Granite.author.ui.assetFinder~AssetController}
     */
    var self = {},
        name = 'Images';

    // make the loadAssets function more flexible
    self.searchRoot = '/content/dam/we-retail';
    // open assets in the admin view (to edit properties)
    self.viewInAdminRoot = '/assetdetails.html{+item}';

    var searchPath = self.searchRoot,
        imageServlet = '/bin/wcm/contentfinder/asset/view.html',
        itemResourceType = 'cq/gui/components/authoring/assetfinder/asset';

    /**
     Pre asset type switch hook
     */
    self.setUp = function () {};

    /**
     Post asset type switch hook
     */
    self.tearDown = function () {};

    /**
     * Loads extended image type resources
     *
     * @param query {String} search query
     * @param lowerLimit {Number} lower bound for paging
     * @param upperLimit {Number} upper bound for paging
     * @returns {jQuery.Promise}
     */
    self.loadAssets = function (query, lowerLimit, upperLimit) {
        var param = {
            '_dc': new Date().getTime(),  // cache killer
            'query': query.concat("order:\"-jcr:content/jcr:lastModified\" "), // sort by jcr:content/jcr:lastModified property
            'mimeType': 'image,application/x-ImageSet,application/x-SpinSet,application/x-MixedMediaSet,application/x-CarouselSet',
            'itemResourceType': itemResourceType, // single item rendering (cards)
            'limit': lowerLimit + ".." + upperLimit,
            '_charset_': 'utf-8'
        };

        return $.ajax({
            type: 'GET',
            dataType: 'html',
            url: Granite.HTTP.externalize(imageServlet) + searchPath,
            data: param
        });
    };

    /**
     * Set URL to image servlet
     * @param {String} imgServlet - URL to image servlet
     */
    self.setServlet = function (imgServlet) {
        imageServlet = imgServlet;
    };

    self.setSearchPath = function (spath) {
        searchPath = spath || searchPath;
    };

    self.setItemResourceType = function (rt) {
        itemResourceType = rt;
    };

    self.resetSearchPath = function () {
        searchPath = self.searchRoot;
    };

    // register as a asset tab
    ns.ui.assetFinder.register(name, self);

}(jQuery, Granite.author, jQuery(document), this));

Optional step:

You may also want to change path that can be set by author, to use the same location.

pathfield.jpg

To do **bleep** you have overaly /libs/wcm/core/content/editor/jcr:content/sidepanels/edit/items/tabs/items/assetsTab/items/filterPanel/items/search/items/searchpanel/items/imagepath, and change rootPath propoerty value.

pathfield-crx.jpg

Useful links:

In case you would like to set default DAM path dynamically, e.g. differetn path to different group of users or different, then most likely you will need prived those dynamic data e.g. via servlet to ImageAssetPanel.js

Please be aware tha changes descibed above are global changes, which means they will affect all pages edited on given AEM instance by any user.

Avatar

Level 2

Thank you Lukasz for your detailed explanation. This is really helpful. Other than this, is there a way I can achieve this without doing further customizations on the siderail? My siderail is already heavily customized and I am looking to see if I can achieve this with any OOTB feature without doing much of code change.

Avatar

Correct answer by
Community Advisor

@samr2b1 There is no other way to achieve your goal without customization. AEM OOTB is not providing any option to configure the default path, either specific or dedicated endpoint you could use. Of course it can be done many ways, my first post shows probably the easiest customization. Here is an alternative, backend only solution that is using using  Sling Filter.

package com.example.filters;

import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.engine.EngineConstants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.*;
import java.io.IOException;

@Component(service = Filter.class,
           property = {
                   EngineConstants.SLING_FILTER_SCOPE + "=" + EngineConstants.FILTER_SCOPE_REQUEST,
           })
public class AssetsFinderFilter implements Filter {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public void doFilter(final ServletRequest request, final ServletResponse response,
                         final FilterChain filterChain) throws IOException, ServletException {
        final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request;

        if (slingRequest.getRequestPathInfo().getResourcePath().startsWith("/bin/wcm/contentfinder/asset/view")) {
            filterChain.doFilter(new AssetsFinderSlingHttpServletRequest(slingRequest), response);
        }
        else {
            filterChain.doFilter(request, response);
        }
    }

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }
}
package com.example.filters;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.request.RequestPathInfo;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class AssetsFinderSlingHttpServletRequest extends SlingHttpServletRequestWrapper {

    protected RequestPathInfo requestPathInfo;

    public AssetsFinderSlingHttpServletRequest(SlingHttpServletRequest wrappedRequest) {
        super(wrappedRequest);
        WrappedRequestPathInfo wrappedRequestPathInfo = createWrappedRequestPathInfo();
        RequestPathInfo wrappedRequestInfo = (RequestPathInfo) Proxy.newProxyInstance(
                RequestPathInfo.class.getClassLoader(),
                new Class[] { RequestPathInfo.class},
                wrappedRequestPathInfo);
        requestPathInfo = wrappedRequestInfo;
    }

    public WrappedRequestPathInfo createWrappedRequestPathInfo() {
        return new WrappedRequestPathInfo();
    }

    @Override
    public RequestPathInfo getRequestPathInfo() {
        return requestPathInfo;
    }

    class WrappedRequestPathInfo implements InvocationHandler {

        private final static String DEFAULT_SUFFIX = "/content/dam/we-retail";

        private RequestPathInfo getOriginal() {
            return getSlingRequest().getRequestPathInfo();
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            switch (methodName) {
                case "getResourcePath":
                    return getResourcePath();
                case "getExtension":
                    return getExtension();
                case "getSelectorString":
                    return getSelectorString();
                case "getSelectors":
                    return getSelectors();
                case "getSuffix":
                    return getSuffix();
                case "getSuffixResource":
                    return getSuffixResource();
                default:
                    throw new UnsupportedOperationException("REQUESTPATHINFOWRAPPER >> NO IMPLEMENTATION FOR " + methodName);
            }
        }

        public String getResourcePath() {
            return getOriginal().getResourcePath();
        }

        public String getExtension() {
            return getOriginal().getExtension();
        }

        public String getSelectorString() {
            return getOriginal().getSelectorString();
        }

        public String[] getSelectors() {
            return getOriginal().getSelectors();
        }

        public String getSuffix() {
            return StringUtils.defaultIfBlank(getOriginal().getSuffix(), DEFAULT_SUFFIX);
        }

        public Resource getSuffixResource() {
            return getOriginal().getSuffixResource();
        }
    }
}

Summarizing, if you do not want to write custom code, then the only OOTB option is that authors will always set path manually. In other case customization is needed, you can choose which customization way will be the best from your perspective.