PDA

View Full Version : Ext.superExtend



alunharford
21 Jan 2008, 2:45 AM
I got annoyed with a few aspects of how classes are generally written in Ext, so I wrote my own mechanism. I had to write a lot of code to check that the config provided when instantiating a class is valid, and I wanted a way to turn that off in release mode (for performance).

I wrote some code to do that. It can be used like this to create a simple class:


//Creates a class called Ext.ux.MyPanel
Ext.superExtend("Ext.ux.MyPanel", {
superclass: Ext.Panel, //superclass - if none is specified, Ext.superExtend automatically uses Ext.override instead of Ext.extend
checkParams:
{
items: true, //Adds a check: config MUST contain 'items'
}
};

Or like this to create something more complicated:


//Creates a class called Ext.ux.MyPanel
Ext.superExtend("Ext.ux.MyPanel", {
superclass: Ext.Panel, //superclass - if none is specified, Ext.superExtend automatically uses Ext.override instead of Ext.extend
checkParams:
{
items: true, //Adds a check: config MUST contain 'items'
layout: false //Adds a check: config MUST NOT contain 'layout'
},
defaults: function(config)
{
return {
layout: 'border' //does Ext.applyIf(config, {layout: 'border'});
};
},
type: function(config)
{
//some code that runs before the superclass' constructor is called
},
postConstructor: function(config)
{
//some code that runs after the superclass' constructor is called
},
overrides:
{
//public function (would 'normally' be specified with Ext.extend)
foo: function()
{
Ext.Msg.alert('foo!');
}
}
});

And here is the code for Ext.superExtend. If you find bugs or have comments, let me know. I expect I'll refactor this code soon (it really needs it!), so doing fixes shouldn't be too hard :-)


Ext.superExtend = function(name, config)
{
if(typeof(name) === 'object' && typeof(config) === 'undefined')
{
config = name;
name = null;
}
var allowedParams = [];
var constructParam = function(configParams, defaultValue)
{
var result = null;
for(var i = 0, l = configParams.length; !result && i < l; i++)
{
result = config[configParams[i]];
}
allowedParams = allowedParams.concat(configParams);
return result || defaultValue;
};

config = config || {};

var supertype = constructParam(["supertype", "st", "superclass", "sc"], null);
var type = constructParam(["type"], Ext.emptyFn);
var postConstructor = constructParam(["postConstructor", "pc"], Ext.emptyFn);
var hash = constructParam(["checkParams", "cp"], {});
var overrides = constructParam(["overrides", "ov"], {});
var defaults = constructParam(["defaults"], function(){return {};});

if(typeof(defaults) !== 'function')
{
throw new Error("defaults passed into Ext.superExtend must be specified with a function. Failed in " + name);
}

var checkParamSpelling = function()
{
var isConfigItemAllowed = function(item)
{
return (allowedParams.findAll(function(param){ return param === item; }).length !== 0);
};
for(configItem in config)
{
if(!isConfigItemAllowed(configItem))
{
throw new Error("Input to Ext.superExtend contained an unexpected parameter");
}
}
};
checkParamSpelling();

var buildChecks = function()
{
var addCheck = function(variable, checks)
{
//Variable capture
var _variable = variable;
var _checks = checks;
var _hash = hash;

if(_hash[_variable])
{
return function(config)
{
if(typeof(config[_variable]) === 'undefined')
{
throw new Error(_variable + " cannot be null");
}
_checks(config);
};
} else {
return function(config)
{
if(typeof(config) !== 'undefined' && typeof(config[_variable]) !== 'undefined')
{
throw new Error(_variable + " cannot be set in the config");
}
_checks(config);
};
}
};

var configCheck = function(config)
{
if(!config)
{
throw new Error("config cannot be null");
}
};

var configCheckFn;
var checks = function(config){};
for(varname in hash)
{
if(hash[varname])
{
configCheckFn = configCheckFn || configCheck;
}
checks = addCheck(varname, checks);
}
return configCheckFn ? function(config)
{
configCheckFn(config);
checks(config);
} : checks;
};

var assignToVariableName = function(name, variable)
{
if(name)
{
Ext.namespace(name);
var parts = name.split(".");
var v = self;
for(var i = 0, l = parts.length - 1; i < l; i++)
{
v = v[parts[i]];
}
v[parts[parts.length - 1]] = variable;
}
};

var res;
var checks = buildChecks();
if(supertype)
{
res = function(config)
{
config = config || {};
checks.call(this, config);
Ext.applyIf(config, defaults(config));
type.call(this, config);
res.superclass.constructor.call(this, config);
postConstructor.call(this, config);
};
Ext.extend(res, supertype, overrides);
}
else
{
res = function(config)
{
config = config || {};
checks.call(this, config);
Ext.applyIf(config, defaults(config));
type.call(this, config);
postConstructor.call(this, config);
};
Ext.override(res, overrides);
}
assignToVariableName(name, res);
return res;
};

franckxx
21 Jan 2008, 4:01 AM
Hi,

It's very interesting, but i'm sorry i don't understand...
Have you got a online demo to see in action ?

Thx for share your work ;-)

alunharford
21 Jan 2008, 4:40 AM
Well there is no UI, so a demo is a bit tricky, but I'll try to explain with a better example. Suppose you want to define a class:


Ext.namespace('Ext.ux');

