PDA

View Full Version : Ext.Ext.extend(...) and Reference to 'this' in a Member Function



Cliff
23 Sep 2010, 1:37 PM
I subclassed a CombBox as follows:


DiaFileSelectCombo = Ext.extend(Ext.form.ComboBox, {
/**
* Use defaults and config members to initialize this subclass.
* @param config Members specific to this subclass:
* postLoadCallback Callback function used in loadContent().
* postLoadCallbackParams Params that get pass through to the postLoadCallback call.
*/
constructor: function(config) {
var defaults = {
id:'comboDefaultID',
mode: 'local',
store: new Ext.data.JsonStore({
id: 0,
fields: [
'fileName',
'displayTitle'
]
}),
displayField: 'displayTitle',
valueField: 'fileName',
triggerAction: 'all',
width: 200
};

var newConfig = Ext.applyIf(config, defaults);
DiaFileSelectCombo.superclass.constructor.call(this, newConfig);

/**
* Callback function meant to be set from the config @ construction and called from the load process. This is
* initialized here with an anonymous function that logs an error. It's meant to be reassigned from the config.
* (This would typically be the callback function used in the select handler for this object.)
*/
this.postLoadCallback = function(){
diaLog('ERROR: A post-load-callback function must be passed as part of the config when this.autoPopulateFirstFile is true.')
};
if (typeof newConfig.postLoadCallback == 'function'){
// If this does not occur, it is likely an error.
this.postLoadCallback = newConfig.postLoadCallback;
}
/**
* Additional params specified to be passed-thru to the call to postLoadCallback().
*/
this.postLoadCallbackParams = {};
if (typeof newConfig.postLoadCallbackParams == 'object'){
this.postLoadCallbackParams = newConfig.postLoadCallbackParams;
}
},
/**
* Loads this ComboBox with its contents.
*/
loadContent: function() {
var initThisCombo = function (xmlDomResourcesNode){
var fileList = [];
var resourceFiles = new ResourceFilesRoot(xmlDomResourcesNode);
if (resourceFiles.presentationFileChildren){
var file;
for (var i=0; i<resourceFiles.presentationFileChildren.length; i++){
file = resourceFiles.presentationFileChildren[i];
fileList[fileList.length] = {
'fileName': file.getFileToOpen(),
'displayTitle': file.displayTitle
};
}

this.getStore().loadData(fileList);// Blows up here, 'this' does not reference the instance of DiaFileSelectCombo

if (fileList.length > 0){
var params = fileList[0];
Ext.apply(params, this.postLoadCallbackParams);
//diaLog('Param toolbarSvc is valid:'+(params.toolbarSvc!=undefined));
this.setValue(params.fileName);
getXmlFromFile(params.fileName, this.postLoadCallback, params);
}
}
};
// This reads the XML file with the file list for the combo box ...
getXmlFromFile('xml/manifestDialectResourceFile.xml', initThisCombo);
}
});
This object is constructed in an Ext.onReady handler as follows:


var myCombo = new DiaFileSelectCombo({/*config stuff for this instance*/};
// do other stuff ...
// Now load the combo with contents of a local XML file ...
myCombo.loadContent();
The above call to loadContent() blows up at its call to ...
this.getStore().loadData(fileList);
... because 'this' does not refer to the instance of the subclassed Ext ComboBox. Okay, this isn't Java.
I see where 'this' refers to the global object, 'window', and that I could probably change that by using call() on the call to loadContent().

I could probably also make loadContent() the handler for afterrender and use the config.scope for the listener or createDelegate() to impact the 'this' reference.

The thing that I know works is adding an argument to loadContent() that is used in place of this in the original code above. Like this:


var myCombo = new DiaFileSelectCombo({/*config stuff for this instance*/};
myCombo.loadContent( myCombo );
Is there a best-way to do this that I don't know of? I'm leaning toward that afterrender option.
Thx.

troseberry
24 Sep 2010, 4:29 AM
Check out screencast done by Jay Garcia. He details out some things that might help you with your scope references in your class. http://tdg-i.com/364/abstract-classes-with-ext-js

Cliff
24 Sep 2010, 9:55 AM
troseberry, thanks for the tip. I did watch the video this morning and I ordered his book.

I just figured out what I was doing wrong. The this.getStore() on the subclassed ComboBox (below) was failing because 'this' did not contain a reference to the object instance at runtime, or so I thought. The problem had nothing to do with Ext or Ext.extend(). It had to do with the fact that the error occurred inside a function, initThisCombo() that is declared locally inside loadContent(). The initThisCombo() function is actually passed as a callback function (at bottom below). It's calling context, therefore, is not the same as loadContent(). In fact, I realized through logging that the 'this' reference in loadContent() is indeed what I wanted and expected. So, I took advantage of lexical scope inside loadContent() and added that first line of code (should be bolded below). That corrected the error inside initThisCombo(), as the calls to functions such as getStore() on Combox are now correct. The corrected source code is below:


DiaFileSelectCombo = Ext.extend(Ext.form.ComboBox, {
constructor: function(config) {
// ... construct this ...
},
loadContent: function() {
// Because initThisCombo() below is a callback fn(), its scope gets lost. Since it needs 'this', stash it locally ...
var thisCombo = this;
var initThisCombo = function (xmlDomResourcesNode){
var fileList = [];
// ... DO STUFF TO POPULATE fileList[] via JSON ...
// The original blow-up started here on the call to ...
// this.getStore().loadData(fileList);
// ... but that's changed now to use 'thisCombo' as assigned above ...
thisCombo.getStore().loadData(fileList);

if (fileList.length > 0){
// Take the 1st file in the combo and render the page based on its XML content ...
var params = fileList[0];
Ext.apply(params, thisCombo.postLoadCallbackParams);
thisCombo.setValue(params.fileName);
getXmlFromFile(params.fileName, thisCombo.postLoadCallback, params);
}
}
};
//initThisCombo.createDelegate(this); This did NOT work. Used the local var declared above to supply 'this'.

// This reads the XML file with the file list for the combo box ...
getXmlFromFile('xml/manifestDialectResourceFile.xml', initThisCombo);
});
I spent way too much time on this, but I'm glad I did. I could tell there was an important lesson in there that I needed to understand. There was.

darthwes
24 Sep 2010, 12:38 PM
FYI, you can address the problem with a closure.


var initThisCombo = function(thisCombo) {
return function(xmlDomResourcesNode) {
...
};
}(this);

This structure has helped me stay out of scope-problem-land.

steffenk
24 Sep 2010, 2:40 PM
var defaults = {
id:'comboDefaultID',

never do that, it prevents any double usage.