Quantcast
Channel: Experiencing Adobe Experience Manager (AEM, CQ)
Viewing all articles
Browse latest Browse all 525

AEM CQ 56 - New (Tag Tree) Tab in Sidekick

$
0
0

Goal


This post is on adding a new tab to the Sidekick. Here we extend Sidekick and add a new tab showing CQ Tag Tree. To add new tags or remove tags for a page, multiple clicks are involved in opening Sidekick -> Page Tab -> Page Properties -> Basic -> Tags

If adding and removing tags is a common use-case in your project, adding the tag tree in a sidekick tab could be useful and saves some clicks. The new tab loads a checkbox tree; to add a tag check the box and to remove uncheck. If a tag is checked, parent tags are shown in bold to identify the tags checked, deep down in the tree. Source code, demo video andpackage installare available for download



Prerequisites


If you are new to CQ

1) Read this post on how to create a sample page component

2) Read this post on how to setup your IDE and create an OSGI component

Tags Servlet


First, we need a servlet (deployed as OSGI component ) to feed the Tag tree ( explained in next section ) with tags json data. Create servlet GetTagsCheckedForPage and add the following code

package apps.mysample.sidekick;

import com.day.cq.commons.LabeledResource;
import com.day.cq.tagging.JcrTagManagerFactory;
import com.day.cq.tagging.Tag;
import com.day.cq.tagging.TagManager;
import com.day.text.Text;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang3.StringUtils;
import org.apache.felix.scr.annotations.Reference;
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.servlets.SlingAllMethodsServlet;
import org.apache.sling.commons.json.io.JSONWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.jcr.Node;
import javax.jcr.Session;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

@SlingServlet (
paths="/bin/mycomponents/sidekick/tags",
methods = "GET",
metatype = true,
label = "Tags Servlet"
)
public class GetTagsCheckedForPage extends SlingAllMethodsServlet {
private static final Logger LOG = LoggerFactory.getLogger(GetTagsCheckedForPage.class);

@Reference
JcrTagManagerFactory tmf;

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

String pagePath = request.getParameter("page");
String tagPath = request.getParameter("tag");

try{
ResourceResolver resolver = request.getResourceResolver();
Session session = resolver.adaptTo(Session.class);
TagManager tMgr = tmf.getTagManager(session);

JSONWriter jw = new JSONWriter(response.getWriter());

if(StringUtils.isEmpty(pagePath) || StringUtils.isEmpty(tagPath)){
jw.object();
jw.key("error").value("required parameters page and tag missing");
jw.endObject();
return;
}

Resource resource = resolver.getResource(pagePath + "/jcr:content");

if(resource == null){
jw.object();
jw.key("error").value("resource " + pagePath + " not found");
jw.endObject();
return;
}

Resource parentTag = resolver.getResource(tagPath);

if(parentTag == null){
jw.object();
jw.key("error").value("tag " + parentTag + " not found");
jw.endObject();
return;
}

Tag[] pageTags = tMgr.getTags(resource);
List<String> pageTagsList = new ArrayList<String>();

for(Tag t : pageTags){
pageTagsList.add(t.getPath());
}

Iterator<Resource> itr = parentTag.listChildren();

Resource tag = null;
Node node = null;
String parentPath = null, cls = null;

jw.array();

while(itr.hasNext()){
tag = itr.next();

if(!tag.getResourceType().equals("cq/tagging/components/tag")){
continue;
}

parentPath = tag.getParent().getPath();

jw.object();

jw.key("name").value(tag.getPath().substring(1));

cls = parentPath.equals("/etc/tags") || parentPath.equals("/etc") ? "folder" : "tag x-tree-node-icon";

for(Tag t : pageTags){
if(t.getPath().indexOf(tag.getPath()) == 0){
//Make the breadcrumb trail bold, the css class x-menu-item-default is defined in CQ as
//.x-menu-item-default SPAN { font-weight:bold; }
cls = "x-menu-item-default " + cls;
break;
}
}

jw.key("cls").value(cls);

node = tag.adaptTo(Node.class);

if(node.hasProperty("jcr:title")){
jw.key("text").value(node.getProperty("jcr:title").getString());
}else{
jw.key("text").value(node.getName());
}

jw.key("checked").value(pageTagsList.contains(tag.getPath()));

jw.endObject();
}

jw.endArray();
}catch(Exception e){
LOG.error("Error getting tags",e);
throw new ServletException(e);
}
}
}


UI Extension


1) Next step is adding a UI extension, for generating Tag tree and adding it in a new Sidekick tab.

2) Using overlay architecture of CQ, overlay the file /libs/cq/ui/widgets/source/widgets/wcm/ContentFinder.js by creating /apps/cq/ui/widgets/source/widgets/wcm/ContentFinder.js ( If not ContentFinder.js,  developer can opt overlaying Sidekick.js etc, any code added in the overlay file should be executed after Sidekick is loaded on the page )

3) In /apps/cq/ui/widgets/source/widgets/wcm/ContentFinder.js add the following code

CQ.Ext.ns("MyClientLib");

MyClientLib.Sidekick = {
SK_TAB_PANEL: "cq-sk-tabpanel",
TAGADMIN_TREE_ID: "myclientlib-cq-tagadmin-tree",
TAGS : "TAGS",

addTagsPanel: function(sk){
var CONTEXTS = CQ.wcm.Sidekick.CONTEXTS;

if( ($.inArray(this.TAGS, CONTEXTS) != -1) || sk.panels[this.TAGS]){
return;
}

CONTEXTS.push(this.TAGS);

var tabPanel = sk.findById(this.SK_TAB_PANEL);

var treeLoader = new CQ.Ext.tree.TreeLoader({
dataUrl:"/bin/mycomponents/sidekick/tags",
requestMethod: "GET",
baseParams: {
page: sk.getPath()
},
listeners: {
beforeload: function(tl, node){
this.baseParams.tag = "/" + node.attributes.name;
}
}
});

var treeRoot = new CQ.Ext.tree.AsyncTreeNode({
name: "etc/tags",
text: CQ.I18n.getMessage("Tags"),
expanded: true
});

var tree = new CQ.Ext.tree.TreePanel({
"id": this.TAGADMIN_TREE_ID,
"margins":"5 0 5 5",
"width": 200,
"animate":true,
"loader": treeLoader,
"root": treeRoot,
"rootVisible": false,
"tbar": [{
"iconCls":"cq-siteadmin-refresh",
"handler":function(){
CQ.Ext.getCmp(MyClientLib.Sidekick.TAGADMIN_TREE_ID).getRootNode().reload();
},
"tooltip": {
"text":CQ.I18n.getMessage("Refresh the tree")
}
}],
listeners: {
checkchange : function( cNode, checked ){
var tagTree = CQ.Ext.getCmp(MyClientLib.Sidekick.TAGADMIN_TREE_ID);
var tag = cNode.attributes.name;

//to create something like geometrixx-media:entertainment/music
tag = tag.substr("etc/tags".length + 1);
tag = tag.substr(0, tag.indexOf("/")) + ":" + tag.substr(tag.indexOf("/") + 1) ;

var data = { "./cq:tags@TypeHint" : "String[]", "./cq:tags@Patch" : "true",
"./cq:tags" : (checked ? "+" : "-") + tag };

$.ajax({
url: sk.getPath() + "/jcr:content",
dataType: "json",
data: data,
success: function(rdata){
var pNodes = [];
var pNode = cNode.parentNode;

while(true){
if(pNode.attributes.name == "etc/tags"){
break;
}

pNodes.push(pNode);
pNode = pNode.parentNode;
}

var dec = pNodes.length - 1;

var callBack = function(rNode){
if(dec < 0 ){
return;
}

var toRefresh;

CQ.Ext.each(rNode.childNodes, function(child){
if(!toRefresh && (child.attributes.name == pNodes[dec].attributes.name)){
toRefresh = child;
}
});

if(toRefresh){
dec--;
toRefresh.reload(callBack);
}
};

tagTree.getRootNode().reload(callBack);
},
type: 'POST'
});
}
}
});

sk.panels[this.TAGS] = new CQ.Ext.Panel({
"border": false,
//"autoScroll": true,
"layout": "fit",
items: [tree],
"id": "cq-sk-tab-" + this.TAGS
});

tabPanel.add({
"tabTip": "Tags",
"iconCls": "cq-sidekick-tab cq-cft-tab-icon full",
"items": sk.panels[this.TAGS],
"layout": "fit"
});

sk.doLayout();
}
};

if(window.location.pathname.indexOf("/cf") == 0 || window.location.pathname.indexOf("/content") == 0){
$.getScript("/libs/cq/ui/widgets/source/widgets/wcm/ContentFinder.js", function(){
var s = MyClientLib.Sidekick;

var SK_INTERVAL = setInterval(function(){
var sk = CQ.WCM.getSidekick();

if(sk && sk.findById(s.SK_TAB_PANEL)){
clearInterval(SK_INTERVAL);
s.addTagsPanel(sk);
}
}, 250);
}).fail(function(jqxhr, settings, exception){
console.log("Error parsing /libs/cq/ui/widgets/source/widgets/wcm/ContentFinder.js");
console.log(exception);
});
}

4) #133 we are loading ootb ContentFinder.js and #141 calling the js function to add a new tab and panel after #139 the sidekick is loaded and available

Viewing all articles
Browse latest Browse all 525

Trending Articles