{"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 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 <\/p>\n Now the pathfield looked\u00a0like this:<\/p>\n <\/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 The first question here is how this tree structure shows up\u00a0here:<\/span><\/p>\n <\/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 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 <\/p>\n <\/p>\n Here is the updated browseDialog.js<\/em><\/p>\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 After all the changes, we can see the desired results as follows.<\/span><\/p>\n <\/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 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":"\nProblem with the Default xtype in AEM<\/strong>:<\/h6>\n
Steps I followed to Solve This xtype Error<\/strong>:<\/span><\/h6>\n
\n
\nrootPath:\/etc\/commerce\/products\/accunity\/en_us\/products<\/em><\/li>\nBut How did I Write the Custom xtype?<\/strong><\/h6>\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
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
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