*/
var Y = require('Y').Y
+, core = require('Y/core')
, Emitter = require('Y/modules/y.event').Emitter
, unwrap = require('Y/types/function').unwrap
, objToString = _Object[P].toString
, classToString = function toString(){ return this.className+"()"; }
+, classMagic = [ '__static__', '__mixins__', '__emitter__', '__bases__', '__initialise__' ]
+, mixinSkip = [ '__mixin_skip__', 'onMixin', 'onInit', 'init' ].concat(classMagic)
,
/**
* @private
* Private delegating constructor. This function must be redefined for every
- * new class to prevent shared state. All construction is actually done in
- * the methods initialise and init.
+ * new class to prevent shared state.
*
* Fires `create` event prior to initialisation if not subclassing.
+ *
+ * Fires `init` event after calling init(). nb. init() is fired
+ * for proper instances of the class, and instances of subclasses (but
+ * not for the instance created when subclassing).
*/
ConstructorTemplate =
exports['ConstructorTemplate'] =
function ConstructorTemplate() {
- var cls = arguments.callee
- , instance = this;
+ var k, v
+ , cls = arguments.callee
+ , instance = this
+ ;
// Not subclassing
if ( unwrap(cls.caller) !== Y.fabricate ) {
+ // Perform actions that should only happen once in Constructor
+ instance.__emitter__ = new Emitter(instance, cls);
+
cls.fire('create', instance, {
'instance' : instance,
'cls' : cls,
'args' : Y(arguments)
});
- if ( instance.initialise )
- return instance.initialise.apply(instance, arguments);
+ var initialise = instance.__initialise__;
+ if ( isFunction(initialise) )
+ return initialise.apply(instance, arguments);
}
return instance;
,
/**
- * Fires `init` event after calling init(). Note that events are fired
- * for proper instances of the class and instances of subclasses (but
- * not for the instance created when subclassing).
+ * Class must be captured in a closure so we can dispatch events correctly both
+ * on a new instance and off a call from a subclass by invoking
+ * MyClass.init.apply(instance, args).
*/
createInitialise =
exports['createInitialise'] =
-function createInitialise(NewClass){
+function createInitialise(cls){
function initialise(){
var instance = this
- , init = NewClass.prototype.init
+ , binds = cls.fn.__bind__ // list of names to bind
;
- instance.__emitter__ = new Emitter(instance, NewClass);
+ if (binds)
+ Y.bindAll(instance, binds);
- if (init) {
+ var init = cls.fn.init;
+ if ( isFunction(init) ) {
var result = init.apply(instance, arguments);
- if (result) instance = result;
+ if (result) instance = result; // XXX: I think this needs to go away
}
instance.fire('init', instance, {
'instance' : instance,
- 'cls' : NewClass,
+ 'cls' : cls,
'args' : Y(arguments)
});
return instance;
}
return Y(initialise);
-}
-;
+};
+function notClassMagic(v, k){
+ return classMagic.indexOf(k) === -1;
+}
NewClass.prototype = NewClass.fn = prototype;
// Fix Constructors
- NewClass.__super__ = SuperClass; // don't override NewClass.constructor -- it should be Function
+ NewClass.__super__ = SuperClass; // don't override NewClass.constructor -- it should be Function
+ NewClass.__bases__ = NewClass.fn.__bases__ = [SuperClass].concat( SuperClass.__bases__ || [ Class, Object ] );
prototype.constructor = prototype.__class__ = NewClass;
- NewClass.init = prototype.initialise = createInitialise(NewClass);
+ NewClass.init = prototype.__initialise__ = createInitialise(NewClass);
// Add class emitter
var ParentEmitter = (Parent.__emitter__ ? Parent : ClassFactory)
, ClassEmitter = NewClass.__emitter__ = new Emitter(NewClass, ParentEmitter)
;
+ // Record for metaprogramming
+ KNOWN_CLASSES[className] = NewClass;
+
// Either invoke body constructor...
if ( isFunction(members) ) {
members.call(prototype, NewClass);
// Or add new instance methods
- } else for (var k in members) {
- if ( hasOwn.call(members, k) )
- setDesc(prototype, k, getDesc(members,k));
+ } else {
+ var mixins = members.__mixins__
+ , statics = members.__static__
+ ;
+
+ if (mixins && hasOwn.call(members,'__mixins__'))
+ mixin(NewClass, mixins);
+
+ if (statics)
+ core.descriptors( NewClass, core.filter(statics, notClassMagic) );
+
+ core.descriptors( prototype, core.filter(members, notClassMagic) );
+ // for (var k in members) {
+ // if ( hasOwn.call(members,k) && classMagic.indexOf(k) === -1 )
+ // setDesc(prototype, k, getDesc(members,k));
+ // }
+
+ NewClass.__super__ = SuperClass;
+ prototype.constructor = prototype.__class__ = NewClass;
}
- // Record for metaprogramming
- KNOWN_CLASSES[className] = NewClass;
// Notify parent of the subclass
ParentEmitter.fire('subclass',
Class.__emitter__ = new Emitter(Class);
Class.fn = Class.prototype;
Class.fn.__class__ = Class.fn.constructor = Class;
+Class.fn.__bases__ = Class.__bases__ = [ Object ];
Class.className = Class.fn.className = "Class";
/* Class Methods */
});
+function lookupClass(name){
+ return (typeof name === "function") ? name : KNOWN_CLASSES[name];
+}
+
+var
+
+/**
+ * Everybody's favourite party metaclass!
+ */
+Mixin =
+exports['Mixin'] =
+new Class('Mixin', Class, {
+
+ /**
+ * Keys to be skipped by mixIntoClass()
+ */
+ __mixin_skip__ : mixinSkip,
+
+ __mixin_skip : function __mixin_skip(k){
+ var skip = this.__mixin_skip__;
+ return !hasOwn.call(this,k) || (skip && skip.indexOf(k) !== -1);
+ },
+
+ __static__ : {
+ mixInto : function mixIntoClass(cls){
+ var mxn = this;
+ return mixin(cls, mxn);
+ }
+ }
+
+})
+,
+
+/**
+ * Mixes a Mixin into another Class.
+ */
+mixin =
+exports['mixin'] =
+function mixin(cls, mxn){
+ var proto = cls.fn
+ , mxns = (Y.isArray(mxn) ? mxn : Y(arguments, 1))
+ ;
+ mxns.forEach(function(mxn){
+ mxn = (typeof mxn === "string") ? lookupClass(mxn) : mxn;
+
+ if ( !mxn )
+ throw new Error('Cannot mix in non-object! '+mxn);
+
+ var mproto = (typeof mxn === "function") ? mxn.fn : mxn
+ , bases = cls.__bases__
+ , statics = mproto.__static__
+ , onInit = mproto.onInit
+ ;
+
+ core.extend(proto, core.filter(mproto, mproto.__mixin_skip, mproto));
+
+ if (bases)
+ bases.push(mxn);
+
+ // Add mixin statics to class constructor
+ if (statics)
+ core.extend(cls, statics);
+
+ // Register onInit to fire whenever a new instance is initialized
+ if ( hasOwn.call(mproto,'onInit') && isFunction(onInit) )
+ cls.addEventListener('init', onInit);
+
+ // Fire the mixin event on this Mixin
+ if (mxn instanceof Mixin)
+ mxn.fire('mixin', cls, { 'mixin':mxn, 'into':cls });
+ });
+ return cls;
+}
+;
+
+Mixin.addEventListener('subclass',
+ function onMixinSubclass(evt){
+ var d = evt.data
+ , mxn = d.child
+ , members = d.members
+ , onMixin = members.onMixin
+ ;
+
+ if ( hasOwn.call(members,'onMixin') && isFunction(onMixin) )
+ mxn.addEventListener('mixin', onMixin);
+ });
+
+
// Expose
exports['Class'] =
exports['subclass'] = Class;
exports['instantiate'] = instantiate;
exports['fabricate'] = Y.fabricate;
-
+exports['lookupClass'] = lookupClass;