The Performance Guidepost

The Performance Guidepost

Imagine on a bright sunny day you open up your analytics dashboard and you notice the visitors graph going high and high and high and suddenly drops all of a sudden.

You will be wondering what is happening, only to find that your colleague had started a nice promotion and due to the promotion the website load increased multifold.

But what happened all of a sudden? Why the drop in visits? Ah… the promotion became too popular as it had gone viral in the social network and print media. And due to the heavy demand more number of users visited and the site could not take up the anticipated load and crashed.

A poor show, in spite the excellent job by marketing team.

What could have been done to avoid such situation and do not let the customer suffer and the drop in sales and site visitors?

Performance testing is the answer on anticipated site load.

 

[av_heading heading=’What is Performance Testing?’ tag=’h3′ style=” size=” subheading_active=” subheading_size=’15’ padding=’10’ color=” custom_font=”][/av_heading]

Triple “S” check is a must for the public facing websites.

  • Speed – Determines whether the application responds quickly.
  • Scalability – Determines maximum user load the software application can handle.
  • Stability – Determines if the application is stable under varying loads.

And all three can be measured using performance testing.

Performance testing is the investigation done either to determine or to prove the response time, scalability and performance of the website to ensure that they will perform well under their expected regular workload, at peak load and uncover inconsistencies across different operating systems/devices.

The goal of performance testing is not to find bugs but to eliminate performance bottlenecks and tune the system for maximum load. This is also to determine the maximum threshold the website can take.

[av_heading heading=’Common Performance Problems’ tag=’h3′ style=” size=” subheading_active=” subheading_size=’15’ padding=’10’ color=” custom_font=”][/av_heading]

Poor Response Time:

Once the user performs an action and user has to wait for so long before the response is provided. This can lead to poor user experience.

Poor Load Distribution:

Poor load distribution can cause slow response time by incorrectly assigning new site visitors to hanged up servers. If too many people are on the same server, they’re going to experience difficulties, even if the overall system is well under capacity. Check the sites of some of the big players and you will notice the site loads in a flash of moments.

[av_heading heading=’Types of Performance Testing’ tag=’h3′ style=” size=” subheading_active=” subheading_size=’15’ padding=’10’ color=” custom_font=”][/av_heading]

Load Test:

Generally a load test is conducted to understand the behaviour of a system under the specific expected load. It helps to identify the maximum operating capacity of an application as well as any bottlenecks and determine which element is causing degradation. E.g. If the number of users are increased then how much CPU, memory will be consumed, what is the network and bandwidth response time.

It also helps in measuring the response time, throughput rates, and resource-utilization levels, and to identify the breaking point, and the peak load the website can handle.

This can answer questions like, what is the maximum number of users that can use the system without any impact on performance and acceptable response time.

Stress Test:

Stress testing refers to the testing of website to determine whether its performance is satisfactory under any extreme and unfavourable conditions, which may occur as a result of heavy network traffic, process loading and maximum requests for resource utilization. Stress testing enables to identify how the website behaves under extreme load conditions.

This will answer questions like, what is the maximum peak load the system can handle and determine if the system will perform sufficiently if the current load goes well above the expected maximum limits.

Soak / Endurance Test:

This is usually done to determine if the system can sustain the continuous expected load, this helps in detecting potential memory leaks and utilization. Also to check the performance degradation when the system is being used for long duration.

This can answer questions like, if the promotion becomes very popular and the load of system is way beyond for a very long duration, can the system handle such situations?

Spike Test:

Spike testing is a subset of stress testing.  A spike test is a type of performance test focused on determining reaction to a sudden large spikes in the load generated by users.

This can answer questions like, if the competitor, runs a promotion and we are unaware of it, due to such situations users visiting the site for similar promotions on our site, there could be sudden spike of users and if the system can handle such situations.

[av_heading heading=’Tools’ tag=’h3′ style=” size=” subheading_active=” subheading_size=’15’ padding=’10’ color=” custom_font=”][/av_heading]

Lot of performance testing tools are available for different types of tests and it is quite difficult to cover all types of test using one.

