0
votes

I've searched a little about the scope problem and found some stuff but I'm not sure they work for my case. I'm really confused atm so I thought I might as well ask directly to the forums.

I have a tree panel. Inside that tree panel I have a dockedItem of xtype: 'textfield'. That textfield takes a value and uses it as a parameter in an ajax request via Ext.Ajax.Request inside a handler for the specialkey event. The server returns a JSON object on its response that gets decoded and contains folder IDs of the nodes of the tree. Inside the SUCCESS config of the ajax request there is a function I want to run that has to refer to the nodes of the tree via getNodeById(IdFromAjaxResponse) and expand them. The problem is that I do not know how to refer to those nodes. I tried this.getStore().getNodeById but it turns out 'this' refers to the window (I guess my container for the tree panel???). How do I refer to the tree panel or the tree store? I do not want to use direct referrals like getCmp etc.

Some code to help:

Ext.define('treePanel', {
    extend: 'Ext.tree.Panel',
    alias: 'widget.folderTreePanel',
    //title: 'Folder Tree',
    displayField: 'name',
    rootVisible: false,
    store: 'treeStore'
    dockedItems: {
        xtype: 'textfield',
        name: 'Search',
        allowBlank: true,
        enableKeys: true,
        listeners: {
            specialkey: function (txtField, e) { //This handler will essentially take the search value and return the path from the DB
                if (e.getKey() == e.ENTER){
                    var searchValue = txtField.getValue();
                    Ext.Ajax.request({
                        url: 'MyServlet',
                        params: {
                            caseType: 'search',
                            value: searchValue
                        },
                        success: function(response) {
                            response = Ext.decode(response.responseText);
                            var node, i=0;
                            expandFn = function () {
This is where I have a problem->node = this.up('treePanel').getStore().getNodeById(response.IDs[i].folderId);
                                node.expand();
                                i++;
                                if (i >= response.IDs.length-1) return;
                                expandFn();
                            }
                        }
                    });
                }
            }, scope: this
        }
    },
    columns: [{
        xtype: 'treecolumn',
        text: 'Folder Name',
        flex: 2,
        sortable: true,
        dataIndex: 'name'
    },{
        text: 'Folder ID',
        dataIndex: 'id',
        flex: 2,
        sortable: true
    }]
});

EDIT: I found the answer. Each event passes the instance that triggered it as an argument into the handler function. In this case txtField refers to the textfield itself. I can then traverse the inheritance and find the panel.

However there is a new problem now. The expandFn() gets called ONCE and then stops because 'node' is undefined. It is supposed to recurse until there are no more items in the response array. Again I think this is a scope problem but I'm really confused and I don't seem to see the goes wrong...

3

3 Answers

2
votes

The listeners config has a property scope. You can set that to the Tree Panel.

[Edit] Inside the specialkey listener, assign this to a variable. Inside the success callback, the scope is different from the one in the listener for specialkey. See the modified code below.

listeners: {
   scope: this,
   specialkey: function (txtField, e) { 
            var me = this;
            if (e.getKey() == e.ENTER){
                var searchValue = txtField.getValue();
                Ext.Ajax.request({
                    url: 'MyServlet',
                    params: {
                        caseType: 'search',
                        value: searchValue
                    },
                    success: function(response) {
                        response = Ext.decode(response.responseText);
                        var node, i=0;
                        expandFn = function () {
                            node = me.getStore().getNodeById(response.IDs[i].folderId);
                            node.expand();
                            i++;
                            if (i >= response.IDs.length-1) return;
                            expandFn();
                        }
                    }
                });
            }
}
1
votes

The problem is, that your scope this refers to the current scope when the tree panel is defined. You cannot set the scope to an instance of the tree panel when this tree panel is just about to be defined.

To solve this problem you can add a initComponent function to your tree panel and set the dockedItems config there:

Ext.define('treePanel', {
    extend: 'Ext.tree.Panel',

    // [...]

    initComponent: function() {
        this.dockedItems = {
            // [...]
        };

        this.callParent();
    }
});

The difference is that initComponent is a member function of your tree panel which is called automatically by the framework during the instantiation of the component. At this time you already have an instance this which you can use as the scope of your textfield's listener.

1
votes

It has since been more than a year and am now much more proficient with extjs and javascript basics in general. Here are some things that one should know to answer the question:

this -> in javascript always refers to the object that called the function in which we use it. In my case since the function was the success callback of the ajax request this could not possibly refer to the treepanel. To solve such problems it is good practice to set a reference to the object you want to be able to access at the top of the function. In my case I could do var treepanel = txtField.up('treePanel'); to get a reference to the panel.

expandFn -> First things first, expandFn is defined inside the success callback which is another function. This creates a closure and we don't want that. Closures provide some special functionality that might cause problems if we're not prepared to deal with it. Take expandFn and define it outside, in the treePanel's scope. The most basic problem here is that node = this.up('treePanel')... the this keyword once again does not refer to the txtField I thought it did. This is why we defined treePanel earlier. We can have access to the store through that. Also node.expand() takes time to complete, especially if the node isn't loaded and we have to make a server request. For that case the correct way to do the recursion is by adding a listener:

node.on('expand', Ext.bind(treePanel.expandFn, treePanel, [args]), treePanel, {single:true})

Some things to note here:

  • We must use Ext.bind if we want to pass DIFFERENT arguments to expandFn than those passed by the expand event by default. If we wrote node.on('expand', treePanel.expandFn, treePanel, {single:true}) then when the expand event fired it would fire expandFn but with its default arguments which according to the documentation are the expanded node itself and an eOpts event options object.
  • We add {single: true} to make sure expandFn will be fired only on the next 'expand' event. We don't want it to fire every time we expand a node.
  • This listener must be added BEFORE we use node.expand().

This answer cannot EXACTLY be applied to the supplied code above, but it explains the problems with it and what should one do to solve it.