248 lines
7.8 KiB
JavaScript
248 lines
7.8 KiB
JavaScript
|
dojo.provide("dojox.lang.oo.declare");
|
||
|
|
||
|
dojo.experimental("dojox.lang.oo.mixin");
|
||
|
|
||
|
// a copy of the version #7 (with makeDeclare() yet a drop-in replacement for dojo.declare())
|
||
|
|
||
|
(function(){
|
||
|
var d = dojo, oo = dojox.lang.oo, op = Object.prototype,
|
||
|
isF = d.isFunction, xtor = function(){}, extraNames, i,
|
||
|
|
||
|
each = function(a, f){ for(var i = 0, l = a.length; i < l; ++i){ f(a[i]); } },
|
||
|
|
||
|
error = function(cond, msg){ if(cond){ throw new Error("declare: " + msg); } },
|
||
|
|
||
|
mix = function(target, source, name){
|
||
|
var t = target[name], s = source[name];
|
||
|
return t !== s && s !== op[name] ? target[name] = s : 0;
|
||
|
},
|
||
|
|
||
|
mixName = function(target, source, name){
|
||
|
var t = mix(target, source, name);
|
||
|
if(isF(t)){ t.nom = name; }
|
||
|
},
|
||
|
|
||
|
mixer = function(target, source, mix){
|
||
|
for(var name in source){ mix(target, source, name); }
|
||
|
each(extraNames, function(name){ if(name in source) mix(target, source, name); });
|
||
|
},
|
||
|
|
||
|
collect = function(meta, base){
|
||
|
var m = base._meta, mb = meta.bases;
|
||
|
m && mb.push(m.bases);
|
||
|
mb.push(base);
|
||
|
};
|
||
|
|
||
|
for(i in {toString: 1}){ extraNames = []; break; }
|
||
|
extraNames = extraNames || ["hasOwnProperty", "valueOf", "isPrototypeOf",
|
||
|
"propertyIsEnumerable", "toLocaleString", "toString"];
|
||
|
|
||
|
oo.makeDeclare = function(before, after){
|
||
|
|
||
|
var chains = {constructor: "after"},
|
||
|
|
||
|
buildMethodList = function(meta, name){
|
||
|
var fs = [], mb = meta.bases, i, l, t, c, m, h;
|
||
|
for(i = 0, l = mb.length; i < l; ++i){
|
||
|
// the assignment on the next line is intentional
|
||
|
(t = (c = mb[i]) && (m = c._meta) && (h = m.hidden) ?
|
||
|
(name in h) && h[name] : c.prototype[name]) && fs.push(t);
|
||
|
}
|
||
|
// the assignment on the next line is intentional
|
||
|
(t = meta.hidden[name]) && fs.push(t);
|
||
|
// the next line works for inherited methods too
|
||
|
return chains[name] === "after" ? fs : fs.reverse();
|
||
|
},
|
||
|
|
||
|
inherited = function(name, args, a){
|
||
|
var c = this.constructor, m = c._meta, cache = c._cache, caller,
|
||
|
i, l, f, n, ch, s, x;
|
||
|
// crack arguments
|
||
|
if(typeof name != "string"){
|
||
|
a = args;
|
||
|
args = name;
|
||
|
name = "";
|
||
|
}
|
||
|
caller = inherited.caller;
|
||
|
n = caller.nom;
|
||
|
error(n && name && n !== name, "calling inherited() with a different name: " + name);
|
||
|
name = name || n;
|
||
|
ch = cache[name];
|
||
|
// get the cached method list
|
||
|
if(!ch){
|
||
|
error(!name, "can't deduce a name to call inherited()");
|
||
|
error(typeof chains[name] == "string", "chained method: " + name);
|
||
|
ch = cache[name] = buildMethodList(m, name);
|
||
|
}
|
||
|
// check the stack
|
||
|
do{
|
||
|
s = this._inherited, n = s.length - 1;
|
||
|
if(n >= 0){
|
||
|
x = s[n];
|
||
|
if(x.name === name && ch[x.pos] === caller && caller.caller === inherited){
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
// find the caller
|
||
|
for(i = 0, l = ch.length; i < l && ch[i] !== caller; ++i);
|
||
|
if(i == l){
|
||
|
// the assignment on the next line is intentional
|
||
|
this[name] === caller && (i = -1) || error(1, "can't find the caller for inherited()");
|
||
|
}
|
||
|
// the assignment on the next line is intentional
|
||
|
s.push(x = {name: name, start: i, pos: i});
|
||
|
}while(false);
|
||
|
f = ch[++x.pos];
|
||
|
try{
|
||
|
return f ? f.apply(this, a || args) : undefined;
|
||
|
}finally{
|
||
|
x.start == --x.pos && s.pop();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
before = before || [];
|
||
|
each(before, function(name){ chains[name] = "before"; });
|
||
|
after = after || [];
|
||
|
each(after, function(name){ chains[name] = "after"; });
|
||
|
|
||
|
return function(className, superclass, props){
|
||
|
var mixins, proto, i, l, t, f, ctor, hidden = {}, meta = {bases: []};
|
||
|
|
||
|
// crack parameters
|
||
|
if(typeof className != "string"){
|
||
|
props = superclass;
|
||
|
superclass = className;
|
||
|
className = "";
|
||
|
}
|
||
|
if(d.isArray(superclass)){
|
||
|
mixins = superclass;
|
||
|
superclass = mixins[0];
|
||
|
}
|
||
|
|
||
|
// build a prototype
|
||
|
if(superclass){
|
||
|
collect(meta, superclass);
|
||
|
if(mixins){
|
||
|
for(i = 1, l = mixins.length; i < l; ++i){
|
||
|
// the assignment on the next line is intentional
|
||
|
error(!(t = mixins[i]), "mixin #" + i + " is null");
|
||
|
collect(meta, t);
|
||
|
// delegation
|
||
|
xtor.prototype = superclass.prototype;
|
||
|
proto = new xtor;
|
||
|
mixer(proto, t.prototype, mix);
|
||
|
(ctor = function(){}).superclass = superclass;
|
||
|
ctor.prototype = proto;
|
||
|
superclass = proto.constructor = ctor;
|
||
|
}
|
||
|
}
|
||
|
// add props
|
||
|
xtor.prototype = superclass.prototype;
|
||
|
proto = new xtor;
|
||
|
}else{
|
||
|
proto = {};
|
||
|
}
|
||
|
|
||
|
// add props
|
||
|
// the assignment on the next line is intentional
|
||
|
mixer(proto, (meta.hidden = props || {}), mixName);
|
||
|
|
||
|
// flatten the base list and collect our chain instructions
|
||
|
meta.bases = meta.bases.concat.apply([], meta.bases);
|
||
|
|
||
|
// build chains and add them to the prototype
|
||
|
each(after.concat(before), function(name){
|
||
|
// the assignment on the next line is intentional
|
||
|
(proto[name] = function(){
|
||
|
var c = this.constructor, t = buildMethodList(c._meta, name), l = t.length,
|
||
|
f = function(){ for(var i = 0; i < l; ++i){ t[i].apply(this, arguments); } };
|
||
|
f.nom = name;
|
||
|
// the assignment on the next line is intentional
|
||
|
(c.prototype[name] = f).apply(this, arguments);
|
||
|
}).nom = name;
|
||
|
});
|
||
|
|
||
|
// add inherited to the prototype
|
||
|
proto.inherited = inherited;
|
||
|
|
||
|
// build ctor
|
||
|
t = buildMethodList(meta, "constructor");
|
||
|
ctor = function(){
|
||
|
this._inherited = [];
|
||
|
|
||
|
// perform the shaman's rituals of the original dojo.declare()
|
||
|
|
||
|
// 1) call two types of the preamble
|
||
|
var a = arguments, args = a, a0 = a[0], f, i, l;
|
||
|
// the assignment on the next line is intentional
|
||
|
a = a0 && (f = a0.preamble) && f.apply(this, a) || a;
|
||
|
// the assignment on the next line is intentional
|
||
|
a = (f = this.preamble) && f.apply(this, a) || a;
|
||
|
|
||
|
// 2) call the constructor with different parameters
|
||
|
for(i = 0, l = t.length - 1; i < l; ++i){
|
||
|
t[i].apply(this, a);
|
||
|
}
|
||
|
l >= 0 && t[i].apply(this, t[i] === ctor._meta.hidden.constructor ? args : a);
|
||
|
|
||
|
// 3) continue the original ritual: call the postscript
|
||
|
// the assignment on the next line is intentional
|
||
|
(f = this.postscript) && f.apply(this, args);
|
||
|
};
|
||
|
|
||
|
// build metadata on the constructor
|
||
|
ctor._meta = meta;
|
||
|
ctor._cache = {};
|
||
|
ctor.superclass = superclass && superclass.prototype;
|
||
|
|
||
|
proto.constructor = ctor;
|
||
|
ctor.prototype = proto;
|
||
|
|
||
|
// the assignment on the next line is intentional
|
||
|
className && d.setObject(proto.declaredClass = className, ctor);
|
||
|
|
||
|
return ctor; // Function
|
||
|
};
|
||
|
};
|
||
|
|
||
|
/*=====
|
||
|
// summary:
|
||
|
// Create a feature-rich constructor from compact notation
|
||
|
// className: String?:
|
||
|
// The optional name of the constructor (loosely, a "class")
|
||
|
// stored in the "declaredClass" property in the created prototype
|
||
|
// superclass: Function|Function[]:
|
||
|
// May be null, a Function, or an Array of Functions. If an array,
|
||
|
// the first element is used as the prototypical ancestor and
|
||
|
// any following Functions become mixin ancestors.
|
||
|
// props: Object:
|
||
|
// An object whose properties are copied to the created prototype.
|
||
|
// Add an instance-initialization function by making it a property
|
||
|
// named "constructor".
|
||
|
// description:
|
||
|
// Create a constructor using a compact notation for inheritance and
|
||
|
// prototype extension.
|
||
|
//
|
||
|
// Mixin ancestors provide a type of multiple inheritance. Prototypes of mixin
|
||
|
// ancestors are copied to the new class: changes to mixin prototypes will
|
||
|
// not affect classes to which they have been mixed in.
|
||
|
//
|
||
|
// "className" is cached in "declaredClass" property of the new class.
|
||
|
//
|
||
|
// example:
|
||
|
// | dojox.lang.oo.declare("my.classes.bar", my.classes.foo, {
|
||
|
// | // properties to be added to the class prototype
|
||
|
// | someValue: 2,
|
||
|
// | // initialization function
|
||
|
// | constructor: function(){
|
||
|
// | this.myComplicatedObject = new ReallyComplicatedObject();
|
||
|
// | },
|
||
|
// | // other functions
|
||
|
// | someMethod: function(){
|
||
|
// | doStuff();
|
||
|
// | }
|
||
|
// | });
|
||
|
=====*/
|
||
|
oo.declare = oo.makeDeclare();
|
||
|
})();
|