AEM CQ 56 - Add New Column to SiteAdmin Search Panel

Goal


Add a new column to SiteAdmin Search Panel. Here the Status column we add, shows the scheduled activations/deactivations of a page. Package Install available for download

For adding a column to damadmin search panel check this post




Related


1) Read this post on how to add a column to siteadmin grid

2) Check this post for modifying a column in siteadmin search panel grid


Solution



It's a two step process, first register servlet to get the page scheduled tasks as json, and second, add necessary UI changes


Deploy Servlet



1) Create and deploy servlet apps.mysample.searchpanelcolumn.GetScheduledActions as OSGI bundle; to get the scheduled activations/deactivations of page(s) as json. Add the following code..

package apps.mysample.searchpanelcolumn;

import com.day.cq.workflow.exec.Workflow;
import com.day.cq.workflow.metadata.MetaDataMap;
import com.day.cq.workflow.status.WorkflowStatus;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.commons.json.io.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;

@SlingServlet(
        paths="/bin/mycomponents/schtasks",
        methods = "GET",
        metatype = false,
        label = "Scheduled Activation Instances"
)
public class GetScheduledActions extends SlingAllMethodsServlet {
    private static final Logger log = LoggerFactory.getLogger(GetScheduledActions.class);
    private static SimpleDateFormat FORMATTER = new SimpleDateFormat("EEE, dd MMM, yyyy HH:mm");

    private static final String ACTIVATE_MODEL = "/etc/workflow/models/scheduled_activation/jcr:content/model";
    private static final String DEACTIVATE_MODEL = "/etc/workflow/models/scheduled_deactivation/jcr:content/model";

    @Override
    protected void doGet(final SlingHttpServletRequest request, final SlingHttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("application/json");
        response.setCharacterEncoding("utf-8");

        JSONWriter jw = new JSONWriter(response.getWriter());
        String pageStr = request.getParameter("path");

        try{
            jw.object();

            if(StringUtils.isEmpty(pageStr)){
                jw.key("error").value("page input required");
                jw.endObject();
                return;
            }

            String type = request.getParameter("type");

            if(StringUtils.isEmpty(type)){
                type = "ACTIVATE";
            }

            pageStr = pageStr.trim();
            String[] pages = pageStr.split(",");

            ResourceResolver resolver = request.getResourceResolver();
            Resource resource = null;

            WorkflowStatus wStatus = null;
            List<Workflow> workflows = null;

            Map<String, List<Map<String, String>>> retMap = new HashMap<String, List<Map<String, String>>>();
            Map<String, String> map = null;
            MetaDataMap mdMap = null;

            String absTime = null, id = null, version = null;
            List<Map<String, String>> list = null;

            for(String page: pages){
                if(StringUtils.isEmpty(page)){
                    continue;
                }

                resource = resolver.getResource(page);

                if(resource == null){
                    continue;
                }

                wStatus = resource.adaptTo(WorkflowStatus.class);
                workflows = wStatus.getWorkflows(false);

                if(CollectionUtils.isEmpty(workflows)){
                    continue;
                }

                for (Workflow w: workflows) {
                    id = w.getWorkflowModel().getId();

                    if(type.equals("ACTIVATE") && !id.equals(ACTIVATE_MODEL)){
                        continue;
                    }else if(type.equals("DEACTIVATE") && !id.equals(DEACTIVATE_MODEL)){
                        continue;
                    }

                    list = retMap.get(page);

                    if(list == null){
                        list = new ArrayList<Map<String, String>>();
                        retMap.put(page, list);
                    }

                    map = new HashMap<String, String>();
                    list.add(map);

                    mdMap = w.getMetaDataMap();
                    absTime = mdMap.get("absoluteTime", String.class);

                    map.put("id", w.getId());
                    map.put("name", resource.getChild("jcr:content").adaptTo(ValueMap.class).get("jcr:title", String.class));
                    map.put("st",FORMATTER.format(w.getTimeStarted().getTime()));
                    map.put("ini", w.getInitiator());
                    map.put("type", id.equals(ACTIVATE_MODEL) ? "ACTIVATE" : "DEACTIVATE");
                    map.put("dt", FORMATTER.format(Long.parseLong(absTime)));
                }
            }

            String path = null;
            Iterator<Map<String, String>> itr = null;

            jw.key("data").array();

            for(Map.Entry<String, List<Map<String, String>>> entry : retMap.entrySet()){
                list = entry.getValue();
                path = entry.getKey();

                itr = list.iterator();

                while(itr.hasNext()){
                    jw.object();
                    jw.key("path").value(path);

                    for(Map.Entry<String, String> mEntry : itr.next().entrySet()){
                        jw.key(mEntry.getKey()).value(mEntry.getValue());
                    }

                    jw.endObject();
                }
            }

            jw.endArray();
            jw.endObject();
        }catch(Exception e){
            log.error("Error getting schedule activation instances",e);
            throw new ServletException(e);
        }
    }
}

2) Login to CRXDE Lite, create folders /apps/searchpanelstatus/apps/searchpanelstatus/install

