AEM 62 - Adding Dynamic Columns in Asset Reports

Goal


Give AEM admin the flexibility to add additional asset metadata columns in Reporting Console http://localhost:4502/mnt/overlay/dam/gui/content/reports/reportspage.html

The custom column configuration is stored in cookies, so the configuration is per report and per user

For more information on Assets Reporting check documentation

Thank you ACS Commons for the Generic Lists

For adding columns to Search Results check this post

For adding custom columns to Assets List View check this post

Demo | Package Install | Github


Configure Columns

            http://localhost:4502/etc/experience-aem/report-columns.html



Enable Columns



Columns in Report



Report Exported as CSV



Solution


1) Create the extension nt:folder /apps/eaem-asset-reports-dynamic-columns

2) Add the ACS Commons Generic List in nt:folder /apps/eaem-asset-reports-dynamic-columns/components, /apps/eaem-asset-reports-dynamic-columns/templates (available in Package Install if installing the extension)

3) Create sling:Folder /apps/eaem-asset-reports-dynamic-columns/dialog/columns and nt:unstructured /apps/eaem-asset-reports-dynamic-columns/dialog/columns/column with the following properties for creating column html

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/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"
    sling:resourceType="cq/gui/components/siteadmin/admin/pages/headers/deflt"
    class="type small-col"
    modalResourceType="granite/ui/components/foundation/form/checkbox"
    name="./columns"
    sort-selector=".label .eaemCol"
    text="eaemCol"
    title="eaemCol"/>


4) Create nt:unstructured /apps/eaem-asset-reports-dynamic-columns/dialog/modal with following properties, for showing the column names in Configure Columns modal (set in property columnspath)

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/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"
    sling:resourceType="dam/gui/coral/components/admin/reports/customcolumnmodal"
    columnspath="/apps/eaem-asset-reports-dynamic-columns/dialog/columns"/>

5) Create cq:ClientLibraryFolder /apps/eaem-asset-reports-dynamic-columns/clientlib with categories cq.dam.admin.reports.customcolumns.coral and dependencies underscore

6) Create nt:file /apps/eaem-asset-reports-dynamic-columns/clientlib/js.txt with the following content

                  dynamic-report-columns.js

7) Create nt:file /apps/eaem-asset-reports-dynamic-columns/clientlib/dynamic-report-columns.js with the following code