Ext.ux.MyPanel = function(config)
{
config = config || [];
if(typeof(config.items) === 'undefined')
{
throw new Error("items cannot be null");
}
if(typeof(config.layout) !== 'undefined')
{
throw new Error("layout cannot be set in the config");
}
Ext.applyIf(config, {
layout: 'border'
});
Ext.ux.MyPanel.superclass.constructor.call(this, config);
};

Ext.extend(Ext.ux.MyPanel, Ext.Panel, {
foo: function()
{
Ext.Msg.alert('foo!');
}
});

Ext.superExtend removes a lot of that code. Instead, you can just write:


Ext.superExtend("Ext.ux.MyPanel", {
superclass: Ext.Panel,
checkParams:
{
items: true,
layout: false
},
defaults: function(config)
{
return { layout: 'border' };
},
overrides:
{
foo: function()
{
Ext.Msg.alert('foo!');
}
}
});

krycek
21 Jan 2008, 5:13 AM
Great!!

Suggestions:



Ext.superExtend("Ext.ux.MyPanel", {
superclass: Ext.Panel,
mandatory: { items, another_one },
defaults: {
layout: 'border',
test1: 'opa'
},
overrides:
{
foo: function()
{
Ext.Msg.alert('foo!');
}
}
});


what do you think?

mystix
21 Jan 2008, 5:52 AM
Great!!

Suggestions:



Ext.superExtend("Ext.ux.MyPanel", {
superclass: Ext.Panel,
mandatory: { items, another_one },
defaults: {
layout: 'border',
test1: 'opa'
},
overrides: {
foo: function() {
Ext.Msg.alert('foo!');
}
}
});


what do you think?

you mean


Ext.superExtend("Ext.ux.MyPanel", {
superclass: Ext.Panel,
mandatory: [items, another_one],
defaults: {
layout: 'border',
test1: 'opa'
},
overrides: {
foo: function() {
Ext.Msg.alert('foo!');
}
}
});

:-/:-/

cimperia
21 Jan 2008, 9:59 AM
The suggestion of using


mandatory: [items, another_one],

would not offer the same functionality as the original


checkParams:
{
items: true, //Adds a check: config MUST contain 'items'
layout: false //Adds a check: config MUST NOT contain 'layout'
}
where an option must be either present (true) or absent (false).

You probably would need something like this:


mandatory: [items],
excluded: [layout],

But, yes the idea is a good one, and superExtend allows for some very elegant coding as well as adding some useful functionality.

tof
22 Jan 2008, 2:28 AM
The idea is good (less code is always welcome).

But it's dedicated to components ?

And it's hard to see (or maybe it's only me) where goes "constructor" function, and calls to superclasses.

I don't find very useful the "checkparams", but the "defaults", and "superclass" are !

So, I won't use this - but I find the idea really great, good work.

alunharford
22 Jan 2008, 4:03 AM
you mean


Ext.superExtend("Ext.ux.MyPanel", {
superclass: Ext.Panel,
mandatory: [items, another_one],
Unfortunately, this would just give you 'undefined' and 'undefined'.
I could get it to work with:

mandatory: ["items", "another_one"],
I think that's a really good idea.



defaults: {
layout: 'border',
test1: 'opa'
},
This has potential for a subtle bug. If you were to pass:

defaults: {
something: []
}
Then (with all sensible systems I can come up with) you're passing the same object to each class that is created. The result is that if one instance changes config.something, it will be changed for all instances (!).
I could clone all the items, but then somebody could pass (for example) a panel and things will probably get very messy.

alunharford
22 Jan 2008, 6:13 AM
I don't find very useful the "checkparams", but the "defaults", and "superclass" are !

I think the weakest part of Ext is that you can create a component and (because you forgot a parameter in the config) it'll fail at some random point - making debugging really hard.

mystix
22 Jan 2008, 5:51 PM
Unfortunately, this would just give you 'undefined' and 'undefined'.
I could get it to work with:

mandatory: ["items", "another_one"],
I think that's a really good idea.

whoops.. missed the quotes. my bad :">



This has potential for a subtle bug. If you were to pass:

defaults: {
something: []
}
Then (with all sensible systems I can come up with) you're passing the same object to each class that is created. The result is that if one instance changes config.something, it will be changed for all instances (!).
I could clone all the items, but then somebody could pass (for example) a panel and things will probably get very messy.
i disagree. think of it as passing a default configuration (the way the default config is passed for, say, a Panel with BorderLayout down to each region).

each object down the line receiving the default config would simply do a


Ext.applyIf(objectConfig, defaultConfig)

and voila. correct me if i'm wrong though cos the effects of my morning coffee have yet to kick in. ;)

alunharford
23 Jan 2008, 2:02 AM
i disagree. think of it as passing a default configuration (the way the default config is passed for, say, a Panel with BorderLayout down to each region).

each object down the line receiving the default config would simply do a


Ext.applyIf(objectConfig, defaultConfig)

and voila. correct me if i'm wrong though cos the effects of my morning coffee have yet to kick in. ;)

Unfortunately, unless I've misunderstood, you're passing a reference to a single array around if you do that. Consider:


var a = {};
var b = {};
var defaultConfig = { foo: [] };
Ext.applyIf(a, defaultConfig);
Ext.applyIf(b, defaultConfig);
a.foo.push('ouch');

You're passing a reference to the array around - b.foo contains 'ouch'. :(

mystix
23 Jan 2008, 3:06 AM
ahhh now i remember... the old pass-by-value and pass-by-reference problem...

oh well. my bad :">