3) Deploy bundle with apps.mysample.searchpanelcolumn.GetScheduledActions to /apps/searchpanelstatus/install


UI Changes



1) Create folder (nt:folder) /apps/searchpanelstatus/clientlibs

2) Create folder /apps/searchpanelstatus/clientlibs/myclientlib of type cq:ClientLibraryFolder with property categories of type String set to cq.widgets

3) Add file of type nt:file /apps/searchpanelstatus/clientlibs/myclientlib/js.txt with statement

                                   addStatusColumn.js

4) Add file of type nt:file /apps/searchpanelstatus/clientlibs/myclientlib/addStatusColumn.js with following code...

CQ.Ext.ns("MyClientLib");

MyClientLib.SearchPanel = {
    addStatusColumn: function(){
        CQ.wcm.SiteAdminSearchPanel.COLUMNS["status"] =  {
            "header":CQ.I18n.getMessage("Status"),
            "id":"status",
            "dataIndex":"xxxxx", //some undefined, to workaround grid weird layout issue
            "renderer": function(val, meta, rec) {
                if(!rec.data.scheduledTasks){
                    return "";
                }

                var qtip = "<table class='qtip-table'><tr><th>" + CQ.I18n.getMessage("Task")
                                + "</th><th>" + CQ.I18n.getMessage("Scheduled") + "</th><th>";

                CQ.Ext.each(rec.data.scheduledTasks, function(t){
                    var iconCls = (t.type == 'ACTIVATE') ? "status status-scheduledtask-activation" :
                                        "status status-scheduledtask-deactivation";

                    qtip = qtip + "<tr><td class='" + iconCls + "'></td><td>"
                                    + t.dt + " (" + t.ini + ")</td><td>";
                });

                qtip = qtip + "</table>";

                return "<span class=\"status status-scheduledtask\" ext:qtip=\"" + qtip + "\"> </span>";
            }
        };

        MyClientLib.SiteAdminSearchPanel = CQ.Ext.extend(CQ.wcm.SiteAdminSearchPanel, {
            constructor: function(config) {
                if (config.columns) {
                    config.columns.push({
                        "xtype" : "gridcolumn",
                        "usePredefined": "status"
                    });
                }
                MyClientLib.SiteAdminSearchPanel.superclass.constructor.call(this, config);
            }
        });

        CQ.Ext.reg("siteadminsearchpanel", MyClientLib.SiteAdminSearchPanel);
    },

    addSchdTasksInStore: function(grid){
        var store = grid.getStore();

        store.on('load', function(s, recs){
            var pages = "";
            var updateRecs = {};

            CQ.Ext.each(recs, function(r){
                pages = pages + r.id + ",";
                updateRecs[r.id] = r;
            });

            if(!pages){
                return;
            }

            pages = pages.substr(0, pages.lastIndexOf(","));

            $.ajax({
                url: '/bin/mycomponents/schtasks',
                dataType: "json",
                type: 'GET',
                async: false,
                data: { "path" : pages, "type" : "ALL" },
                success: function(data){
                    if(!data || !data["data"]){
                        return;
                    }

                    data = data["data"];
                    var rec;

                    CQ.Ext.each(data, function(d){
                        rec = updateRecs[d["path"]];

                        if(!rec.data.scheduledTasks){
                            rec.data.scheduledTasks = [];
                        }

                        rec.data.scheduledTasks.push(d);
                    });
                }
            });

            grid.getView().refresh();
        });
    }
};

(function(){
    MyClientLib.SearchPanel.addStatusColumn();

    if(window.location.pathname == "/siteadmin"){
        var SA_INTERVAL = setInterval(function(){
            var grid = CQ.Ext.getCmp("cq-siteadminsearchpanel-grid");

            if(grid && (grid.rendered == true)){
                clearInterval(SA_INTERVAL);
                MyClientLib.SearchPanel.addSchdTasksInStore(grid);
            }
        }, 250);

    }
})();


6 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. Interesting way to handle this without overlaying. I am not an expert on ExtJS and would like to know on how do we modify the storeReaderFields (/libs/wcm/core/content/damadmin/grid/assets) without overlaying?

    ReplyDelete
  3. Interesting way to handle this without overlaying. I am not an expert on ExtJS and would like to know on how do we modify the storeReaderFields (/libs/wcm/core/content/damadmin/grid/assets) without overlaying?

    ReplyDelete
    Replies
    1. hi, its similar... for dam you'll work on the dam admin grid CQ.Ext.getCmp("cq-damadmin-grid")

      Delete
  4. This comment has been removed by the author.

    ReplyDelete
  5. How to handle the damadmin Search panel grid to add columns to grid? Both damadmin and siteadmin search panels sharing the same "cq-siteadminsearchpanel-grid", I tried in the same way mentioned in this post, but "config.columns" is null for damadmin search . So I tried to grid.getColumnModel(), added columns using columns.push and those are added to "cq-siteadminsearchpanel-grid-hcols-menu" but not showing in the grid row.

    ReplyDelete