Skip to content

Commit

Permalink
Support exposing methods of multiple mixins (of different class) on a…
Browse files Browse the repository at this point in the history
… single host object class
  • Loading branch information
Evgeny Poberezkin committed Sep 5, 2014
1 parent 25f6215 commit 7ea7946
Show file tree
Hide file tree
Showing 14 changed files with 346 additions and 117 deletions.
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "milo",
"version": "0.1.6",
"version": "0.1.6.1",
"homepage": "https://github.com/MailOnline/milo",
"authors": [
"MOL Technical <[email protected]>"
Expand Down
73 changes: 35 additions & 38 deletions lib/abstract/mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,19 @@ function Mixin(hostObject, proxyMethods) { // , other args - passed to init meth
* - [_createProxyMethods](#_createProxyMethods)
*/
_.extendProto(Mixin, {
_createProxyMethod: _createProxyMethod,
_createProxyMethods: _createProxyMethods
_createProxyMethod: _createProxyMethod, // deprecated, should not be used
_createProxyMethods: _createProxyMethods // deprecated, should not be used
});


/**
* ####Mixin instance methods####
* These methods should be called in host class declaration.
* ####Mixin class methods####
* These method should be called in host class declaration.
*
* - [addMethod](#Mixin$$addMethod)
* - [addMethods](#Mixin$$addMethods)
* - [useWith](#Mixin$$useWith)
*/
_.extend(Mixin, {
setInstanceKey: Mixin$$setInstanceKey,
addMethod: Mixin$$addMethod,
addMethods: Mixin$$addMethods
useWith: Mixin$$useWith
});


Expand Down Expand Up @@ -120,46 +117,45 @@ function _createProxyMethods(proxyMethods, hostObject) {
/**
* Sets mixin instance property name on the host class
* Can be called only once
*
*
* @private
* @param {Function} this Mixin subclass (not instance)
* @param {Function} hostClass
* @param {String} instancePropertyName
* @param {String} instanceKey
*/
function Mixin$$setInstanceKey(hostClass, instancePropertyName) {
function Mixin_setInstanceKey(hostClass, method, instanceKey) {
check(hostClass, Function);
check(instancePropertyName, Match.IdentifierString);
check(instanceKey, Match.IdentifierString);

var prop = config.mixin.instancePropertiesMap
, instanceKeys = hostClass[prop] = hostClass[prop] || {};

var key = config.mixin.instancePropertyKey
if (hostClass[key])
throw new MixinError('Mixin: instance key is already set');
hostClass[key] = instancePropertyName;
if (instanceKeys[method.name])
throw new Error('Mixin: instance property for method with name '
+ method.name + ' is already set');

instanceKeys[method.name] = instanceKey;
}


/**
* Adds method of Mixin subclass to host class prototype.
*
* @private
* @param {Function} this Mixin subclass (not instance)
* @param {String} mixinMethodName name of method in Mixin subclass
* @param {String} hostMethodName (optional) name of created proxy method on host object, same if not specified
* @param {Object} hostObject object class, must be specified as the last parameter (2nd or 3rd)
*/
function Mixin$$addMethod(mixinMethodName, hostMethodName, hostClass) {
if (typeof hostMethodName == 'function') {
hostClass = hostMethodName;
hostMethodName = mixinMethodName;
}

check(hostClass, Function);
check(mixinMethodName, Match.IdentifierString);
check(hostMethodName, Match.IdentifierString);

function Mixin_addMethod(hostClass, instanceKey, mixinMethodName, hostMethodName) {
var method = this.prototype[mixinMethodName];
check(method, Function);

method = _wrapMixinMethod.call(this, method);
var wrappedMethod = _wrapMixinMethod.call(this, method);

_.defineProperty(hostClass.prototype, hostMethodName, method, _.WRIT);
_.defineProperty(hostClass.prototype, hostMethodName, wrappedMethod, _.WRIT);

Mixin_setInstanceKey(hostClass, method, instanceKey)
}


Expand All @@ -172,7 +168,7 @@ function Mixin$$addMethod(mixinMethodName, hostMethodName, hostClass) {
*/
function _wrapMixinMethod(method) {
return function() { // ,... arguments
var mixinInstance = _getMixinInstance.call(this);
var mixinInstance = _getMixinInstance.call(this, method.name);
return method.apply(mixinInstance, arguments);
}
}
Expand All @@ -186,29 +182,30 @@ function _wrapMixinMethod(method) {
* @private
* @return {Object}
*/
function _getMixinInstance() {
return this instanceof Mixin
? this
: this[this.constructor[config.mixin.instancePropertyKey]];
function _getMixinInstance(methodName) {
if (this instanceof Mixin) return this;
var instanceKeys = this.constructor[config.mixin.instancePropertiesMap]
return this[instanceKeys[methodName]];
}


/**
* Adds methods of Mixin subclass to host class prototype.
*
* @param {Function} this Mixin subclass (not instance)
* @param {Hash[String]|Array[String]} mixinMethods map of names of methods, key - host method name, value - mixin method name. Can be array.
* @param {Object} hostClass host object class; must be specified.
* @param {String} instanceKey the name of the property the host class instance will store mixin instance on
* @param {Hash[String]|Array[String]} mixinMethods map of names of methods, key - host method name, value - mixin method name. Can be array.
*/
function Mixin$$addMethods(mixinMethods, hostClass) {
function Mixin$$useWith(hostClass, instanceKey, mixinMethods) {
check(mixinMethods, Match.Optional(Match.OneOf([String], Match.ObjectHash(String))));

if (Array.isArray(mixinMethods))
mixinMethods.forEach(function(methodName) {
this.addMethod(methodName, hostClass);
Mixin_addMethod.call(this, hostClass, instanceKey, methodName, methodName);
}, this);
else
_.eachKey(mixinMethods, function(mixinMethodName, hostMethodName) {
this.addMethod(mixinMethodName, hostMethodName, hostClass);
Mixin_addMethod.call(this, hostClass, instanceKey, mixinMethodName, hostMethodName);
}, this);
}
4 changes: 1 addition & 3 deletions lib/components/c_class.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,7 @@ _.extendProto(Component, {
* Expose Messenger methods on Component prototype
*/
var MESSENGER_PROPERTY = '_messenger';
Messenger.addMethods(Messenger.defaultMethods, Component);
Messenger.setInstanceKey(Component, MESSENGER_PROPERTY);

Messenger.useWith(Component, MESSENGER_PROPERTY, Messenger.defaultMethods);


var COMPONENT_DATA_TYPE_PREFIX = 'x-application/milo-component';
Expand Down
3 changes: 1 addition & 2 deletions lib/components/c_facet.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,7 @@ _.extend(ComponentFacet, {
* Expose Messenger methods on Facet prototype
*/
var MESSENGER_PROPERTY = '_messenger';
Messenger.addMethods(Messenger.defaultMethods, ComponentFacet);
Messenger.setInstanceKey(ComponentFacet, MESSENGER_PROPERTY);
Messenger.useWith(ComponentFacet, MESSENGER_PROPERTY, Messenger.defaultMethods);


// initComponentFacet
Expand Down
11 changes: 9 additions & 2 deletions lib/components/c_facets/Events.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,21 @@ facetsRegistry.add(Events);
module.exports = Events;


/**
* Expose DOMEventsSource trigger method on Events prototype
*/
var MSG_SOURCE_KEY = '_domEventsSource'
DOMEventsSource.useWith(Events, MSG_SOURCE_KEY, ['trigger']);


/**
* Events facet instance method
* Initialzes facet, connects DOMEventsSource to facet's messenger
*/
function Events$init() {
ComponentFacet.prototype.init.apply(this, arguments);

var domEventsSource = new DOMEventsSource(this, { trigger: 'trigger' }, undefined, this.owner);
var domEventsSource = new DOMEventsSource(this, undefined, undefined, this.owner);
this._setMessageSource(domEventsSource);
_.defineProperty(this, '_domEventsSource', domEventsSource);
_.defineProperty(this, MSG_SOURCE_KEY, domEventsSource);
}
11 changes: 9 additions & 2 deletions lib/components/c_facets/Frame.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,24 @@ facetsRegistry.add(Frame);
module.exports = Frame;


/**
* Expose FrameMessageSource trigger method on Events prototype
*/
var MSG_SOURCE_KEY = '_messageSource';
FrameMessageSource.useWith(Frame, MSG_SOURCE_KEY, ['trigger']);


/**
* Frame facet instance method
* Initialzes facet, connects FrameMessageSource to facet's messenger
*/
function Frame$init() {
ComponentFacet.prototype.init.apply(this, arguments);

var messageSource = new FrameMessageSource(this, { trigger: 'trigger' }, undefined, this.owner);
var messageSource = new FrameMessageSource(this, undefined, undefined, this.owner);
this._setMessageSource(messageSource);

_.defineProperty(this, '_messageSource', messageSource);
_.defineProperty(this, MSG_SOURCE_KEY, messageSource);
}


Expand Down
10 changes: 8 additions & 2 deletions lib/components/c_facets/ModelFacet.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
var ComponentFacet = require('../c_facet')
, facetsRegistry = require('./cf_registry')
, Model = require('../../model')

, Mixin = require('../../abstract/mixin')
, _ = require('mol-proto');


Expand All @@ -23,10 +23,16 @@ facetsRegistry.add(ModelFacet);
module.exports = ModelFacet;


/**
* Expose Model class methods on ModelFacet
*/
Model.useWith(ModelFacet, 'm');


function ModelFacet$init() {
this.m = new Model(this.config.data, this);
ComponentFacet.prototype.init.apply(this, arguments);
this.m.proxyMethods(this); // Creates model's methods directly on facet
// this.m.proxyMethods(this); // Creates model's methods directly on facet
}


Expand Down
2 changes: 1 addition & 1 deletion lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ config({
componentRef: '___milo_component',
componentPrefix: 'milo_',
mixin: {
instancePropertyKey: '___mixin_instance'
instancePropertiesMap: '___mixin_instances'
},
template: {
compile: doT.compile
Expand Down
21 changes: 18 additions & 3 deletions lib/messenger/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ _.extendProto(Messenger, {
init: init, // called by Mixin (superclass)
destroy: Messenger$destroy,
on: Messenger$on,
once: _.partial(_Messenger_onWithOptions, { dispatchTimes: 1 }),
onSync: _.partial(_Messenger_onWithOptions, { sync: true }),
onAsync: _.partial(_Messenger_onWithOptions, { sync: false }),
once: Messenger$once,
onSync: Messenger$onSync,
onAsync: Messenger$onAsync,
onMessage: Messenger$on, // deprecated
off: Messenger$off,
offMessage: Messenger$off, // deprecated
Expand Down Expand Up @@ -213,6 +213,21 @@ function _Messenger_on(messages, subscriber) {
}


function Messenger$once(messages, subscriber) {
return _Messenger_onWithOptions.call(this, { dispatchTimes: 1 }, messages, subscriber);
}


function Messenger$onSync(messages, subscriber) {
return _Messenger_onWithOptions.call(this, { sync: true }, messages, subscriber);
}


function Messenger$onAsync(messages, subscriber) {
return _Messenger_onWithOptions.call(this, { sync: false }, messages, subscriber);
}


function _Messenger_onWithOptions(options, messages, subscriber) {
check(messages, Match.OneOf(String, [String], RegExp));
check(subscriber, Match.OneOf(Function, {
Expand Down
26 changes: 21 additions & 5 deletions lib/model/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Model.prototype.__proto__ = Model.__proto__;
_.extendProto(Model, {
path: Model$path,
get: Model$get,
proxyMessenger: proxyMessenger,
proxyMessenger: proxyMessenger, // deprecated, should not be used
proxyMethods: proxyMethods,
_prepareMessengers: _prepareMessengers,
_getHostObject: _getHostObject,
Expand All @@ -92,16 +92,16 @@ _.extendProto(Model, synthesize.modelMethods);
*/
_.extend(Model, {
Path: ModelPath,
registerWithDOMStorage: Model$$registerWithDOMStorage
registerWithDOMStorage: Model$$registerWithDOMStorage,
useWith: Model$$useWith
});


/**
* Expose Messenger methods on Facet prototype
*/
var MESSENGER_PROPERTY = '_messenger';
Messenger.addMethods(Messenger.defaultMethods, Model);
Messenger.setInstanceKey(Model, MESSENGER_PROPERTY);
Messenger.useWith(Model, MESSENGER_PROPERTY, Messenger.defaultMethods);


/**
Expand Down Expand Up @@ -160,6 +160,23 @@ function proxyMessenger(modelHostObject) {
}


var modelMethodsToProxy = ['path', 'get', 'set', 'del', 'splice', 'len', 'push', 'pop', 'unshift', 'shift'];


/**
* Expose model methods on
* See same method in Mixin class for parameters meaning
*
* @param {Function} hostClass
* @param {[type]} instanceKey
* @param {[type]} mixinMethods optional
*/
function Model$$useWith(hostClass, instanceKey, mixinMethods) {
mixinMethods = mixinMethods || modelMethodsToProxy;
Mixin.useWith.call(Model, hostClass, instanceKey, mixinMethods);
}


/**
* Model instance method.
* Proxy model methods to host object.
Expand All @@ -170,7 +187,6 @@ function proxyMethods(modelHostObject) {
modelHostObject = modelHostObject || this._hostObject;
Mixin.prototype._createProxyMethods.call(this, modelMethodsToProxy, modelHostObject);
}
var modelMethodsToProxy = ['path', 'get', 'set', 'del', 'splice', 'len', 'push', 'pop', 'unshift', 'shift'];


/**
Expand Down
3 changes: 1 addition & 2 deletions lib/model/m_path.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,7 @@ _.extend(ModelPath, {
* Expose Messenger methods on Facet prototype
*/
var MESSENGER_PROPERTY = '_messenger';
Messenger.addMethods(Messenger.defaultMethods, ModelPath);
Messenger.setInstanceKey(ModelPath, MESSENGER_PROPERTY);
Messenger.useWith(ModelPath, MESSENGER_PROPERTY, Messenger.defaultMethods);


/**
Expand Down
Loading

0 comments on commit 7ea7946

Please sign in to comment.