AEM CQ 56 - Extend Combo Box (CQ.Ext.form.ComboBox) Widget

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 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

2) Create clientlib /apps/groupsuserscombo/groupsuserscombo/clientlib of type cq:ClientLibraryFolder with categories cq.widgets

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);

4) Create file /apps/groupsuserscombo/groupsuserscombo/clientlib/js.txt and add the following code

               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 comments: