How to add your own content finder tab in CQ5

So the content finder tabs are handy to be able to search for different kinds of content in CQ5 to drag and drop into content pages. It’s handy to be able to roll your own which executes your specific query. So how to do this? There are two parts:

1. Custom content finder tab
2. Custom backing service to refresh content finder tab

The first part is as follows:

1. Create a folder (set jcr:primaryType to sling:Folder) in the form apps/wcm/extensions/contentfinder
2. Set property extensionGroup to “tabs”
3. Set property extensionType to “contentfinder_extension”
4. Create a js file under this folder (e.g. advsearch.js)

An example of the contents of this js file is as follows:

{
	"tabTip": CQ.I18n.getMessage("My DAM Search"),
	"id": "cfTab-AdvDamSearch",
	"xtype": "contentfindertab",
	"iconCls": "cq-cft-tab-icon advsearch",
	"style": "background-image: url('/etc/designs/myapp/images/advsearch.png');",
	/*"closable": true,*/
	"ranking": 10,
	"allowedPaths": [
		"/content/*",
		"/etc/scaffolding/*",
		"/etc/workflow/packages/*"
	],
	"items": [
		CQ.wcm.ContentFinderTab.getQueryBoxConfig({
			"id": "cfTab-AdvDamSearch-QueryBox",
			"height": 300,
			"items": [
			    /* Set your own items here to denote each Ext-JS element that will appear in the query box portion of the content finder tab */	
			    {
			    	"xtype":"combo",
					"style": "marginTop:0px;width:100%",
			    	"typeAhead":false,
			    	"mode": 'local',
			    	"triggerAction": 'all',
			    	"store": ["Animation","Audio","Document","HD Video","Image","SD Video"],
			    	"name":"media-type",
					"emptyText":"Media Type",
			    	listeners: {
                                                 /* DAMSearch.onMediaTypeChanged refers to a function in our own custom clientlibs which has category cq.wcm.edit */
						'select' : DAMSearch.onMediaTypeChanged
					}
			    	
			    }
			    ,      
				{ /* example custom text field for search */
					"style":"marginTop:5px;width:100%",
					"xtype":"textfield",
					"name":"dc:title",
					"emptyText":"Title",
					"boxMaxHeight":25
					
				}
				,
				{
					"style":"marginTop:5px;width:100%",
					"xtype":"textfield",
					"name":"dc:description",
					"emptyText":"Description",
					"boxMaxHeight":25
				}
				,
				{
					"style":"marginTop:5px;width:100%",
					"xtype":"textfield",
					"name":"cq:tags",
					"emptyText":"Keywords",
					"boxMaxHeight":25
						
				},
				{/* combobox in query box */
					"style": "marginTop:0px;width:100%",
					"xtype": "combo",
					"store": ["Multimedia","Primarily Text","Images","Code / Software Specific"],
					"name":"document-contents",
					"emptyText":"Document contents",
					"typeAhead":false,
					"mode": 'local',
			    	"triggerAction": 'all',
					"hidden":true
					
				}
				,
				{
					"style":"marginTop:5px;marginBottom:5px;width:100%",
					"xtype":"textfield",
					"name":"jcr:createdBy",
					"emptyText":"Upload user",
					"boxMaxHeight":25,
				}
				,
				{
					"style":"marginTop:0px;width:100%",
					"xtype":"datefield",
					"name":"jcr:createdLower",
					"emptyText":"Upload date from",
					"width":170

				}
				,
				{/* date field */
					"style":"marginTop:0px;width:100%",
					"xtype":"datefield",
					"name":"jcr:createdUpper",
					"emptyText":"Upload date to",
					"width":170
				}
				,
				{
					xtype:"button",
					name:"search",
					text:"Search",
					handler:function(){
						var tab = CQ.Ext.getCmp("cfTab-AdvDamSearch");
						tab.submitQueryBox.call(tab,this);
					}
				},
				{/* just some random html */
					border: false,
				    xtype: "panel",
				    html: "<a style='margin-top:5px;border-style:none;cursor:pointer;color:blue;text-decoration:underline' href='/content/dam/asset-share.html' target='_blank'>Advanced Search</a>"
				}
			]
		}),
/* Customize here based on what a dragged item would create in the content page:resource type, property to set, etc */
		CQ.wcm.ContentFinderTab.getResultsBoxConfig({
			"itemsDDGroups": [CQ.wcm.EditBase.DD_GROUP_ASSET],
			"itemsDDNewParagraph": {
				"path": "foundation/components/image",
				"propertyName": "./fileReference"
			},
			"noRefreshButton": true,
			"tbar": [
				CQ.wcm.ContentFinderTab.REFRESH_BUTTON,
				"->",
				{
					"toggleGroup": "cfTab-Images-TG",
					"enableToggle": true,
					"toggleHandler": function(button, pressed) {
						var tab = CQ.Ext.getCmp("cfTab-AdvDamSearch");
						if (pressed) {
/* change the rendering of the results pane using these templates (e.g. thumbnail or detail) */
							tab.dataView.tpl = new CQ.Ext.XTemplate(CQ.wcm.ContentFinderTab.THUMBS_TEMPLATE);
							tab.dataView.itemSelector = CQ.wcm.ContentFinderTab.THUMBS_ITEMSELECTOR;
						}
						if (tab.dataView.store != null) {
							tab.dataView.refresh();
						}
					},
					"pressed": true,
					"allowDepress": false,
					"cls": "cq-btn-thumbs cq-cft-dataview-btn",
					"iconCls":"cq-cft-dataview-mosaic",
					"tooltip": {
						"text": CQ.I18n.getMessage("Mosaic View"),
						"autoHide":true
					}
				},
				{
					"toggleGroup": "cfTab-Images-TG",
					"enableToggle": true,
					"toggleHandler": function(button, pressed) {
						var tab = CQ.Ext.getCmp("cfTab-AdvDamSearch");
						if (pressed) {
							tab.dataView.tpl = new CQ.Ext.XTemplate(CQ.wcm.ContentFinderTab.DETAILS_TEMPLATE);
							tab.dataView.itemSelector = CQ.wcm.ContentFinderTab.DETAILS_ITEMSELECTOR;
						}
						if (tab.dataView.store != null) {
							tab.dataView.refresh();
						}
					},
					"pressed": false,
					"allowDepress": false,
					"cls": "cq-btn-details cq-cft-dataview-btn",
					"iconCls":"cq-cft-dataview-list",
					"tooltip": {
						"text": CQ.I18n.getMessage("List View"),
						"autoHide": true
					}
				}
			]
		},{
/* Points to your own Backend Service which will return search results */
			"url": "/bin/wcm/contentfinder/mydamsearch/view.json/content/dam"
		},{
			"baseParams": {
				/*"defaultMimeType": "image"*/
			}
		})
	]
}