(function ($, $document) {
    var VIEW_SETTINGS_COLUMN_CONFIG = {},
        FOUNDATION_CONTENT_LOADED = "foundation-contentloaded",
        REPORTS_FORM = "#customcolumnsForm",
        DAM_ADMIN_REPORTS_PAGE = ".cq-damadmin-admin-reportspage",
        METADATA_COL_MAPPING = "data-eaem-col-mapping",
        EXPORT_BUTTON = ".dam-admin-reports-export",
        QB = "/bin/querybuilder.json?",
        EXPORT_URL = "/apps/eaem-asset-reports-dynamic-columns/dialog/export.html",
        REPORT_RESULT_URL = "/mnt/overlay/dam/gui/content/reports/reportsresult.html",
        EAEM_COL = "eaemCol",
        FORM_FIELD_WRAPPER = ".coral-Form-fieldwrapper",
        COOKIE_REPORT_LIST_VIEW_COLUMNS = "eaem.report.listview.columns",
        REPORTS_CONFIGURE_COLUMNS_DIALOG = "reports-configure-columns-dialog",
        COLUMN_URL = "/apps/eaem-asset-reports-dynamic-columns/dialog/columns/column.html",
        COLUMNS_MODAL = "/apps/eaem-asset-reports-dynamic-columns/dialog/modal.html",
        DYNAMIC_COLS_CONFIG_URL = "/etc/experience-aem/report-columns/jcr:content/list.infinity.json",
        MAIN_COLUMN = "dam/gui/coral/components/admin/reports/columns/main",
        MAIN_COLUMN_WIDTH = 22,
        COLUMN_CACHE = [], COLUMN_WIDTH = "0%", CELL_DATA = [], searchUrl;

    if (typeof EAEM == "undefined") {
        EAEM = { REPORT : {} };
    }

    EAEM.REPORT.storeEnabledColumns = storeEnabledColumns;

    loadColumnsConfiguration();

    $document.on(FOUNDATION_CONTENT_LOADED, function(event){
        _.defer(function(){
            handleContentLoad(event);
        });
    });

    $document.on("submit", "form.foundation-form", function(e) {
        searchUrl = getSearchUrl();
    });

    function loadColumnsConfiguration() {
        if(!_.isEmpty(VIEW_SETTINGS_COLUMN_CONFIG)){
            return;
        }

        $.ajax(DYNAMIC_COLS_CONFIG_URL).done(function(data){
            if(_.isEmpty(data)){
                return;
            }

            _.each(data, function(obj, key){
                if(key.indexOf("item") !== 0){
                    return;
                }

                VIEW_SETTINGS_COLUMN_CONFIG[obj.value] = obj["jcr:title"];
            });
        });
    }

    function handleContentLoad(event){
        var target = event.target;

        if(isConfigureColumnsDialog(target)){
            addDynamicColumnsInModal();
        }else if(isReportsPage(event)){
            handleReportsPage();
        }
    }

    function handleReportsPage(){
        var $reportsPage = $(DAM_ADMIN_REPORTS_PAGE);

        if(_.isEmpty($reportsPage)){
            return;
        }

        var enabledColumns = getEnabledColumnsObj()[getReportPath()];

        if(_.isEmpty(enabledColumns)){
            return;
        }

        handleHeaders();
    }

    function handleHeaders(){
        var $reportsPage = $(DAM_ADMIN_REPORTS_PAGE),
            $hContainers = $reportsPage.find("header > .label"),
            $aContainers = $reportsPage.find("article");

        if(customHeadersAdded($hContainers)){
            handlePagination($aContainers);
            return;
        }

        handleExport();

        var enabledColumns = getEnabledColumnsObj()[getReportPath()];

        $.ajax(COLUMN_URL).done(function(colHtml) {
            _.each(enabledColumns, function(colMetaPath){
                addColumnHeaders($hContainers, $aContainers, colHtml, colMetaPath);
            });

            if(!searchUrl){
                return;
            }

            $.get(searchUrl).done(function(data){
                if(_.isEmpty(data.hits)){
                    return;
                }

                CELL_DATA = data.hits;

                addCellValues();
            });
        });
    }

    function handlePagination($aContainers){
        var $labelContainers = $aContainers.find(".label"),
            $lContainer;

        $labelContainers.each(function(index, aContainer){
            $lContainer = $(aContainer);

            if(customHeadersAdded($lContainer)){
                return;
            }

            _.each(COLUMN_CACHE, function(cellHtml){
                $lContainer.append(cellHtml);
            })
        });

        fixCellWidths($labelContainers, COLUMN_WIDTH);

        addCellValues();
    }

    function addColumnHeaders($hContainers, $aContainers, colHtml, colMetaPath){
        var $columnHeader, $labelContainers = $aContainers.find(".label"),
            cellHtml;

        $columnHeader = $(colHtml).appendTo($hContainers);

        $columnHeader.attr(METADATA_COL_MAPPING, colMetaPath)
                        .html(VIEW_SETTINGS_COLUMN_CONFIG[colMetaPath]);

        COLUMN_WIDTH = fixHeaderWidths($hContainers);

        if(_.isEmpty($labelContainers)){
            return;
        }

        cellHtml = getCellHtml(colMetaPath, "");

        $labelContainers.append(cellHtml);

        fixCellWidths($labelContainers, COLUMN_WIDTH);

        COLUMN_CACHE.push(cellHtml);
    }

    function addCellValues( ){
        var $reportsPage = $(DAM_ADMIN_REPORTS_PAGE),
            $aContainers = $reportsPage.find("article"),
            $aParent = $aContainers.parent(),
            $article, $cell,
            enabledColumns = getEnabledColumnsObj()[getReportPath()];

        _.each(CELL_DATA, function(hit){
            $article = $aParent.find("article[data-path='" + hit["jcr:path"] + "']");

            if(_.isEmpty($article)){
                return;
            }

            _.each(enabledColumns, function(colMetaPath){
                $cell = $article.find("[" + METADATA_COL_MAPPING + "='" + colMetaPath + "']");
                $cell.html(nestedPluck(hit, colMetaPath));
            })
        })
    }

    function fixCellWidths($lContainers, colWidth){
        $lContainers.children("div").removeClass("small-col large-col")
            .css("width", MAIN_COLUMN_WIDTH + "%");

        $lContainers.children("p").removeClass("small-col large-col")
            .css("width", colWidth).css("float", "left");
    }

    function getCellHtml(colMapping, colText){
        return "<p "
            + METADATA_COL_MAPPING + "='" + colMapping + "'>"
                + colText +
            "</p>";
    }

    function fixHeaderWidths($hContainer){
        var $hDivs = $hContainer.children("div"), $hDiv,
            colWidth = ((100 - MAIN_COLUMN_WIDTH) / ($hDivs.length - 1)) + "%";

        $hDivs.each(function(index, hDiv){
            $hDiv = $(hDiv);

            $hDiv.removeClass("small-col large-col");

            if( $hDiv.data("itemresourcetype") === MAIN_COLUMN){
                $hDiv.css("width", MAIN_COLUMN_WIDTH + "%");
                return;
            }

            $hDiv.css("width", colWidth);
        });

        return colWidth;
    }

    function customHeadersAdded($hContainers){
        return !_.isEmpty($hContainers.find("[" + METADATA_COL_MAPPING + "]"));
    }

    function isReportsPage(event){
        var target = event.target;

        if(!target){
            return false;
        }

        var $target = (!target.$ ? $(target) : target.$);

        return (!_.isEmpty($target.find(DAM_ADMIN_REPORTS_PAGE))
                    || $target.hasClass(DAM_ADMIN_REPORTS_PAGE.substr(1)));
    }

    function isConfigureColumnsDialog(target){
        if(!target || (target.tagName !== "CORAL-DIALOG")){
            return false;
        }

        var $target = (!target.$ ? $(target) : target.$);

        return $target.hasClass(REPORTS_CONFIGURE_COLUMNS_DIALOG);
    }

    function addDynamicColumnsInModal(){
        var url = COLUMNS_MODAL + getReportPath();

        $.ajax(url).done(handler);

        function handler(html){
            if(_.isEmpty(html)){
                return;
            }

            var $html, $column, $input, $form = $(REPORTS_FORM),
                enabledColumns = getEnabledColumnsObj()[getReportPath()];

            _.each(VIEW_SETTINGS_COLUMN_CONFIG, function(colTitle, colPath){
                $html = $(html);

                $input = $html.find("input[title='" + EAEM_COL + "']")
                            .attr(METADATA_COL_MAPPING, colPath)
                            .val("");

                if(contains(enabledColumns, colPath)){
                    $input.attr("checked", "checked");
                }

                $input.attr("onchange", "EAEM.REPORT.storeEnabledColumns()");

                $column = $input.closest(FORM_FIELD_WRAPPER);

                $column.find(".coral-Checkbox-description").html(colTitle);

                $form.append($column[0].outerHTML);
            });

            styleDialog();
        }
    }

    function handleExport(){
        $document.off("click", EXPORT_BUTTON).fipo("tap", "click", EXPORT_BUTTON, handler);

        function handler(){
            var currReportPath = getReportPath();

            if(currReportPath.indexOf("default") !== -1) {
                return;
            }

            var $form = $(".dam-admin-reports").find("[report-path='" + currReportPath + "']").find('form'),
                $sliderrange = $form.find(".sliderrange");

            setSliderValue($sliderrange);

            var url = Granite.HTTP.externalize(EXPORT_URL + currReportPath + "?" + $form.serialize());

            var downloadURL = function (url) {
                $('<iframe>', {
                    id: 'idown',
                    src: url
                }).hide().appendTo('body');
            };

            downloadURL(url);
        }

        function setSliderValue($form){
            var tickValues = $form.find(".coral-Slider").attr("data-tickvalues").split(","),
                lowerInd = $form.find(".coral-Slider.lower").attr('value'),
                upperInd = $form.find(".coral-Slider.upper").attr('value'),
                order = $form.find(".coral-Slider").attr("data-order"), tmp;

            if (order && order === "increasing") {
                if (lowerInd > upperInd) {
                    tmp = lowerInd;
                    lowerInd = upperInd;
                    upperInd = tmp;
                }
            } else if (lowerInd < upperInd) {
                tmp = lowerInd;
                lowerInd = upperInd;
                upperInd = tmp;
            }

            $form.find(".coral-Slider.lower .lowervalue").val(tickValues[lowerInd]);
            $form.find(".coral-Slider.upper .uppervalue").val(tickValues[upperInd]);
        }
    }

    function getReportPath(){
        return $('input[name=dam-asset-report]:checked').attr("report-path");
    }

    function getEnabledColumnsObj(){
        var cookieValue = getCookie(COOKIE_REPORT_LIST_VIEW_COLUMNS);

        if(!cookieValue){
            cookieValue = {};
        }else{
            cookieValue = JSON.parse(decodeURIComponent(cookieValue));
        }

        return cookieValue;
    }

    function storeEnabledColumns(){
        var $input, columns = [], colMapping;

        $(REPORTS_FORM).find("input:checked").each(function(index, input){
            $input = $(input);

            colMapping = $input.attr(METADATA_COL_MAPPING);

            if(_.isEmpty(colMapping)){
                return;
            }

            columns.push(colMapping);
        });

        var cookieObj = getEnabledColumnsObj();

        cookieObj[getReportPath()] = columns;

        addCookie(COOKIE_REPORT_LIST_VIEW_COLUMNS, JSON.stringify(cookieObj));
    }

    function getCookie(cookieName){
        var cookieValue = "";

        if(_.isEmpty(cookieName)){
            return;
        }

        var cookies = document.cookie.split(";"), tokens;

        _.each(cookies, function(cookie){
            tokens = cookie.split("=");

            if(tokens[0].trim() !== cookieName){
                return;
            }

            cookieValue = tokens[1].trim();
        });

        return cookieValue;
    }

    function addCookie(cookieName, value){
        if(_.isEmpty(cookieName)){
            return;
        }

        $.cookie(cookieName, value, { expires: 365, path: "/" } );
    }

    function contains(arrOrObj, key){
        var doesIt = false;

        if(_.isEmpty(arrOrObj) || _.isEmpty(key)){
            return doesIt;
        }

        if(_.isArray(arrOrObj)){
            doesIt = (arrOrObj.indexOf(key) !== -1);
        }

        return doesIt;
    }

    function styleDialog(){
        var $form = $(REPORTS_FORM);

        $form.css("max-height", "21.5rem").css("overflow-y", "auto");
    }

    function getSearchUrl(){
        var $form = $("form[action='" + REPORT_RESULT_URL + getReportPath() + "']");
        return QB + $form.serialize() + "&p.nodedepth=2&p.hits=full";
    }

    function nestedPluck(object, key) {
        if (!_.isObject(object) || _.isEmpty(object) || _.isEmpty(key)) {
            return [];
        }

        if (key.indexOf("/") === -1) {
            return object[key];
        }

        var nestedKeys = _.reject(key.split("/"), function(token) {
            return token.trim() === "";
        }), nestedObjectOrValue = object;

        _.each(nestedKeys, function(nKey) {
            if(_.isUndefined(nestedObjectOrValue)){
                return;
            }

            if(_.isUndefined(nestedObjectOrValue[nKey])){
                nestedObjectOrValue = undefined;
                return;
            }

            nestedObjectOrValue = nestedObjectOrValue[nKey];
        });

        return nestedObjectOrValue;
    }
})(jQuery, jQuery(document));