Jmeter is one of the renowned open source tool designed to load test functional behaviour and measure performance.

Lets explore more about Jmeter and its functioning in the upcoming blog.

[av_heading heading=’Conclusion’ tag=’h3′ style=” size=” subheading_active=” subheading_size=’15’ padding=’10’ color=” custom_font=”][/av_heading]

Performance Testing is a must before the website goes to market, as poor performance and inconsistent behaviour of the site may lead to inadequate reputation, poor user experience and will not meet the sales goals.

Hence its concluded that it’s a must to perform performance testing at initial stage of building website and regularly in different intervals. Analytics can help monitor the peak loads and help plan for performance testing. This can go a long way building customer trust, relation and not only retain but expand customer base. Lets read about that too in another upcoming blog.

Custom Xtype For Pathfield BrowseDialog

Custom Xtype For Pathfield BrowseDialog

[av_dropcap1]S[/av_dropcap1]ometimes the xtypes provided by AEM doesn’t fulfill our requirements. So we need to design our custom xtypes on regular basis.

[av_heading heading=’Problem’ tag=’h2′ style=” size=” subheading_active=” subheading_size=’15’ padding=’10’ color=” custom_font=”][/av_heading]

While working with xtype pathfield, I stumbled upon a use case/problem. I wanted a pathfield so that  I can choose the product under /etc/commerce/products/accunity/en_us/products hierarchy by a productName property.

Note: Generally, pathfield can show the nodes by its name or jcr:title.

