{"id":3865,"date":"2016-09-28T09:44:04","date_gmt":"2016-09-28T04:14:04","guid":{"rendered":"\/?p=3865"},"modified":"2020-08-17T11:48:55","modified_gmt":"2020-08-17T06:18:55","slug":"custom-xtype-pathfield-browsedialog-aem","status":"publish","type":"post","link":"https:\/\/www.argildx.us\/technology\/custom-xtype-pathfield-browsedialog-aem\/","title":{"rendered":"Create Custom Xtypes For Pathfield BrowseDialog in AEM"},"content":{"rendered":"

Sometimes, we need to design custom xtypes for pathfield BrowseDialog on a regular basis. This is because the default xtypes provided by AEM doesn\u2019t fulfill our requirements.<\/span><\/p>\n

Problem with the Default xtype in AEM<\/strong>:<\/h6>\n

While working with\u00a0xtype pathfield, I stumbled upon a use case\/problem.\u00a0<\/span>I wanted a pathfield so that I can choose the product under \/etc\/commerce\/products\/accunity\/en_us\/products<\/em> hierarchy by a productName property.<\/span><\/p>\n

Note: <\/b>Generally, pathfield can show the nodes by its name or jcr:title<\/em>.<\/p>\n

\"path<\/p>\n

Steps I followed to Solve This xtype Error<\/strong>:<\/span><\/h6>\n
    \n
  1. I created a widget of xtype:pathfield<\/strong><\/li>\n
  2. If I opened my \u00a0dialog, I could select any values under \/content<\/strong><\/span><\/li>\n
  3. So I needed to set rootPath<\/strong> in the widget
    \nrootPath:\/etc\/commerce\/products\/accunity\/en_us\/products<\/em><\/li>\n
  4. The nodes under products are of type nt:unstructured<\/strong>. By default, pathfield doesn\u2019t allow this types of nodes in the tree hierarchy.<\/li>\n
  5. So added a property predicate: nosystem<\/strong><\/li>\n<\/ol>\n

    Now the pathfield looked\u00a0like this:<\/p>\n

    \"how<\/p>\n

    But still, it is a very tedious task for the author to select a particular product. I wanted the pathfield to show the productName in place of node-name<\/em>. So I decided to write a custom xtype<\/strong>.<\/span><\/p>\n

    But How did I Write the Custom xtype?<\/strong><\/h6>\n

    The first question here is how this tree structure shows up\u00a0here:<\/span><\/p>\n

    \"Product<\/p>\n

    So while debugging\u00a0my dialog, I found, it calls currentPath.ext.json<\/em> and shows the \u201cname\u201d property of \u00a0JSON in the tree hierarchy.<\/span><\/p>\n

    So the next step for me was to change this servlet<\/strong>.<\/span><\/p>\n

    import org.apache.felix.scr.annotations.Component;\r\nimport org.apache.felix.scr.annotations.sling.SlingServlet;\r\nimport org.apache.sling.api.SlingHttpServletRequest;\r\nimport org.apache.sling.api.SlingHttpServletResponse;\r\nimport org.apache.sling.api.resource.Resource;\r\nimport org.apache.sling.api.resource.ValueMap;\r\nimport org.apache.sling.api.servlets.SlingSafeMethodsServlet;\r\nimport org.apache.sling.commons.json.JSONArray;\r\nimport org.apache.sling.commons.json.JSONException;\r\nimport org.apache.sling.commons.json.JSONObject;\r\n\r\nimport java.io.IOException;\r\nimport java.util.Iterator;\r\n\r\n\r\n@Component\r\n@SlingServlet(generateComponent = false, resourceTypes = \"sling\/servlet\/default\", selectors = {\"path\"}, extensions = {\"json\"})\r\n\r\npublic class MyServlet extends SlingSafeMethodsServlet {\r\n   protected void doGet(SlingHttpServletRequest request,SlingHttpServletResponse response)throws IOException\r\n   {\r\n       if (request.getRequestPathInfo().getSelectors()[0].equals(\"path\")) {\r\n           String resourcePath = request.getRequestPathInfo().getResourcePath();\r\n           Resource resource = request.getResourceResolver().getResource(resourcePath);\r\n           if (resource != null) {\r\n               Iterator<Resource> iter = resource.listChildren();\r\n               JSONArray jsonArray = new JSONArray();\r\n               JSONObject jsonObject = null;\r\n               while (iter.hasNext()) {\r\n                   Resource childResource = iter.next();\r\n                   if (!childResource.getName().equals(\"image\")) {\r\n                       ValueMap valueMap = childResource.adaptTo(ValueMap.class);\r\n                       jsonObject = new JSONObject();\r\n                       try {\r\n                           jsonObject.put(\"name\", childResource.getName());\r\n                           if (valueMap.containsKey(\"productName\"))\r\n                               jsonObject.put(\"text\", valueMap.get(\"productName\", \"\"));\r\n                           else if (valueMap.containsKey(\"jcr:title\"))\r\n                               jsonObject.put(\"text\", valueMap.get(\"jcr:title\", \"\"));\r\n                           else\r\n                               jsonObject.put(\"text\", childResource.getName());\r\n                           jsonObject.put(\"type\", valueMap.get(\"jcr:primaryType\", \"\"));\r\n                           if (valueMap.get(\"jcr:primaryType\").equals(\"sling:folder\"))\r\n                               jsonObject.put(\"cls\", \"folder\");\r\n                           else\r\n                               jsonObject.put(\"cls\", \"file\");\r\n                           jsonArray.put(jsonObject);\r\n                       } catch (JSONException e) {\r\n                           e.printStackTrace();\r\n                       }\r\n                   }\r\n               }\r\n               response.getWriter().print(jsonArray);\r\n           }\r\n       }\r\n\r\n   }\r\n}\r\n<\/pre>\n

    The next question<\/strong> was\u00a0from where this servlet is getting called.<\/span><\/p>\n

    The answer is browserDialog<\/em> widget. Inside pathfield widget, it is calling browserDialog<\/em> to show this tree structure.<\/span><\/p>\n

    Note<\/b>: Go to browseDialog.js<\/em><\/span><\/p>\n

    Change this part:<\/span><\/p>\n

    \"tree<\/p>\n

     <\/p>\n

    Here is the updated browseDialog.js<\/em><\/p>\n

    CQ.CustomBrowseDialog = CQ.Ext.extend(CQ.Dialog, {\r\n\r\n    \/**\r\n     * The browse dialog's tree panel.\r\n     * @private\r\n     * @type CQ.Ext.tree.TreePanel\r\n     *\/\r\n    treePanel: null,\r\n\r\n    \/**\r\n     * The browse dialog's browse field.\r\n     * @private\r\n     * @type CQ.form.BrowseField\r\n     *\/\r\n    browseField: null,\r\n\r\n    initComponent: function(){\r\n       CQ.CustomBrowseDialog.superclass.initComponent.call(this);\r\n    },\r\n\r\n    \/**\r\n     * Selects the specified path in the tree.\r\n     * @param {String} path The path to select\r\n     *\/\r\n    loadContent: function(path) {\r\n        if (typeof path == \"string\") {\r\n            this.path = path;\r\n            this.treePanel.selectPath(path,\"name\");\r\n            if(this.parBrowse){\r\n                \/\/ reload paragraph store\r\n                this.paraProxy.api[\"read\"].url = CQ.HTTP.externalize(path, true) + \".paragraphs.json\";\r\n                this.paraStore.reload();\r\n            }\r\n        }\r\n    },\r\n\r\n    \/**\r\n     * Returns the path of the selected tree node (or an empty string if no\r\n     * tree node has been selected yet).\r\n     * @return {String} The path\r\n     *\/\r\n    getSelectedPath: function() {\r\n        try {\r\n            return this.treePanel.getSelectionModel().getSelectedNode().getPath();\r\n        } catch (e) {\r\n            return \"\";\r\n        }\r\n    },\r\n\r\n    \/**\r\n     * Returns the anchor of the selected paragraph (or an empty string if\r\n     * no paragraph has been selected yet).\r\n     * @return {String} The anchor\r\n     *\/\r\n    getSelectedAnchor: function() {\r\n        try {\r\n            var anchorID = this.data.getSelectedRecords()[0].get(\"path\");\r\n            anchorID = anchorID.substring(anchorID.indexOf(\"jcr:content\")\r\n                    + \"jcr:content\".length + 1);\r\n            return anchorID.replace(\/\\\/\/g, \"_\").replace(\/:\/g, \"_\");\r\n        } catch (e) {\r\n            return \"\";\r\n        }\r\n    },\r\n\r\n    constructor: function(config){\r\n\r\n        var treeRootConfig = CQ.Util.applyDefaults(config.treeRoot, {\r\n            \"name\": \"content\",\r\n            \"text\": CQ.I18n.getMessage(\"Site\"),\r\n            \"draggable\": false,\r\n            \"singleClickExpand\": true,\r\n            \"expanded\":true\r\n        });\r\n\r\n        var treeLoaderConfig = CQ.Util.applyDefaults(config.treeLoader, {\r\n            \"dataUrl\": CQ.HTTP.externalize(\"\/content.path.json\"),\r\n            \"requestMethod\":\"GET\",\r\n            \"baseParams\": {\r\n                \"predicate\": \"hierarchy\",\r\n                \"_charset_\": \"utf-8\"\r\n            },\r\n            \"baseAttrs\": {\r\n                \"singleClickExpand\":true\r\n            },\r\n            \"listeners\": {\r\n                \"beforeload\": function(loader, node){\r\n                    this.dataUrl = node.getPath() + \".path.json\";\r\n                }\r\n            }\r\n        });\r\n\r\n        this.treePanel = new CQ.Ext.tree.TreePanel({\r\n            \"region\":\"west\",\r\n            \"lines\": CQ.themes.BrowseDialog.TREE_LINES,\r\n            \"bodyBorder\": CQ.themes.BrowseDialog.TREE_BORDER,\r\n            \"bodyStyle\": CQ.themes.BrowseDialog.TREE_STYLE,\r\n            \"height\": \"100%\",\r\n            \"width\": 200,\r\n            \"autoScroll\": true,\r\n            \"containerScroll\": true,\r\n            \"root\": new CQ.Ext.tree.AsyncTreeNode(treeRootConfig),\r\n            \"loader\": new CQ.Ext.tree.TreeLoader(treeLoaderConfig),\r\n            \"defaults\": {\r\n                \"draggable\": false\r\n            }\r\n        });\r\n\r\n        var width = CQ.themes.BrowseDialog.WIDTH;\r\n        var items = this.treePanel;\r\n\r\n        if (config.parBrowse) {\r\n            this.treePanel.on(\"click\", this.onSelectPage.createDelegate(this));\r\n\r\n            \/\/ Paragraph store\r\n            var reader = new CQ.Ext.data.JsonReader({\r\n                \"id\":            \"path\",\r\n                \"root\":          \"paragraphs\",\r\n                \"totalProperty\": \"count\",\r\n                \"fields\":        [ \"path\", \"html\" ]\r\n            });\r\n            this.paraProxy = new CQ.Ext.data.HttpProxy({\r\n                \"url\": \"\/\"\r\n            });\r\n            this.paraStore = new CQ.Ext.data.Store({\r\n                \"proxy\":    this.paraProxy,\r\n                \"reader\":   reader,\r\n                \"autoLoad\": false\r\n            });\r\n\r\n            \/\/ Paragraph template\r\n            var paraTemplate = new CQ.Ext.XTemplate(\r\n                '<tpl for=\".\">',\r\n                    '<div class=\"cq-paragraphreference-paragraph\">{html}<\/div>',\r\n                '<\/tpl>'\r\n            );\r\n\r\n            \/\/ Paragraph view\r\n            this.data = new CQ.Ext.DataView({\r\n                \"id\": \"cq-paragraphreference-data\",\r\n                \"region\": \"center\",\r\n                \"store\": this.paraStore,\r\n                \"tpl\": paraTemplate,\r\n                \"itemSelector\": \"div.cq-paragraphreference-paragraph\",\r\n                \"selectedClass\": \"cq-paragraphreference-selected\",\r\n                \"singleSelect\": true,\r\n                \"style\": { \"overflow\": \"auto\" }\r\n            });\r\n\r\n            \/\/ init dialog width and fields\r\n            width = 550;\r\n            items = new CQ.Ext.Panel({\r\n                \"border\":false,\r\n                \"layout\": \"border\",\r\n                \"items\": [ this.treePanel, this.data ]\r\n            });\r\n        }\r\n\r\n        CQ.Util.applyDefaults(config, {\r\n            \"title\": CQ.I18n.getMessage(\"Select Path\"),\r\n            \"closable\": true,\r\n            \"width\": width,\r\n            \"height\": CQ.themes.BrowseDialog.HEIGHT,\r\n            \"minWidth\": CQ.themes.BrowseDialog.MIN_WIDTH,\r\n            \"minHeight\": CQ.themes.BrowseDialog.MIN_HEIGHT,\r\n            \"resizable\": CQ.themes.BrowseDialog.RESIZABLE,\r\n            \"resizeHandles\": CQ.themes.BrowseDialog.RESIZE_HANDLES,\r\n            \"autoHeight\": false,\r\n            \"autoWidth\": false,\r\n            \"cls\":\"cq-browsedialog\",\r\n            \"ok\": function() { this.hide(); },\r\n            \"buttons\": CQ.Dialog.OKCANCEL,\r\n            \"items\": items\r\n        });\r\n       CQ.CustomBrowseDialog.superclass.constructor.call(this, config);\r\n    },\r\n\r\n    \/**\r\n     * @private\r\n     *\/\r\n    onSelectPage: function(node, event) {\r\n        this.paraProxy.api[\"read\"].url = CQ.HTTP.externalize(node.getPath() + \".paragraphs.json\", true);\r\n        this.paraStore.reload();\r\n    }\r\n});\r\n\r\nCQ.Ext.reg('recipebrowsedialog',CQ.CustomBrowseDialog);\r\n<\/pre>\n

    We can\u2019t make this change in the \/libs section<\/em>. So, I made my own xtype as productPathfield<\/em> and add a custom browseDialog in pathfield.js with this modification.<\/span><\/p>\n

    Note: xtype pathfield doesn\u2019t fulfill my requirements so needed to change it with productPathfield.<\/strong><\/p>\n

    CQ.form.CustomPathField = CQ.Ext.extend(CQ.Ext.form.ComboBox, {\r\n\r\n    \/**\r\n     * Remembers the last value when the last key up happened.\r\n     * @type String\r\n     * @private\r\n     *\/\r\n    lastValue: null,\r\n\r\n    \/**\r\n     * The ID of the delayed search interval.\r\n     * @type Number\r\n     * @private\r\n     *\/\r\n    searchIntervalId: 0,\r\n\r\n    \/**\r\n     * The panel holding the link-browser.\r\n     * @type CQ.BrowseDialog\r\n     * @private\r\n     *\/\r\n    browseDialog: null,\r\n\r\n    \/**\r\n     * Returns the anchor of the selected paragraph (or an empty string if\r\n     * no paragraph has been selected yet).\r\n     * @return {String} The anchor\r\n     *\/\r\n    getParagraphAnchor: function() {\r\n        return this.browseDialog.getSelectedAnchor();\r\n    },\r\n\r\n    \/**\r\n     * Checks if the current path is quoted. If yes the new value is decorated\r\n     * with quotes as well.\r\n     * @private\r\n     *\/\r\n    adjustNewValue: function(currentValue, newValue) {\r\n        if (\/^path:\"\/.test(currentValue)) {\r\n            \/\/ current value starts with quotes: decorate with quotes\r\n            \/\/ (add final quotes even if they do not exist yet - otherwise\r\n            \/\/ the triggered search would fail ('path:\"\/content')\r\n            newValue = '\"' + newValue + '\"';\r\n        }\r\n        return newValue;\r\n    },\r\n\r\n    \/**\r\n     * Executed on key up in the control.\r\n     * - Checks if its value matches a path. If yes, request .pages.json\r\n     * @private\r\n     *\/\r\n    keyup: function(comp, evt) {\r\n        var currentValue = this.getRawValue();\r\n\r\n        var key = evt.getKey();\r\n        if (key == 13) {\r\n            \/\/ [enter] hit\r\n            this.fireEvent(\"search\", this, currentValue);\r\n        }\r\n\r\n        if (currentValue == this.lastValue) {\r\n            \/\/ value did not change (key was arrows, ctrl etc.)\r\n            return;\r\n        }\r\n        this.lastValue = currentValue;\r\n\r\n        var path = currentValue;\r\n\r\n        if (\/^\\\/\/.test(path) && \/\\\/$\/.test(path)) {\r\n            \/\/ path starts with a slash: ignore non-absolute path (#29745)\r\n            \/\/ path ends with a slash: request path.pages.json\r\n            if (path == \"\/\") {\r\n                path = this.rootPath ? this.rootPath : \"\/\";\r\n            }\r\n            else {\r\n                \/\/ remove final slash:\r\n                path = path.replace(\/\\\/$\/, \"\");\r\n            }\r\n            this.loadStore(CQ.shared.HTTP.encodePath(path));\r\n        }\r\n        else if (this.searchDelay) {\r\n            window.clearTimeout(this.searchIntervalId);\r\n            var pc = this;\r\n            this.searchIntervalId = window.setTimeout(function() {\r\n                pc.fireEvent(\"search\", pc, currentValue);\r\n            }, this.searchDelay);\r\n        }\r\n\r\n    },\r\n\r\n    \/**\r\n     * Reloads the autocompletion store with a new URL.\r\n     * @private\r\n     *\/\r\n    loadStore: function(path) {\r\n        this.store.proxy.api[\"read\"].url = path + \".pages.json\";\r\n        this.store.reload();\r\n    },\r\n\r\n    \/**\r\n     * The trigger action of the TriggerField, creates a new BrowseDialog\r\n     * if it has not been created before, and shows it.\r\n     * @private\r\n     *\/\r\n    onTriggerClick : function() {\r\n        if (this.disabled) {\r\n            return;\r\n        }\r\n        \/\/ lazy creation of browse dialog\r\n        if (this.browseDialog == null || this.modeless) {\r\n            function okHandler() {\r\n                var path = this.getSelectedPath();\r\n                var anchor = this.parBrowse ? this.getSelectedAnchor() : null;\r\n\r\n                var value;\r\n                if (anchor) {\r\n                    value = CQ.Util.patchText(this.pathField.parLinkPattern, [path, anchor]);\r\n                } else {\r\n                    value = CQ.Util.patchText(this.pathField.linkPattern, path);\r\n                }\r\n                if (this.pathField.suffix) {\r\n                    value += this.pathField.suffix;\r\n                }\r\n\r\n                this.pathField.setValue(value);\r\n\r\n                this.pathField.fireEvent(\"dialogselect\", this.pathField, path, anchor);\r\n                this.hide();\r\n            }\r\n\r\n            var browseDialogConfig = CQ.Util.applyDefaults(this.browseDialogCfg, {\r\n                ok: okHandler,\r\n                \/\/ pass this to the BrowseDialog to make in configurable from 'outside'\r\n                parBrowse: this.parBrowse,\r\n                treeRoot: this.treeRoot,\r\n                treeLoader: this.treeLoader,\r\n                listeners: {\r\n                    hide: function() {\r\n                        if (this.pathField) {\r\n                            this.pathField.fireEvent(\"dialogclose\");\r\n                        }\r\n                    }\r\n                },\r\n                loadAndShowPath: function(path) {\r\n                    this.path = path;\r\n                    \/\/ if the root node is the real root, we need an additional slash\r\n                    \/\/ at the begining for selectPath() to work properly\r\n                    if (this.pathField.rootPath == \"\" || this.pathField.rootPath == \"\/\") {\r\n                        path = \"\/\" + path;\r\n                    }\r\n\r\n                    var browseDialog = this;\r\n                    var treePanel = this.treePanel;\r\n\r\n                    \/\/ what to do when selectPath worked\r\n                    function successHandler(node) {\r\n                        \/\/ ensureVisible fails on root, ie. getParentNode() == null\r\n                        if (node.parentNode) {\r\n                            node.ensureVisible();\r\n                        }\r\n                        if (browseDialog.parBrowse) {\r\n                            browseDialog.onSelectPage(node);\r\n                        }\r\n                    }\r\n\r\n                    \/\/ string split helper function\r\n                    function substringBeforeLast(str, delim) {\r\n                        var pos = str.lastIndexOf(delim);\r\n                        if (pos >= 0) {\r\n                            return str.substring(0, pos);\r\n                        } else {\r\n                            return str;\r\n                        }\r\n                    }\r\n\r\n                    \/\/ try to handle links created by linkPattern\/parLinkPattern,\r\n                    \/\/ such as \"\/content\/foo\/bar.html#par_sys\"; needs to try various\r\n                    \/\/ cut-offs until selectPath works (eg. \/content\/foo\/bar)\r\n                    \/\/ 1) try full link (path)\r\n                    treePanel.selectPath(path, null, function(success, node) {\r\n                        if (success && node) {\r\n                            successHandler(node);\r\n                        } else {\r\n                            \/\/ 2) try and split typical anchor from (par)linkPattern\r\n                            path = substringBeforeLast(path, \"#\");\r\n\r\n                            treePanel.selectPath(path, null, function(success, node) {\r\n                                if (success && node) {\r\n                                    successHandler(node);\r\n                                } else {\r\n                                    \/\/ 3) try and split typical extension from (par)linkPattern\r\n                                    path = substringBeforeLast(path, \".\");\r\n\r\n                                    treePanel.selectPath(path, null, function(success, node) {\r\n                                        if (success && node) {\r\n                                            successHandler(node);\r\n                                        }\r\n                                    });\r\n                                }\r\n                            });\r\n                        }\r\n                    });\r\n                },\r\n                pathField: this\r\n            });\r\n\r\n            \/\/ fix dialog width for par browse to include 3 cols of pars\r\n            if (this.parBrowse) {\r\n                browseDialogConfig.width = 570;\r\n            }\r\n\r\n            \/\/ build the dialog and load its contents\r\n            this.browseDialog = new CQ.CustomBrowseDialog(browseDialogConfig);\r\n        }\r\n\r\n        this.browseDialog.loadAndShowPath(this.getValue());\r\n\r\n        this.browseDialog.show();\r\n        this.fireEvent(\"dialogopen\");\r\n    },\r\n\r\n    constructor : function(config){\r\n        \/\/ set default values\r\n        \/\/ done here, because it is already used in below applyDefaults\r\n        if (typeof config.rootTitle === \"undefined\") {\r\n            config.rootTitle = config.rootPath || CQ.I18n.getMessage(\"Websites\");\r\n        }\r\n        if (typeof config.rootPath === \"undefined\") {\r\n            config.rootPath = \"\/content\";\r\n        }\r\n        var rootName = config.rootPath;\r\n        \/\/ the root path must not include a leading slash for the root tree node\r\n        \/\/ (it's added automatically in CQ.Ext.data.Node.getPath())\r\n        if (rootName.charAt(0) === \"\/\") {\r\n            rootName = rootName.substring(1);\r\n        }\r\n        if (typeof config.predicate === \"undefined\") {\r\n            config.predicate = \"siteadmin\";\r\n        }\r\n        if (typeof config.showTitlesInTree === \"undefined\") {\r\n            config.showTitlesInTree = true;\r\n        }\r\n\r\n        var pathField = \"path\";\r\n        if (config.escapeAmp) {\r\n            pathField = \"escapedPath\";\r\n            delete config.escapeAmp;\r\n        }\r\n\r\n        CQ.Util.applyDefaults(config, {\r\n            linkPattern: config.parBrowse ? \"{0}.html\" : \"{0}\",\r\n            parLinkPattern: \"{0}.html#{1}\",\r\n\r\n            tpl: new CQ.Ext.XTemplate(\r\n                '<tpl for=\".\">',\r\n                    '<div ext:qtip=\"{tooltip}\" class=\"x-combo-list-item\">',\r\n                        '<span class=\"cq-pathfield-completion-list-name\">{label}<\/span>',\r\n                        '<span class=\"cq-pathfield-completion-list-title\">{title}<\/span>',\r\n                    '<\/div>',\r\n                '<\/tpl>'),\r\n            displayField: pathField,\r\n            typeAhead: true,\r\n            searchDelay: 200,\r\n            suffix:\"\",\r\n            mode: 'local',\r\n            selectOnFocus:true,\r\n            enableKeyEvents: true,\r\n            validationEvent: false,\r\n            validateOnBlur: false,\r\n            \/\/ show a search icon\r\n            triggerClass: \"x-form-search-trigger\",\r\n            treeRoot: {\r\n                name: rootName,\r\n                \/\/ label for the root\r\n                text: config.rootTitle\r\n            },\r\n            treeLoader: {\r\n                dataUrl: CQ.shared.HTTP.getXhrHookedURL(CQ.Util.externalize(config.rootPath + \".ext.json\")),\r\n                baseParams: {\r\n                    predicate: config.predicate,\r\n                    \"_charset_\": \"utf-8\"\r\n                },\r\n                \/\/ overwriting method to be able to intercept node labeling\r\n                createNode: function(attr) {\r\n                    if (!config.showTitlesInTree) {\r\n                        \/\/ no labled resources, use plain node name for tree nodes\r\n                        attr.text = attr.name;\r\n                    }\r\n                    return CQ.Ext.tree.TreeLoader.prototype.createNode.call(this, attr);\r\n                },\r\n                \/\/ overwriting method to fix handling of array params\r\n                \/\/ (needed for config.predicate string array case)\r\n                getParams: function(node) {\r\n                    var params = this.baseParams;\r\n                    params.node = node.id;\r\n                    return CQ.Ext.urlEncode(params);\r\n                },\r\n                listeners: {\r\n                    beforeLoad: function(loader, node) {\r\n                        this.dataUrl = node.getPath() + \".ext.json\";\r\n                    }\r\n                }\r\n            }\r\n        });\r\n\r\n        \/\/ store for autocompletion while typing\r\n        if (!(config.store instanceof CQ.Ext.data.Store)) {\r\n            var storeConfig = CQ.Util.applyDefaults(config.store, {\r\n                \/\/ URL for proxy is set dynamically based on current path in loadStore()\r\n                proxy: new CQ.Ext.data.HttpProxy({\r\n                    url: \"\/\",\r\n                    method:\"GET\"\r\n                }),\r\n                baseParams: {\r\n                    predicate: config.predicate\r\n                },\r\n                \"reader\": new CQ.Ext.data.JsonReader(\r\n                    {\r\n                        \"totalProperty\": \"results\",\r\n                        \"root\": \"pages\",\r\n                        \"id\": \"path\"\r\n                    },\r\n                    CQ.Ext.data.Record.create([\r\n                        {\r\n                            \"name\": \"label\",\r\n                            \"convert\": function(v, rec) {return CQ.shared.XSS.getXSSValue(rec.label);}\r\n                        },\r\n                        {\r\n                            \"name\": \"title\",\r\n                            \"mapping\": CQ.shared.XSS.getXSSPropertyName(\"title\")\r\n                        },\r\n                        {\r\n                            \"name\": pathField\r\n                        },\r\n                        {\r\n                            \"name\": \"tooltip\",\r\n                            \/\/ have to encode this twice because the template decodes the value before \r\n                            \/\/ injecting it into the tooltip div\r\n                            \"convert\": function(v, rec) {return _g.Util.htmlEncode(_g.Util.htmlEncode(rec.path));}\r\n                        }\r\n                    ])\r\n                )\r\n            });\r\n            config.store = new CQ.Ext.data.Store(storeConfig);\r\n        }\r\n        this.store = config.store;\r\n\r\n        CQ.form.CustomPathField.superclass.constructor.call(this, config);\r\n    },\r\n\r\n    initComponent : function(){\r\n        CQ.form.CustomPathField.superclass.initComponent.call(this);\r\n\r\n        this.addListener(\"keyup\", this.keyup, this);\r\n\r\n        this.addEvents(\r\n            \/**\r\n             * @event search\r\n             * Fires when the enter key is hit or after the user stopped typing.\r\n             * The period between the last key press and the firing of the event\r\n             * is specified in {@link #searchDelay}.\r\n             * @param {CQ.form.CustomPathField} this\r\n             * @param {String} value The current value of the field\r\n             *\/\r\n            'search',\r\n            \/**\r\n             * @event dialogopen\r\n             * Fires when the browse dialog is opened.\r\n             * @param {CQ.form.CustomPathField} this\r\n             *\/\r\n            \"dialogopen\",\r\n            \/**\r\n             * @event dialogselect\r\n             * Fires when a new value is selected in the browse dialog.\r\n             * @param {CQ.form.CustomPathField} this\r\n             * @param {String} path The path selected in the tree of the browse dialog\r\n             * @param {String} anchor The paragraph selected in the browse dialog (or null)\r\n             *\/\r\n            \"dialogselect\",\r\n            \/**\r\n             * @event dialogclose\r\n             * Fires when the browse dialog is closed.\r\n             * @param {CQ.form.CustomPathField} this\r\n             *\/\r\n            \"dialogclose\"\r\n        );\r\n        \r\n        \/\/ register component as drop target\r\n        CQ.WCM.registerDropTargetComponent(this);\r\n    },\r\n    \r\n    getDropTargets : function() {\r\n        var pathFieldComponent = this;\r\n        var target = new CQ.wcm.EditBase.DropTarget(this.el, {\r\n            \"ddAccept\": \"*\/*\",\r\n            \"notifyDrop\": function(dragObject, evt, data) {\r\n                if (dragObject && dragObject.clearAnimations) {\r\n                    dragObject.clearAnimations(this);\r\n                }\r\n                if (data && data.records && data.records[0]) {\r\n                    var pathInfo = data.records[0].get(\"path\");\r\n                    if (pathInfo) {\r\n                        pathFieldComponent.setValue(pathInfo);\r\n                        return true;\r\n                    }\r\n                }\r\n                return false;\r\n            }\r\n        });\r\n        target.groups[\"media\"] = true;\r\n        target.groups[\"s7media\"] = true;\r\n        target.groups[\"page\"] = true;\r\n        return [target];\r\n    }\r\n});\r\n\r\nCQ.Ext.reg(\"productPathfield\", CQ.form.CustomPathField);\r\n<\/pre>\n

    After all the changes, we can see the desired results as follows.<\/span><\/p>\n

    \"product<\/p>\n

    Please leave your precious comments on what you think about this approach. Happy to learn better solution for the same problem!<\/p>\n

    <\/div>\n","protected":false},"excerpt":{"rendered":"

    Sometimes, we need to design custom xtypes for pathfield BrowseDialog on a regular basis. This is because the default xtypes provided by AEM doesn\u2019t fulfill our requirements. Problem with the Default xtype in AEM: While working with\u00a0xtype pathfield, I stumbled upon a use case\/problem.\u00a0I wanted a pathfield so that I can choose the product under … Read more<\/a><\/p>\n","protected":false},"author":29,"featured_media":6670,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_et_pb_use_builder":"","_et_pb_old_content":"","_et_gb_content_width":"","content-type":"","footnotes":""},"categories":[66],"tags":[25,26,27,28,29],"yst_prominent_words":[534,1207,1208,1400,1401,1214,1209,1212,1210,1204,1220,1216,1211,1206,1200,1202,1205,1215,1198,1213],"acf":[],"yoast_head":"\nCustom xtype For Pathfield BrowseDialog Improves on AEM Default xtype<\/title>\n<meta name=\"description\" content=\"Custom Xtypes For Pathfield BrowseDialog are designed to improve the shortcomings of default xtypes in AEM and fulfil author requirements.\" \/>\n<meta name=\"robots\" content=\"index, follow\" \/>\n<meta name=\"googlebot\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<meta name=\"bingbot\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.argildx.us\/technology\/custom-xtype-pathfield-browsedialog-aem\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Custom xtype For Pathfield BrowseDialog Improves on AEM Default xtype\" \/>\n<meta property=\"og:description\" content=\"Custom Xtypes For Pathfield BrowseDialog are designed to improve the shortcomings of default xtypes in AEM and fulfil author requirements.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.argildx.us\/technology\/custom-xtype-pathfield-browsedialog-aem\/\" \/>\n<meta property=\"og:site_name\" content=\"Argil DX\" \/>\n<meta property=\"article:published_time\" content=\"2016-09-28T04:14:04+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2020-08-17T06:18:55+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.argildx.us\/wp-content\/uploads\/2016\/09\/Custom-Xtype-For-Pathfield-BrowseDialog.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1440\" \/>\n\t<meta property=\"og:image:height\" content=\"542\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.argildx.us\/#website\",\"url\":\"https:\/\/www.argildx.us\/\",\"name\":\"Argil DX\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":\"https:\/\/www.argildx.us\/?s={search_term_string}\",\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/www.argildx.us\/technology\/custom-xtype-pathfield-browsedialog-aem\/#primaryimage\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/www.argildx.us\/wp-content\/uploads\/2016\/09\/Custom-Xtype-For-Pathfield-BrowseDialog.jpg\",\"width\":1440,\"height\":542,\"caption\":\"Custom Xtype For Pathfield BrowseDialog\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.argildx.us\/technology\/custom-xtype-pathfield-browsedialog-aem\/#webpage\",\"url\":\"https:\/\/www.argildx.us\/technology\/custom-xtype-pathfield-browsedialog-aem\/\",\"name\":\"Custom xtype For Pathfield BrowseDialog Improves on AEM Default xtype\",\"isPartOf\":{\"@id\":\"https:\/\/www.argildx.us\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.argildx.us\/technology\/custom-xtype-pathfield-browsedialog-aem\/#primaryimage\"},\"datePublished\":\"2016-09-28T04:14:04+00:00\",\"dateModified\":\"2020-08-17T06:18:55+00:00\",\"author\":{\"@id\":\"https:\/\/www.argildx.us\/#\/schema\/person\/1c5b6f3f2f7218d9acb851588b98551f\"},\"description\":\"Custom Xtypes For Pathfield BrowseDialog are designed to improve the shortcomings of default xtypes in AEM and fulfil author requirements.\",\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.argildx.us\/technology\/custom-xtype-pathfield-browsedialog-aem\/\"]}]},{\"@type\":[\"Person\"],\"@id\":\"https:\/\/www.argildx.us\/#\/schema\/person\/1c5b6f3f2f7218d9acb851588b98551f\",\"name\":\"Argil DX Media\",\"image\":{\"@type\":\"ImageObject\",\"@id\":\"https:\/\/www.argildx.us\/#personlogo\",\"inLanguage\":\"en-US\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/0ccbc04705942d1269cdf9f789e58484?s=96&d=mm&r=g\",\"caption\":\"Argil DX Media\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","_links":{"self":[{"href":"https:\/\/www.argildx.us\/wp-json\/wp\/v2\/posts\/3865"}],"collection":[{"href":"https:\/\/www.argildx.us\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.argildx.us\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.argildx.us\/wp-json\/wp\/v2\/users\/29"}],"replies":[{"embeddable":true,"href":"https:\/\/www.argildx.us\/wp-json\/wp\/v2\/comments?post=3865"}],"version-history":[{"count":0,"href":"https:\/\/www.argildx.us\/wp-json\/wp\/v2\/posts\/3865\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.argildx.us\/wp-json\/wp\/v2\/media\/6670"}],"wp:attachment":[{"href":"https:\/\/www.argildx.us\/wp-json\/wp\/v2\/media?parent=3865"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.argildx.us\/wp-json\/wp\/v2\/categories?post=3865"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.argildx.us\/wp-json\/wp\/v2\/tags?post=3865"},{"taxonomy":"yst_prominent_words","embeddable":true,"href":"https:\/\/www.argildx.us\/wp-json\/wp\/v2\/yst_prominent_words?post=3865"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}