8) For exporting the report with configured custom columns, create nt:unstructured /apps/eaem-asset-reports-dynamic-columns/dialog/export with property sling:resourceType set to /apps/eaem-asset-reports-dynamic-columns/export

9) Create /apps/eaem-asset-reports-dynamic-columns/export/export.jsp with the following code

<%@ page import="java.io.PrintWriter" %>
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.StringWriter" %>
<%@ page import="org.apache.commons.lang3.StringUtils" %>
<%@ page import="org.apache.sling.api.SlingHttpServletRequest" %>
<%@ page import="org.apache.sling.commons.json.JSONObject" %>
<%@ page import="org.slf4j.Logger" %>
<%@ page import="org.slf4j.LoggerFactory" %>
<%@ page import="java.net.URLDecoder" %>
<%@ page import="org.apache.sling.commons.json.JSONArray" %>
<%@ page import="org.apache.sling.api.resource.ResourceResolver" %>
<%@ page import="org.apache.sling.api.resource.Resource" %>
<%@ page import="java.util.*" %>
<%@ page import="org.apache.sling.api.resource.ValueMap" %>
<%@ page import="javax.jcr.Session" %>
<%@taglib prefix="sling" uri="http://sling.apache.org/taglibs/sling/1.2"%>
<%@taglib prefix="cq" uri="http://www.day.com/taglibs/cq/1.0"%>

