Skip to content

Instantly share code, notes, and snippets.

@datchley
Last active December 23, 2015 11:01
Show Gist options
  • Save datchley/6625684 to your computer and use it in GitHub Desktop.
Save datchley/6625684 to your computer and use it in GitHub Desktop.
Inspired by functional mixins in frameworks like Twitter's Flight, I thought I'd work up a version that handled both functional and object based mixins, and with variable arguments instead of having to pass in an array of mixins to add.
var mixin = function(target /*, mixins */) {
var args = [].slice.call(arguments, 1);
target.mixins = target.hasOwnProperty('mixins') ? target.mixins : [];
args.forEach(function(mixin) {
if (target.mixins.indexOf(mixin) === -1) {
if (typeof mixin === 'function') {
// Functional mixin using 'call'
mixin.call(target);
}
else if (typeof mixin === 'object') {
// Simply extend target using object
for (prop in mixin) {
if (!target.hasOwnProperty(prop)) {
target[prop] = mixin[prop];
}
}
}
else {
throw new Error("mixin: can't mix an object of type" + typeof mixin);
}
target.mixins.push(mixin);
}
});
}
// Functional mixin
var withHello = function() {
this.hello = function() { console.log("Hello!"); }
};
var withActions = function() {
// Run a function after another function
this.after = function(name, fn) {
if (typeof this[name] !== 'function')
return;
var method = this[name];
this[name] = function() {
method.apply(this, arguments);
fn.apply(this, arguments);
}
}
// Run a function before another function
this.before = function(name, fn) {
if (typeof this[name] !== 'function')
return;
var method = this[name];
this[name] = function() {
fn.apply(this, arguments);
method.apply(this, arguments);
}
}
// Run a function around an existing function, the
// existing function is passed to the wrap function
// as the first argument
this.wrap = function(name, fn) {
if (typeof this[name] !== 'function')
return;
var method = this[name];
this[name] = function() {
var args = [].concat(method.bind(this), [].slice.call(arguments));
// pass original method as 1st argument
fn.apply(this, args);
}
}
}
// A non-function mixin
var withSearch = {
search: function(what) {
what = what || "something";
console.log(this.name + " is searching for " + what);
}
}
// Our base object
var Base = function(name) {
this.name = name;
};
// Create an instance
var obj = new Base('Dave');
// Add our first mixin (call twice, as it should only be included once)
mixin(obj, withHello);
mixin(obj, withHello);
obj.hello();
// Verify we have only one mixin
console.dir(obj.mixins);
// Add in our second mixin, function augmentation
mixin(obj, withActions);
console.dir(obj.mixins);
obj.after('hello', function() { console.log("Nice to meet you. My name is " + this.name + "!"); });
obj.before('hello', function() { console.log("*ahem*"); });
obj.hello();
// Add our non-functional, object mixin (call it twice to ensure it gets added once only)
mixin(obj, withSearch);
mixin(obj, withSearch);
obj.search("a bug");
console.dir(obj.mixins);
// Test out our wrap function, wrapping the search method
obj.wrap('search', function(wrapfn) {
console.log("> *kicks dirt*, looks around.");
wrapfn();
console.log("> nope, didn't find it");
});
obj.search("another bug");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment