blob: 8df6c29340319d3e6063c4770b38db28bd562098 [file] [log] [blame]
var $ = require('./jquery');
/**
* <p>Plays a similar role to other npm private encapsulation facilities, but
* exposes private members on `this` via per-instance bindings. A class
* definition can contain the following members:
* <ul>
* <li><code>init</code>: constructor/initializer function for an instance. It
* will be called when the class is instantiated via <code>new</code>. Fields
* can be initialized in this function. Private functions and events can also
* be defined within this function.
* <li><code>privates</code>: map of private functions or private static
* constants, with access to other members via <code>this</code>. These
* members are not publicly visible. This is equivalent to associating these
* members explicitly within <code>init</code>.
* <li><code>publics</code>: map of public functions, with access to other
* members via <code>this</code>. These members are publicly visible.
* <li><code>constants</code>: list of names of instance constants initialized
* in <code>init</code> to be exposed.
* <li><code>statics</code>: map of public static constants, accessible from
* the private context, the public context, and on the constructor function.
* <li><code>events</code>: list of event names, some of which can actually be
* a singleton map with the event name and a string of flags, or a map of
* event names to flags. Flags are those to
* <a href="https://api.jquery.com/jQuery.Callbacks/">jQuery Callbacks</a>,
* plus the "private" flag, which hides the event from the public interface
* entirely, and the "public" flag, which exposes the event trigger to the
* public interface.
* </ul>
*
* <p>Care should be taken not to be tempted to declare instance constants
* within <code>private</code>, as any instantiations done on the initial
* values is done at class definition time rather than class instantiation
* time. (As such, using that mechanism to declare private static constants does
* work.)
*/
module.exports = defineClass;
function defineClass(def) {
var constructor = function() {
var pthis = $.extend({}, def.privates, def.publics, def.statics);
var ifc = this;
if (def.events) {
if ($.isArray(def.events)) {
$.each(def.events, function(i, event) {
if ($.type(event) === 'string') {
defineEvent(pthis, ifc, event);
} else {
defineEventsFromObject(pthis, ifc, event);
}
});
} else {
defineEventsFromObject(pthis, ifc, def.events);
}
}
if (def.statics) {
$.extend(ifc, def.statics);
}
if (def.init) {
def.init.apply(pthis, arguments);
}
if (def.publics) {
polyProxy(ifc, pthis, def.publics);
}
if (def.constants) {
$.each(def.constants, function(i, constant) {
ifc[constant] = pthis[constant];
});
}
};
if (def.statics) {
$.extend(constructor, def.statics);
}
return constructor;
}
defineClass.innerClass = function(def) {
var init = def.init;
def.init = function(outer, constructorArgs) {
this.outer = outer;
init.apply(this, constructorArgs);
};
var InnerClass = defineClass(def);
return function() {
return new InnerClass(this, arguments);
};
};
function polyProxy(proxy, context, members) {
$.each(members, function(name, member) {
proxy[name] = $.proxy(member, context);
});
return proxy;
}
function filterProxy(proxy, context, nameFilter) {
$.each(context, function(name, member) {
if (nameFilter(name)) {
proxy[name] = $.proxy(member, context);
}
});
return proxy;
}
function defineEvent(pthis, ifc, name, flags) {
var dispatcher = $.Callbacks(flags);
//Use polyProxy on function that fires to add the callable syntactic sugar
var callableDispatcher = pthis[name] =
polyProxy($.proxy(dispatcher, 'fire'), dispatcher, dispatcher);
if (flags && flags.indexOf('private') > -1) {
return;
}
if (flags && flags.indexOf('public') > -1) {
ifc[name] = callableDispatcher;
} else {
ifc[name] = filterProxy({}, dispatcher, function(name) {
return name !== 'fire' && name !== 'fireWith';
});
}
}
function defineEventsFromObject(pthis, ifc, events) {
$.each(events, function(event, flags) {
defineEvent(pthis, ifc, event, flags);
});
}