<cq:defineObjects />

<%!
    final Logger log = LoggerFactory.getLogger(this.getClass());
    final String EXPORT_JSP = "/libs/dam/gui/content/reports/export";
    final String COLUMNS_CONFIG = "/etc/experience-aem/report-columns/jcr:content/list";

    static String COOKIE_REPORT_LIST_VIEW_COLUMNS = "eaem.report.listview.columns";
    static String PATH_HEADER = "\"PATH\"";
%>

<%
    HttpServletResponseWrapper responseWrapper = getWrapper(response);

    request.getRequestDispatcher(EXPORT_JSP).include(request, responseWrapper);

    String csvContent = responseWrapper.toString();

    csvContent = addCustomColumns(slingRequest, csvContent);

    PrintWriter o = response.getWriter();

    o.write(csvContent);

    o.flush();
%>

<%!
    private String addCustomColumns(SlingHttpServletRequest sRequest, String csvContent){
        Cookie cookie = sRequest.getCookie(COOKIE_REPORT_LIST_VIEW_COLUMNS);

        if(cookie == null){
            return csvContent;
        }

        String enabledColumns = cookie.getValue();

        if(StringUtils.isEmpty(enabledColumns)){
            return csvContent;
        }

        String reportSelected = sRequest.getRequestPathInfo().getSuffix();

        try{
            JSONObject enabled = new JSONObject(URLDecoder.decode(enabledColumns, "UTF-8"));

            if(!enabled.has(reportSelected)){
                return csvContent;
            }

            JSONArray reportCustomColumns = enabled.getJSONArray(reportSelected);
            String[] lines = csvContent.split("\r\n");

            StringWriter customCsvContent = new StringWriter();

            if(lines.length == 0){
                return csvContent;
            }

            Map<String, String> colConfig = getCustomColumnsConfig(sRequest.getResourceResolver());

            addHeaderLine(colConfig, reportCustomColumns, customCsvContent, lines[0]);

            addAssetLines(sRequest, reportCustomColumns, customCsvContent, lines);

            csvContent = customCsvContent.toString();
        }catch(Exception e){
            log.warn("Error adding columns", e);
        }

        return csvContent;
    }

    private void addAssetLines(SlingHttpServletRequest sRequest, JSONArray reportCustomColumns,
                               StringWriter gieCsvContent, String[] csvLines) throws Exception {
        if(csvLines.length == 1){
            return;
        }

        int pathIndex = -1;

        String[] headers = csvLines[0].split(",");

        for(int i = 0; i < headers.length; i++){
            if(headers[i].equalsIgnoreCase(PATH_HEADER)){
                pathIndex = i;
            }
        }

        if(pathIndex == -1){
            return;
        }

        String line = null, assetPath = null, colMapPath = null, colMetaName;
        ValueMap assetMetadata = null;

        ResourceResolver resolver = sRequest.getResourceResolver();
        Session session = resolver.adaptTo(Session.class);

        for(int l = 1; l < csvLines.length; l++){
            line = csvLines[l];

            try{
                assetPath = line.split(",")[pathIndex];

                assetPath = assetPath.replaceAll("\"", "");

                assetPath = assetPath + "/jcr:content/metadata";

                if(!session.nodeExists(assetPath)){
                    continue;
                }

                assetMetadata = resolver.getResource(assetPath).getValueMap();

                for(int index = 0, len = reportCustomColumns.length(); index < len; index++){
                    colMapPath = reportCustomColumns.getString(index);
                    colMetaName = colMapPath.substring(colMapPath.lastIndexOf("/") + 1);
                    line = line + "," + assetMetadata.get(colMetaName, "");
                }
            }catch(Exception e){
            }

            gieCsvContent.append(line);

            gieCsvContent.append("\r\n");
        }
    }

    private Map<String, String> getCustomColumnsConfig(ResourceResolver resolver){
        Resource mlResource = resolver.getResource(COLUMNS_CONFIG);
        Map<String, String> colConfig = new HashMap<String, String>();

        if(mlResource == null){
            return colConfig;
        }

        try{
            Iterator<Resource> managedList = mlResource.listChildren();
            Resource resource;
            ValueMap vm;

            while (managedList.hasNext()) {
                resource = managedList.next();
                vm = resource.getValueMap();

                colConfig.put(vm.get("value", ""), vm.get("jcr:title", ""));
            }
        }catch(Exception e){
            log.warn("Error reading managed list");
        }

        return colConfig;
    }


    private void addHeaderLine(Map<String, String> colConfig, JSONArray customColumns,
                                       StringWriter customCsvContent, String headerLine) throws Exception{
        String colMapPath  = null;

        if(headerLine.indexOf("\"\"") != -1){
            headerLine = headerLine.substring(0, headerLine.indexOf("\"\"") - 1);
        }

        for(int index = 0, len = customColumns.length(); index < len; index++){
            colMapPath = customColumns.getString(index);
            headerLine = headerLine + ",\"" + colConfig.get(colMapPath) + "\"";
        }

        customCsvContent.append(headerLine).append("\r\n");;
    }

    private static HttpServletResponseWrapper getWrapper(HttpServletResponse response){
        return new HttpServletResponseWrapper(response) {
            public final StringWriter sw = new StringWriter();

            @Override
            public PrintWriter getWriter() throws IOException {
                return new PrintWriter(sw);

            }

            @Override
            public String toString() {
                return sw.toString();
            }
        };
    }
%>

1 comment:

  1. This comment has been removed by a blog administrator.

    ReplyDelete