The above has a few extension points (see the comments for where you can add your own bits of code). The main ones to watch out for are CQ.wcm.ContentFinderTab.getQueryBoxConfig, CQ.wcm.ContentFinderTab.getResultsBoxConfig, and url. The first two lets you configure a form panel and results panel using Ext-JS style configurations (the same as you use for dialogs). The url value points to a backing service which you will write in Java.

An example backing service is as follows:

import com.day.cq.wcm.core.contentfinder.ViewHandler;

/**
 * Returns a JSON representation of the lightbox so that it can display as results
 * inside the Content Finder
 * 
 * @author sarwar.bhuiyan
 * 
 */
@Component(label = "Academy of Arts ContentFinder Lightbox Servlet", name = "com.technomaven.wcm.servlets.ContentFinderLightbox", metatype = true, immediate = true)
@Service(value = { Servlet.class })
@Properties({
		@Property(name = "sling.servlet.methods", value = "GET"),
		@Property(name = "sling.servlet.paths", value = {
				"/bin/wcm/contentfinder/mydamsearch/view" }),
		@Property(name = "sling.servlet.extensions", value = { "json" }),
 })
public class ContentFinderLightboxServlet extends SlingSafeMethodsServlet
{
        @Reference
	private QueryBuilder builder;
    
        @Override
	protected void doGet(SlingHttpServletRequest request,
			SlingHttpServletResponse response) throws ServletException,
			IOException {
	
	//do your search here
        //TODO


        //Write out results to content finder. The following is just an example iterating through a number of Strings (entries)
        //representing dam paths and writing them out as a JSON result for content finder.

            response.setContentType("application/json");
	    response.setCharacterEncoding("utf-8");
	    try
	    {
	      TidyJSONWriter out = new TidyJSONWriter(response.getWriter());
	      out.setTidy(true);
	      out.object();
	      out.key("hits");
	      out.array();

	      if (entries != null && request.getParameter("limit")==null) {
	        for (String entry : entries) {
	          Asset asset = (Asset)request.getResourceResolver().getResource(entry).adaptTo(Asset.class);
	          
	          out.object();
	          out.key("name").value(asset.getName());
	          out.key("path").value(asset.getPath());
	          Node assetNode = (Node) asset.adaptTo(Node.class);

	          if(assetNode.hasProperty("jcr:content/metadata/dc:title") ){
	        	  if(assetNode.getProperty("jcr:content/metadata/dc:title").isMultiple())
	        		  out.key("title").value(assetNode.getProperty("jcr:content/metadata/dc:title").getValues()[0].getString());
	        	  else
	        		  out.key("title").value(assetNode.getProperty("jcr:content/metadata/dc:title").getString());
	        	  
	          }
	          out.key("lastModified").value(Long.valueOf(asset.getLastModified()));
	          if(assetNode.hasProperty("jcr:content/metadata/dc:format"))
	        	  out.key("mimeType").value(assetNode.getProperty("jcr:content/metadata/dc:format").getString());
	          long ck = 0L;
					try {
						Node n = (Node) asset.getRendition(
								"cq5dam.thumbnail.48.48.png").adaptTo(
								Node.class);
						ck = n.getNode("jcr:content")
								.getProperty("jcr:lastModified").getLong();

						ck = ck / 1000L * 1000L;
					} catch (Exception e) {
					}
					out.key("ck").value(Long.valueOf(ck));
					long size = 0L;
					Rendition currentOriginal = asset.getOriginal();
					if (currentOriginal != null) {
						size = currentOriginal.getSize();
					}
		      out.key("size").value(Long.valueOf(size));
	          out.endObject();
	        }
	      }


	      out.endArray();
	      out.endObject();

        }

}


The above is just an example. The key thing is the result being in a format that the content finder understands which can then be applied to the tpl (templates) for the result pane. For more examples, have a look at the js files in (/libs/wcm/extensions/contentfinder) for configs of OOTB content finder tabs and /libs/cq/ui/widgets/source/widgets/wcm/ContentFinderTab.js for the template formats. An example of a template string is as follows:

 
CQ.wcm.ContentFinderTab.DETAILS_TEMPLATE =
    '<tpl for=".">' +
        '<div class="cq-cft-search-item" ondblclick="CQ.wcm.ContentFinderTab.resultDblClick(event, \'{id}\', \'{pathEncoded}\', {isFolder}, \'{ddGroups}\');">' +
        '<div title="{pathEncodedTitle}" class="cq-cft-search-thumb" style="background-image:url(\'{[CQ.wcm.ContentFinderTab.THUMBS_URL(values, 48, 48)]}\');"></div>' +
        '<div class="cq-cft-search-text-wrapper">' +
        '<div class="cq-cft-search-title">{[CQ.shared.XSS.getXSSValue(values.name)]}</div>' +
        '{[CQ.shared.XSS.getXSSTablePropertyValue(values, \"title\") ? "<div>" + CQ.shared.XSS.getXSSTablePropertyValue(values, \"title\") + "</div>" : ""]}' +
        '<div>{formattedSize}</div>' +
        '<div>{lastModified}</div>' +
        '</div>' +
        '<div class="cq-cft-search-separator"></div>' +
        '</div>' +
        '</tpl>';

As you can see, it’s made of this element which contains some html and placeholder variables denoted by {} and contain the variables that will be contained in each hit item in the JSON result to the content finder. You may be able to call upon some javascript function (e.g. CQ.shared.XSS.getXSSTablePropertyValue()) provided those functions are in clientlibs registered with cq.wcm.edit category.

Enjoy!

– Sarwar Bhuiyan

Advertisements
How to add your own content finder tab in CQ5

10 thoughts on “How to add your own content finder tab in CQ5

