AEM CQ 56 - PathField Widget with Search

Goal


Extend PathField widget to support select by search in browse dialog. Check the demo, Package install download ( contains a simple component with pathfieldwithsearch widget configured)




Solution


1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create folder /apps/pathfieldsearch

2) Create node /apps/pathfieldsearch/clientlib of type cq:ClientLibraryFolder and add a String property categories with value cq.widgets

3) Create file (nt:file) /apps/pathfieldsearch/clientlib/js.txt and add

                       pathfieldwithsearch.js

4) Create file (nt:file) /apps/pathfieldsearch/clientlib/pathfieldwithsearch.js and add the following code

CQ.Ext.ns("ExperienceAEM");

ExperienceAEM.PathFieldWithSearch = CQ.Ext.extend(CQ.form.PathField, {
    // the tree creation was copied from /libs/cq/ui/widgets/source/widgets/BrowseDialog.js
    getTreePanel: function(){
        var treeRootConfig = CQ.Util.applyDefaults(this.treeRoot, {
            name: "content",
            text: CQ.I18n.getMessage("Site"),
            draggable: false,
            singleClickExpand: true,
            expanded:true
        });

        var treeLoaderConfig = CQ.Util.applyDefaults(this.treeLoader, {
            dataUrl: CQ.HTTP.externalize("/bin/tree/ext.json"),
            requestMethod:"GET",
            baseParams: {
                predicate: "hierarchy",
                _charset_: "utf-8"
            },
            baseAttrs: {
                "singleClickExpand":true
            },
            listeners: {
                beforeload: function(loader, node){
                    this.baseParams.path = node.getPath();
                }
            }
        });

        return this.browseDialog.treePanel = new CQ.Ext.tree.TreePanel({
            region:"west",
            lines: CQ.themes.BrowseDialog.TREE_LINES,
            bodyBorder: CQ.themes.BrowseDialog.TREE_BORDER,
            bodyStyle: CQ.themes.BrowseDialog.TREE_STYLE,
            height: "100%",
            width: 250,
            autoScroll: true,
            containerScroll: true,
            root: new CQ.Ext.tree.AsyncTreeNode(treeRootConfig),
            loader: new CQ.Ext.tree.TreeLoader(treeLoaderConfig),
            defaults: {
                "draggable": false
            }
        });
    },

    getSearchPanel: function(){
        var reader = new CQ.Ext.data.JsonReader({
            id: "path",
            root: "hits",
            fields: [ "path", "title" ]
        });

        //querybuilder servlet returns the search results
        var searchStore = new CQ.Ext.data.Store({
            proxy:    new CQ.Ext.data.HttpProxy({
                url: "/bin/querybuilder.json"
            }),
            baseParams: {
                "p.limit": "100",
                "p.offset": "0",
                "type": "cq:Page"
            },
            reader:   reader,
            autoLoad: false
        });

        var searchTemplate = new CQ.Ext.XTemplate(
            '<tpl for=".">',
            '<div class="search-result">{title} - {path}</div>',
            '</tpl>'
        );

        this.browseDialog.searchResultsView = new CQ.Ext.DataView({
            id: "pathfield-browsedialog-searchpanel",
            store: searchStore,
            tpl: searchTemplate,
            itemSelector: "div.search-result",
            selectedClass: "search-result-selected",
            singleSelect: true,
            style: { margin: "8px 0 0 0" }
        });

        this.browseDialog.searchField = new CQ.Ext.form.TextField({
            width: "220",
            hideLabel: true,
            enableKeyEvents: true,
            listeners:{
                keypress: function(t,e){
                    //initiate search on enter
                    if (e.getKey() == e.ENTER) {
                        searchStore.reload( { params: { fulltext: t.getValue() } } );
                    }
                }
            }
        });

        var button = {
            xtype: "button",
            text: "Search",
            width: 60,
            tooltip: 'Search',
            style: { margin: "0 0 0 10px" },
            handler: (function () {
                searchStore.reload( { params: { fulltext: this.browseDialog.searchField.getValue() } } );
            }).createDelegate(this)
        };

        return new CQ.Ext.Panel({
            region: "center",
            border: false,
            layout: "form",
            autoScroll:true,
            items: [ {
                        xtype: 'panel', layout: 'hbox', border: false,
                        items: [this.browseDialog.searchField, button]
                    }, this.browseDialog.searchResultsView ]
        });
    },

    initComponent : function(){
        ExperienceAEM.PathFieldWithSearch.superclass.initComponent.call(this);

        this.on("dialogopen", function(){
            var bd = this.browseDialog;

            if(bd.searchField == null){
                //remove the existing tree and add a new tree panel; couldn't successfully move the existing tree
                //to new panel with search results, so remove and add new
                bd.remove(this.browseDialog.treePanel, true);

                var items = new CQ.Ext.Panel({
                    border:false,
                    layout: "border",
                    defaults: {
                        bodyStyle: 'padding:15px'
                    },
                    items: [ this.getTreePanel(), this.getSearchPanel()]
                });

                bd.setWidth(600);
                bd.add(items);

                bd.loadAndShowPath(this.getValue());
            }else{
                bd.searchField.setValue("");
                bd.searchResultsView.getStore().removeAll();
            }
        });

        this.on('dialogselect', function(){
            var searchView = this.browseDialog.searchResultsView;

            if(searchView && searchView.getSelectedRecords().length > 0){
                this.setValue(searchView.getSelectedRecords()[0].get("path"));
            }
        });
    }
});
CQ.Ext.reg("pathfieldwithsearch", ExperienceAEM.PathFieldWithSearch);

5) Create file (nt:file) /apps/pathfieldsearch/clientlib/css.txt and add

                       pathfieldwithsearch.css

6)  Create file (nt:file) /apps/pathfieldsearch/clientlib/pathfieldwithsearch.css and add the following code

#pathfield-browsedialog-searchpanel .search-result {
    padding: 3px 3px 3px 0;
}

#pathfield-browsedialog-searchpanel .search-result:hover {
    background-color: gainsboro;
    cursor: pointer;
}

#pathfield-browsedialog-searchpanel .search-result-selected  {
    background-color: gainsboro;
}


2 comments:

  1. Thank you so much for your extremely useful blog! I've learned so much from you.

    One enhancement I added was to leverage the rootPath property of the default CQ.form.PathField:

    var searchRoot = this.rootPath;
    if (!searchRoot || $.isEmptyObject(searchRoot)) {
    searchRoot = "/content";
    }

    And then added it to the baseParams object of searchStore:

    baseParams: {
    "p.limit": "100",
    "p.offset": "0",
    "type": "cq:Page",
    "path": searchRoot
    }


    Never stop posting!

    ReplyDelete