[av_image src=’https://www.argildx.com/wp-content/uploads/2017/06/Path-Field.png’ attachment=’1013′ attachment_size=’large’ align=’center’ styling=” hover=” link=” target=” caption=” font_size=” appearance=” overlay_opacity=’0.4′ overlay_color=’#000000′ overlay_text_color=’#ffffff’ animation=’no-animation’][/av_image]

Steps I followed:

  1. I created a widget of xtype:pathfield
  2. If I opened my  dialog, I could select any values under /content.
  3. So needed to set rootPath in the widget.
    rootPath:/etc/commerce/products/accunity/en_us/products
  4. The nodes under products are of type nt:unstructured. By default, pathfield doesn’t allow this types of nodes in the tree hierarchy.
  5. So added a property predicate: nosystem

Now the pathfield looked like this:

[av_image src=’https://www.argildx.com/wp-content/uploads/2017/06/Pathfield-looked-like.png’ attachment=’1024′ attachment_size=’large’ align=’center’ styling=” hover=” link=” target=” caption=” font_size=” appearance=” overlay_opacity=’0.4′ overlay_color=’#000000′ overlay_text_color=’#ffffff’ animation=’no-animation’][/av_image]

But still, it is a very tedious task for the author to select a particular product. I wanted pathfield to show the productName in place of node-name. So I decided to write custom xtype.

[av_heading heading=’But how?’ tag=’h4′ style=” size=” subheading_active=” subheading_size=’15’ padding=’10’ color=” custom_font=”][/av_heading]

The first question here is how this tree structure shows up here:

[av_image src=’https://www.argildx.com/wp-content/uploads/2017/06/ProductPathField.png’ attachment=’1015′ attachment_size=’large’ align=’center’ styling=” hover=” link=” target=” caption=” font_size=” appearance=” overlay_opacity=’0.4′ overlay_color=’#000000′ overlay_text_color=’#ffffff’ animation=’no-animation’][/av_image]

So while debugging my dialog, I found, it calls currentPath.ext.json and shows the “name”property of  JSON in the tree hierarchy. 

So next step for me was to change this servlet.

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.sling.SlingServlet;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.api.servlets.SlingSafeMethodsServlet;
import org.apache.sling.commons.json.JSONArray;
import org.apache.sling.commons.json.JSONException;
import org.apache.sling.commons.json.JSONObject;

import java.io.IOException;
import java.util.Iterator;


@Component
@SlingServlet(generateComponent = false, resourceTypes = "sling/servlet/default", selectors = {"path"}, extensions = {"json"})

public class MyServlet extends SlingSafeMethodsServlet {
   protected void doGet(SlingHttpServletRequest request,SlingHttpServletResponse response)throws IOException
   {
       if (request.getRequestPathInfo().getSelectors()[0].equals("path")) {
           String resourcePath = request.getRequestPathInfo().getResourcePath();
           Resource resource = request.getResourceResolver().getResource(resourcePath);
           if (resource != null) {
               Iterator<Resource> iter = resource.listChildren();
               JSONArray jsonArray = new JSONArray();
               JSONObject jsonObject = null;
               while (iter.hasNext()) {
                   Resource childResource = iter.next();
                   if (!childResource.getName().equals("image")) {
                       ValueMap valueMap = childResource.adaptTo(ValueMap.class);
                       jsonObject = new JSONObject();
                       try {
                           jsonObject.put("name", childResource.getName());
                           if (valueMap.containsKey("productName"))
                               jsonObject.put("text", valueMap.get("productName", ""));
                           else if (valueMap.containsKey("jcr:title"))
                               jsonObject.put("text", valueMap.get("jcr:title", ""));
                           else
                               jsonObject.put("text", childResource.getName());
                           jsonObject.put("type", valueMap.get("jcr:primaryType", ""));
                           if (valueMap.get("jcr:primaryType").equals("sling:folder"))
                               jsonObject.put("cls", "folder");
                           else
                               jsonObject.put("cls", "file");
                           jsonArray.put(jsonObject);
                       } catch (JSONException e) {
                           e.printStackTrace();
                       }
                   }
               }
               response.getWriter().print(jsonArray);
           }
       }

   }
}

The Next Question was from where this servlet is getting called.

The answer is browserDialog widget. Inside pathfield widget, it is calling browserDialog to show this tree structure.

Note: Go to browseDialog.js

Change this part:

[av_image src=’http://www.accunitysoft.com/wp-content/uploads/2016/09/Screen-Shot-2016-09-28-at-9.54.03-PM-300×137.png’ attachment=’1016′ attachment_size=’large’ align=’center’ styling=” hover=” link=” target=” caption=” font_size=” appearance=” overlay_opacity=’0.4′ overlay_color=’#000000′ overlay_text_color=’#ffffff’ animation=’no-animation’][/av_image]

Here is the updated browseDialog.js

CQ.CustomBrowseDialog = CQ.Ext.extend(CQ.Dialog, {

    /**
     * The browse dialog's tree panel.
     * @private
     * @type CQ.Ext.tree.TreePanel
     */
    treePanel: null,

    /**
     * The browse dialog's browse field.
     * @private
     * @type CQ.form.BrowseField
     */
    browseField: null,

    initComponent: function(){
       CQ.CustomBrowseDialog.superclass.initComponent.call(this);
    },

    /**
     * Selects the specified path in the tree.
     * @param {String} path The path to select
     */
    loadContent: function(path) {
        if (typeof path == "string") {
            this.path = path;
            this.treePanel.selectPath(path,"name");
            if(this.parBrowse){
                // reload paragraph store
                this.paraProxy.api["read"].url = CQ.HTTP.externalize(path, true) + ".paragraphs.json";
                this.paraStore.reload();
            }
        }
    },

    /**
     * Returns the path of the selected tree node (or an empty string if no
     * tree node has been selected yet).
     * @return {String} The path
     */
    getSelectedPath: function() {
        try {
            return this.treePanel.getSelectionModel().getSelectedNode().getPath();
        } catch (e) {
            return "";
        }
    },

    /**
     * Returns the anchor of the selected paragraph (or an empty string if
     * no paragraph has been selected yet).
     * @return {String} The anchor
     */
    getSelectedAnchor: function() {
        try {
            var anchorID = this.data.getSelectedRecords()[0].get("path");
            anchorID = anchorID.substring(anchorID.indexOf("jcr:content")
                    + "jcr:content".length + 1);
            return anchorID.replace(/\//g, "_").replace(/:/g, "_");
        } catch (e) {
            return "";
        }
    },

    constructor: function(config){

        var treeRootConfig = CQ.Util.applyDefaults(config.treeRoot, {
            "name": "content",
            "text": CQ.I18n.getMessage("Site"),
            "draggable": false,
            "singleClickExpand": true,
            "expanded":true
        });

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

        this.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": 200,
            "autoScroll": true,
            "containerScroll": true,
            "root": new CQ.Ext.tree.AsyncTreeNode(treeRootConfig),
            "loader": new CQ.Ext.tree.TreeLoader(treeLoaderConfig),
            "defaults": {
                "draggable": false
            }
        });

        var width = CQ.themes.BrowseDialog.WIDTH;
        var items = this.treePanel;

        if (config.parBrowse) {
            this.treePanel.on("click", this.onSelectPage.createDelegate(this));

            // Paragraph store
            var reader = new CQ.Ext.data.JsonReader({
                "id":            "path",
                "root":          "paragraphs",
                "totalProperty": "count",
                "fields":        [ "path", "html" ]
            });
            this.paraProxy = new CQ.Ext.data.HttpProxy({
                "url": "/"
            });
            this.paraStore = new CQ.Ext.data.Store({
                "proxy":    this.paraProxy,
                "reader":   reader,
                "autoLoad": false
            });

            // Paragraph template
            var paraTemplate = new CQ.Ext.XTemplate(
                '<tpl for=".">',
                    '<div class="cq-paragraphreference-paragraph">{html}</div>',
                '</tpl>'
            );

            // Paragraph view
            this.data = new CQ.Ext.DataView({
                "id": "cq-paragraphreference-data",
                "region": "center",
                "store": this.paraStore,
                "tpl": paraTemplate,
                "itemSelector": "div.cq-paragraphreference-paragraph",
                "selectedClass": "cq-paragraphreference-selected",
                "singleSelect": true,
                "style": { "overflow": "auto" }
            });

            // init dialog width and fields
            width = 550;
            items = new CQ.Ext.Panel({
                "border":false,
                "layout": "border",
                "items": [ this.treePanel, this.data ]
            });
        }

        CQ.Util.applyDefaults(config, {
            "title": CQ.I18n.getMessage("Select Path"),
            "closable": true,
            "width": width,
            "height": CQ.themes.BrowseDialog.HEIGHT,
            "minWidth": CQ.themes.BrowseDialog.MIN_WIDTH,
            "minHeight": CQ.themes.BrowseDialog.MIN_HEIGHT,
            "resizable": CQ.themes.BrowseDialog.RESIZABLE,
            "resizeHandles": CQ.themes.BrowseDialog.RESIZE_HANDLES,
            "autoHeight": false,
            "autoWidth": false,
            "cls":"cq-browsedialog",
            "ok": function() { this.hide(); },
            "buttons": CQ.Dialog.OKCANCEL,
            "items": items
        });
       CQ.CustomBrowseDialog.superclass.constructor.call(this, config);
    },

    /**
     * @private
     */
    onSelectPage: function(node, event) {
        this.paraProxy.api["read"].url = CQ.HTTP.externalize(node.getPath() + ".paragraphs.json", true);
        this.paraStore.reload();
    }
});

CQ.Ext.reg('recipebrowsedialog',CQ.CustomBrowseDialog);

We can’t make this change in the /libs section. So, I made my own xtype as productPathfield and add a custom browseDialog in pathfield.js with this modification.

Note: xtype pathfield doesn’t fulfill my requirements so needed to change it with productPathfield.

CQ.form.CustomPathField = CQ.Ext.extend(CQ.Ext.form.ComboBox, {

    /**
     * Remembers the last value when the last key up happened.
     * @type String
     * @private
     */
    lastValue: null,

    /**
     * The ID of the delayed search interval.
     * @type Number
     * @private
     */
    searchIntervalId: 0,

    /**
     * The panel holding the link-browser.
     * @type CQ.BrowseDialog
     * @private
     */
    browseDialog: null,

    /**
     * Returns the anchor of the selected paragraph (or an empty string if
     * no paragraph has been selected yet).
     * @return {String} The anchor
     */
    getParagraphAnchor: function() {
        return this.browseDialog.getSelectedAnchor();
    },

    /**
     * Checks if the current path is quoted. If yes the new value is decorated
     * with quotes as well.
     * @private
     */
    adjustNewValue: function(currentValue, newValue) {
        if (/^path:"/.test(currentValue)) {
            // current value starts with quotes: decorate with quotes
            // (add final quotes even if they do not exist yet - otherwise
            // the triggered search would fail ('path:"/content')
            newValue = '"' + newValue + '"';
        }
        return newValue;
    },

    /**
     * Executed on key up in the control.
     * - Checks if its value matches a path. If yes, request .pages.json
     * @private
     */
    keyup: function(comp, evt) {
        var currentValue = this.getRawValue();

        var key = evt.getKey();
        if (key == 13) {
            // [enter] hit
            this.fireEvent("search", this, currentValue);
        }

        if (currentValue == this.lastValue) {
            // value did not change (key was arrows, ctrl etc.)
            return;
        }
        this.lastValue = currentValue;

        var path = currentValue;

        if (/^\//.test(path) && /\/$/.test(path)) {
            // path starts with a slash: ignore non-absolute path (#29745)
            // path ends with a slash: request path.pages.json
            if (path == "/") {
                path = this.rootPath ? this.rootPath : "/";
            }
            else {
                // remove final slash:
                path = path.replace(/\/$/, "");
            }
            this.loadStore(CQ.shared.HTTP.encodePath(path));
        }
        else if (this.searchDelay) {
            window.clearTimeout(this.searchIntervalId);
            var pc = this;
            this.searchIntervalId = window.setTimeout(function() {
                pc.fireEvent("search", pc, currentValue);
            }, this.searchDelay);
        }

    },

    /**
     * Reloads the autocompletion store with a new URL.
     * @private
     */
    loadStore: function(path) {
        this.store.proxy.api["read"].url = path + ".pages.json";
        this.store.reload();
    },

    /**
     * The trigger action of the TriggerField, creates a new BrowseDialog
     * if it has not been created before, and shows it.
     * @private
     */
    onTriggerClick : function() {
        if (this.disabled) {
            return;
        }
        // lazy creation of browse dialog
        if (this.browseDialog == null || this.modeless) {
            function okHandler() {
                var path = this.getSelectedPath();
                var anchor = this.parBrowse ? this.getSelectedAnchor() : null;

                var value;
                if (anchor) {
                    value = CQ.Util.patchText(this.pathField.parLinkPattern, [path, anchor]);
                } else {
                    value = CQ.Util.patchText(this.pathField.linkPattern, path);
                }
                if (this.pathField.suffix) {
                    value += this.pathField.suffix;
                }

                this.pathField.setValue(value);

                this.pathField.fireEvent("dialogselect", this.pathField, path, anchor);
                this.hide();
            }

            var browseDialogConfig = CQ.Util.applyDefaults(this.browseDialogCfg, {
                ok: okHandler,
                // pass this to the BrowseDialog to make in configurable from 'outside'
                parBrowse: this.parBrowse,
                treeRoot: this.treeRoot,
                treeLoader: this.treeLoader,
                listeners: {
                    hide: function() {
                        if (this.pathField) {
                            this.pathField.fireEvent("dialogclose");
                        }
                    }
                },
                loadAndShowPath: function(path) {
                    this.path = path;
                    // if the root node is the real root, we need an additional slash
                    // at the begining for selectPath() to work properly
                    if (this.pathField.rootPath == "" || this.pathField.rootPath == "/") {
                        path = "/" + path;
                    }

                    var browseDialog = this;
                    var treePanel = this.treePanel;

                    // what to do when selectPath worked
                    function successHandler(node) {
                        // ensureVisible fails on root, ie. getParentNode() == null
                        if (node.parentNode) {
                            node.ensureVisible();
                        }
                        if (browseDialog.parBrowse) {
                            browseDialog.onSelectPage(node);
                        }
                    }

                    // string split helper function
                    function substringBeforeLast(str, delim) {
                        var pos = str.lastIndexOf(delim);
                        if (pos >= 0) {
                            return str.substring(0, pos);
                        } else {
                            return str;
                        }
                    }

                    // try to handle links created by linkPattern/parLinkPattern,
                    // such as "/content/foo/bar.html#par_sys"; needs to try various
                    // cut-offs until selectPath works (eg. /content/foo/bar)
                    // 1) try full link (path)
                    treePanel.selectPath(path, null, function(success, node) {
                        if (success && node) {
                            successHandler(node);
                        } else {
                            // 2) try and split typical anchor from (par)linkPattern
                            path = substringBeforeLast(path, "#");

                            treePanel.selectPath(path, null, function(success, node) {
                                if (success && node) {
                                    successHandler(node);
                                } else {
                                    // 3) try and split typical extension from (par)linkPattern
                                    path = substringBeforeLast(path, ".");

                                    treePanel.selectPath(path, null, function(success, node) {
                                        if (success && node) {
                                            successHandler(node);
                                        }
                                    });
                                }
                            });
                        }
                    });
                },
                pathField: this
            });

            // fix dialog width for par browse to include 3 cols of pars
            if (this.parBrowse) {
                browseDialogConfig.width = 570;
            }

            // build the dialog and load its contents
            this.browseDialog = new CQ.CustomBrowseDialog(browseDialogConfig);
        }

        this.browseDialog.loadAndShowPath(this.getValue());

        this.browseDialog.show();
        this.fireEvent("dialogopen");
    },

    constructor : function(config){
        // set default values
        // done here, because it is already used in below applyDefaults
        if (typeof config.rootTitle === "undefined") {
            config.rootTitle = config.rootPath || CQ.I18n.getMessage("Websites");
        }
        if (typeof config.rootPath === "undefined") {
            config.rootPath = "/content";
        }
        var rootName = config.rootPath;
        // the root path must not include a leading slash for the root tree node
        // (it's added automatically in CQ.Ext.data.Node.getPath())
        if (rootName.charAt(0) === "/") {
            rootName = rootName.substring(1);
        }
        if (typeof config.predicate === "undefined") {
            config.predicate = "siteadmin";
        }
        if (typeof config.showTitlesInTree === "undefined") {
            config.showTitlesInTree = true;
        }

        var pathField = "path";
        if (config.escapeAmp) {
            pathField = "escapedPath";
            delete config.escapeAmp;
        }

        CQ.Util.applyDefaults(config, {
            linkPattern: config.parBrowse ? "{0}.html" : "{0}",
            parLinkPattern: "{0}.html#{1}",

            tpl: new CQ.Ext.XTemplate(
                '<tpl for=".">',
                    '<div ext:qtip="{tooltip}" class="x-combo-list-item">',
                        '<span class="cq-pathfield-completion-list-name">{label}</span>',
                        '<span class="cq-pathfield-completion-list-title">{title}</span>',
                    '</div>',
                '</tpl>'),
            displayField: pathField,
            typeAhead: true,
            searchDelay: 200,
            suffix:"",
            mode: 'local',
            selectOnFocus:true,
            enableKeyEvents: true,
            validationEvent: false,
            validateOnBlur: false,
            // show a search icon
            triggerClass: "x-form-search-trigger",
            treeRoot: {
                name: rootName,
                // label for the root
                text: config.rootTitle
            },
            treeLoader: {
                dataUrl: CQ.shared.HTTP.getXhrHookedURL(CQ.Util.externalize(config.rootPath + ".ext.json")),
                baseParams: {
                    predicate: config.predicate,
                    "_charset_": "utf-8"
                },
                // overwriting method to be able to intercept node labeling
                createNode: function(attr) {
                    if (!config.showTitlesInTree) {
                        // no labled resources, use plain node name for tree nodes
                        attr.text = attr.name;
                    }
                    return CQ.Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
                },
                // overwriting method to fix handling of array params
                // (needed for config.predicate string array case)
                getParams: function(node) {
                    var params = this.baseParams;
                    params.node = node.id;
                    return CQ.Ext.urlEncode(params);
                },
                listeners: {
                    beforeLoad: function(loader, node) {
                        this.dataUrl = node.getPath() + ".ext.json";
                    }
                }
            }
        });

        // store for autocompletion while typing
        if (!(config.store instanceof CQ.Ext.data.Store)) {
            var storeConfig = CQ.Util.applyDefaults(config.store, {
                // URL for proxy is set dynamically based on current path in loadStore()
                proxy: new CQ.Ext.data.HttpProxy({
                    url: "/",
                    method:"GET"
                }),
                baseParams: {
                    predicate: config.predicate
                },
                "reader": new CQ.Ext.data.JsonReader(
                    {
                        "totalProperty": "results",
                        "root": "pages",
                        "id": "path"
                    },
                    CQ.Ext.data.Record.create([
                        {
                            "name": "label",
                            "convert": function(v, rec) {return CQ.shared.XSS.getXSSValue(rec.label);}
                        },
                        {
                            "name": "title",
                            "mapping": CQ.shared.XSS.getXSSPropertyName("title")
                        },
                        {
                            "name": pathField
                        },
                        {
                            "name": "tooltip",
                            // have to encode this twice because the template decodes the value before 
                            // injecting it into the tooltip div
                            "convert": function(v, rec) {return _g.Util.htmlEncode(_g.Util.htmlEncode(rec.path));}
                        }
                    ])
                )
            });
            config.store = new CQ.Ext.data.Store(storeConfig);
        }
        this.store = config.store;

        CQ.form.CustomPathField.superclass.constructor.call(this, config);
    },

    initComponent : function(){
        CQ.form.CustomPathField.superclass.initComponent.call(this);

        this.addListener("keyup", this.keyup, this);

        this.addEvents(
            /**
             * @event search
             * Fires when the enter key is hit or after the user stopped typing.
             * The period between the last key press and the firing of the event
             * is specified in {@link #searchDelay}.
             * @param {CQ.form.CustomPathField} this
             * @param {String} value The current value of the field
             */
            'search',
            /**
             * @event dialogopen
             * Fires when the browse dialog is opened.
             * @param {CQ.form.CustomPathField} this
             */
            "dialogopen",
            /**
             * @event dialogselect
             * Fires when a new value is selected in the browse dialog.
             * @param {CQ.form.CustomPathField} this
             * @param {String} path The path selected in the tree of the browse dialog
             * @param {String} anchor The paragraph selected in the browse dialog (or null)
             */
            "dialogselect",
            /**
             * @event dialogclose
             * Fires when the browse dialog is closed.
             * @param {CQ.form.CustomPathField} this
             */
            "dialogclose"
        );
        
        // register component as drop target
        CQ.WCM.registerDropTargetComponent(this);
    },
    
    getDropTargets : function() {
        var pathFieldComponent = this;
        var target = new CQ.wcm.EditBase.DropTarget(this.el, {
            "ddAccept": "*/*",
            "notifyDrop": function(dragObject, evt, data) {
                if (dragObject && dragObject.clearAnimations) {
                    dragObject.clearAnimations(this);
                }
                if (data && data.records && data.records[0]) {
                    var pathInfo = data.records[0].get("path");
                    if (pathInfo) {
                        pathFieldComponent.setValue(pathInfo);
                        return true;
                    }
                }
                return false;
            }
        });
        target.groups["media"] = true;
        target.groups["s7media"] = true;
        target.groups["page"] = true;
        return [target];
    }
});

CQ.Ext.reg("productPathfield", CQ.form.CustomPathField);

After all the changes, we can see our desired results here:

[av_image src=’https://www.argildx.com/wp-content/uploads/2017/06/ProductPathField.png’ attachment=’1017′ attachment_size=’large’ align=’center’ styling=” hover=” link=” target=” caption=” font_size=” appearance=” overlay_opacity=’0.4′ overlay_color=’#000000′ overlay_text_color=’#ffffff’ animation=’no-animation’][/av_image]

Please leave your precious comments of what you think about this approach. Happy to learn better solution for the same problem.