  1. The Mighty Sparrow says:

    Great post!

    Quick question (slightly unrelated) – I have a client that wants their own, custom icon in the tab, but I can’t seem to get the CSS file referenced in teh CF. Also, simply setting the “style” attribute on the element is not proving successful either.

    If it’s easier, feel free to just send the example’s source and I’ll track it down from there.

    Many thanks!

    1. Put the CSS in a clientlib folder and set add cq.wcm.edit in the categories. Then it should be picked up by all content finder extensions. Be very careful with the CSS in that because it will affect the cq5 edit interfaces.

  2. Hi, I think your blog might be having browser compatibility
    issues. When I look at your blog site in Opera, it looks
    fine but when opening in Internet Explorer, it has some overlapping.
    I just wanted to give you a quick heads
    up! Other then that, wonderful blog!

    1. Manish, if you look at the url set in the js file it’s “/bin/wcm/contentfinder/mydamsearch/view”

      The Java code I’ve posted there is a custom Sling Servlet which has a path “/bin/wcm/contentfinder/mydamsearch/view”. So the framework calls that to initialize or interact with the servlet.

  3. velpari says:

    Hi Sarwar Bhuiyan my doubt was see you have given three steps 1)To create your own CF tab 2)TO create servlet that invokes the functionality for your CF tab 3) Atlast display the result in cf tab

    I am not clear with the 3rd point can you please explain little bit deeper on what needs to be done for making the result to displayed on CF tab….

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s