Goal
This post is about extending the CQ Combo Box (CQ.Ext.form.ComboBox) widget to provide simple hierarchical (2-level) content. It also has a sample Sling Servlet to retrieve users and groups from CRX as json. Source code and video are available for download
Here is a screenshot and demo
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
Create Servlet
1) Create and deploy servlet to read users and groups from CRX. Here the servlet RepoGroupsUsers ( deployed as OSGI component ) returns the groups and users in json response.
package com.mycomponents.groupsusers;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.jackrabbit.api.security.user.*;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.apache.sling.commons.json.io.JSONWriter;
import org.apache.sling.jcr.base.util.AccessControlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Session;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.Iterator;
@SlingServlet(
paths="/bin/mycomponents/groupsusers",
methods = "GET",
metatype = true,
label = "Groups and Users Servlet"
)
public class RepoGroupsUsers extends SlingAllMethodsServlet {
private static final long serialVersionUID = 1L;
private static final Logger LOG = LoggerFactory.getLogger(RepoGroupsUsers.class);
@Override
protected void doGet(final SlingHttpServletRequest request, final SlingHttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
try{
ResourceResolver resolver = request.getResourceResolver();
Session session = resolver.adaptTo(Session.class);
UserManager um = AccessControlUtil.getUserManager(session);
JSONWriter jw = new JSONWriter(response.getWriter());
Group group = null;
User user = null; Object obj = null;String id = null;
Iterator<Authorizable> users, groups = um.findAuthorizables(new Query() {
public void build(QueryBuilder builder) {
builder.setSelector(Group.class);
}
});
jw.object();
jw.key("data").array();
while(groups.hasNext()){
group = (Group)groups.next();
jw.object();
jw.key("id").value(group.getID());
jw.key("text").value(group.getPrincipal().getName());
jw.key("group").value("y");
jw.endObject();
users = group.getMembers();
while(users.hasNext()){
obj = users.next();
if(!(obj instanceof User)){
continue;
}
user = (User)obj;
id = user.getID();
if(id.contains("@")){
id = id.substring(0, id.indexOf("@"));
}
jw.object();
jw.key("id").value(id);
jw.key("text").value(user.getPrincipal().getName());
jw.endObject();
}
}
jw.endArray();
jw.endObject();
}catch(Exception e){
LOG.error("Error getting groups and users",e);
throw new ServletException(e);
}
}
}
2) Install ( this post explains how-to ) the servlet; you should see folder /apps/groupsuserscombo/install created in CRXDE Lite (http://localhost:4502/crx/de) with the bundle jar
3) Access the servlet in browser with url http://localhost:4502/bin/mycomponents/groupsusers; groups and users in CRX repository ( under /home ) are returned in json response
Create Component
1) Create the component (of type cq:Component) /apps/groupsuserscombo/groupsuserscombo with following properties
2) Create clientlib /apps/groupsuserscombo/groupsuserscombo/clientlib of type cq:ClientLibraryFolder with the following properties
3) Create file /apps/groupsuserscombo/groupsuserscombo/clientlib/combo.js and add the following code. Here we are creating a combo extension and registering it as xtype stepcombo
var MyClientLib = MyClientLib || {};
MyClientLib.StepCombo = CQ.Ext.extend(CQ.Ext.form.ComboBox, {
constructor: function(config){
config = config || {};
config.store = new CQ.Ext.data.Store({
proxy: new CQ.Ext.data.HttpProxy({
"autoLoad":false,
url: "/bin/mycomponents/groupsusers",
method: 'GET'
}),
reader: new CQ.Ext.data.JsonReader({
root: 'data',
fields: [
{name: 'id', mapping: 'id'},
{name: 'text', mapping: 'text'},
{name: 'group', mapping: 'group'}
]
})
});
config.mode = "remote";
config.triggerAction = "all";
config.valueField = 'id';
config.displayField = 'text';
config.tpl ='<tpl for=".">' +
'<tpl if="!group">' +
'<div class="x-combo-list-item" style="margin-left: 20px">{text}</div>' +
'</tpl>' +
'<tpl if="group == \'y\'">' +
'<div class="x-combo-list-item"><b>{text}</b></div>' +
'</tpl>' +
'</tpl>';
MyClientLib.StepCombo.superclass.constructor.call(this, config);
},
initComponent: function () {
MyClientLib.StepCombo.superclass.initComponent.call(this);
var resizeFn = function(combo){
var size = combo.getSize();
size.width = 200;
combo.setSize(size);
};
this.on('loadcontent', resizeFn);
}
});
CQ.Ext.reg("stepcombo", MyClientLib.StepCombo);
combo.js
5) Create dialog /apps/groupsuserscombo/groupsuserscombo/dialog (of type cq:Dialog) with following properties
The xtype of /apps/groupsuserscombo/groupsuserscombo/dialog/items/items/tab1/items/user is set to stepcombo, we've created and registered above
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="cq:Dialog"
title="Groups Users"
xtype="dialog">
<items
jcr:primaryType="cq:Widget"
xtype="tabpanel">
<items jcr:primaryType="cq:WidgetCollection">
<tab1
jcr:primaryType="cq:Panel"
layout="form"
title="Add"
xtype="panel">
<items jcr:primaryType="cq:WidgetCollection">
<user
jcr:primaryType="cq:Widget"
fieldLabel="Select User"
name="./user"
xtype="stepcombo"/>
</items>
</tab1>
</items>
</items>
</jcr:root>
6) Add the following code in /apps/groupsuserscombo/groupsuserscombo/groupsuserscombo.jsp
<%@include file="/libs/foundation/global.jsp" %>
<%@page session="false" %>
Selected User : <%= ( properties.get("user") == null ) ? "None selected" : properties.get("user") %>
7) Here is the component structure