Last active
August 29, 2015 14:11
-
-
Save furf/43778b0cd449da1c8e5f to your computer and use it in GitHub Desktop.
Prototypal factory
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<title>CardFactory</title> | |
</head> | |
<body> | |
<script src="http://code.jquery.com/jquery.js"></script> | |
<script> | |
;(function(window, document, $) { | |
'use strict'; | |
/** | |
* @description <p>Augments a static object or Class prototype with | |
* custom event functionality.</p> | |
* | |
* @example | |
* // Usage with a static object | |
* var dave = { | |
* name: 'dave', | |
* saySomething: function (text) { | |
* alert(this.name + ' says: ' + text); | |
* this.trigger('onSaySomething', [text]); | |
* } | |
* }; | |
* | |
* // Add bindable behavior | |
* $.bindable(dave); | |
* | |
* // Add event listener using bind method | |
* dave.bind('onSaySomething', function (evt, data) { | |
* console.log(this.name + ' said: ' + data); | |
* }); | |
* | |
* dave.saySomething('hello, world!'); | |
* // alerts "furf says: hello, world!" | |
* // logs "furf said: hello, world!" | |
* | |
* @example | |
* // Usage with a class | |
* function Person (name) { | |
* this.name = name | |
* } | |
* | |
* // Add bindable behavior with custom event method | |
* $.bindable(Person, 'onSaySomething'); | |
* | |
* Person.prototype.saySomething = function (text) { | |
* alert(this.name + ' says: ' + text); | |
* this.trigger('onSaySomething', [text]); | |
* }; | |
* | |
* // Create instance | |
* var furf = new Person('furf'); | |
* | |
* // Add event listener using custom event method | |
* furf.onSaySomething(function (evt, data) { | |
* console.log(this.name + ' said: ' + data); | |
* }); | |
* | |
* furf.saySomething('hello, world!'); | |
* // alerts "furf says: hello, world!" | |
* // logs "furf said: hello, world!" | |
* | |
* @param {Object|Function} obj (optional) Object to be augmented with | |
* bindable behavior. If none is supplied, a new Object will be created | |
* and augmented. If a function is supplied, its prototype will be | |
* augmented, allowing each instance of the function access to the | |
* bindable methods. | |
* @param {String} types (optional) Whitespace-delimited list of custom | |
* events which will be exposed as convenience bind methods on the | |
* augmented object | |
* @returns {Object} Augmented object | |
*/ | |
$.bindable = function (obj, types) { | |
// Allow instantiation without object | |
if (!(obj instanceof Object)) { | |
types = obj; | |
obj = {}; | |
} | |
// Allow use of prototype for shorthanding the augmentation of classes | |
obj = $.isFunction(obj) ? obj.prototype : obj; | |
// Augment the object with jQuery's bind, one, and unbind event methods | |
$.each(['bind', 'one', 'unbind', 'on', 'off'], function (i, method) { | |
obj[method] = function (type, data, fn) { | |
$(this)[method](type, data, fn); | |
return this; | |
}; | |
}); | |
// The trigger event must be augmented separately because it requires a | |
// new Event to prevent unexpected triggering of a method (and possibly | |
// infinite recursion) when the event type matches the method name | |
obj.trigger = function (type, data) { | |
var event = new $.Event(type), | |
all = new $.Event(event); | |
event.preventDefault(); | |
all.type = '*'; | |
if (event.type !== all.type) { | |
$.event.trigger(event, data, this); | |
} | |
$.event.trigger(all, data, this); | |
return this; | |
}; | |
// Create convenience methods for event subscription which bind callbacks | |
// to specified events | |
if (typeof types === 'string') { | |
$.each(jQuery.unwhite(types), function (i, type) { | |
obj[type] = function (data, fn) { | |
return arguments.length ? this.bind(type, data, fn) : this.trigger(type); | |
}; | |
}); | |
} | |
return obj; | |
}; | |
})(window, document, jQuery); | |
</script> | |
<script> | |
;(function(window, document, $) { | |
'use strict'; | |
/** | |
* Base object implementation. | |
* @type {Object} | |
* @abstract | |
*/ | |
var base = { | |
/** | |
* Spawn a child object that inherits from the current object. | |
* @param {Object} config Optional configuration for the child object. | |
* @return {Object} Child object. | |
*/ | |
create: function(config) { | |
return $.extend(Object.create(this), config); | |
}, | |
/** | |
* Call a "super" method on the current object | |
* @param {String} methodName Name of the method to call. | |
* @return {*} Return value of the "super" method. | |
*/ | |
_super: function(methodName, args) { | |
var parent = this; | |
var originalMethod = this[methodName]; | |
var method; | |
while ((parent = Object.getPrototypeOf(parent))) { | |
if (parent.hasOwnProperty(methodName)) { | |
method = parent[methodName]; | |
if (method !== originalMethod && $.isFunction(method)) { | |
return method.apply(this, args); | |
} | |
} | |
} | |
// Fail silently or optionally throw an error here. | |
} | |
}; | |
/** | |
* Base card implementation. | |
* @type {Object} | |
* @abstract | |
*/ | |
var card = base.create({ | |
/** | |
* Convert the object to a string value. | |
* @return {String} String value of the object. | |
*/ | |
toString: function() { | |
return '[object Card]'; | |
}, | |
/** | |
* Log the name of the instance. | |
*/ | |
identify: function() { | |
console.log(this.name); | |
} | |
}); | |
// Mixin event functionality | |
$.bindable(card); | |
/** | |
* Generic card implementation. | |
* @type {Object} | |
*/ | |
var genericCard = card.create({ | |
/** | |
* Convert the object to a string value. | |
* @return {String} String value of the object. | |
*/ | |
toString: function() { | |
return '[object GenericCard]'; | |
} | |
}); | |
/** | |
* Enhanced card implementation. | |
* @type {Object} | |
*/ | |
var enhancedCard = card.create({ | |
/** | |
* Convert the object to a string value. | |
* @return {String} String value of the object. | |
*/ | |
toString: function() { | |
return '[object EnhancedCard]'; | |
}, | |
/** | |
* Walk. | |
*/ | |
walk: function() { | |
console.log('Left. Right. Repeat.'); | |
} | |
}); | |
/** | |
* Super card implementation. | |
* @type {Object} | |
*/ | |
var superCard = enhancedCard.create({ | |
/** | |
* Convert the object to a string value. | |
* @return {String} String value of the object. | |
*/ | |
toString: function() { | |
return '[object SuperCard]'; | |
}, | |
/** | |
* Fly. | |
*/ | |
fly: function() { | |
console.log('Look, Ma. No hands!'); | |
this.trigger('fly'); | |
}, | |
/** | |
* Walk. | |
*/ | |
walk: function() { | |
console.log('Before we can run, we must walk.'); | |
this._super('walk'); | |
console.log('RUN!'); | |
} | |
}); | |
/** | |
* Card factory. | |
* @type {Object} | |
*/ | |
var cardFactory = base.create({ | |
/** | |
* Card prototype lookup map. | |
* @type {Object} | |
*/ | |
prototypes: { | |
'generic': genericCard, | |
'enhanced': enhancedCard, | |
'super': superCard | |
}, | |
/** | |
* Card contructor method. | |
* @param {String} type Type of card to generate. | |
* @param {Object} config Optional configuration for card instance. | |
* @return {Object} Configured card instance. | |
*/ | |
createCard: function(type, config) { | |
var prototype = this.prototypes[type]; | |
if (!prototype) { | |
throw new Error('Invalid card type.'); | |
} | |
return prototype.create(config); | |
}, | |
/** | |
* Determine if a card is an instance of a type. | |
* @param {[type]} instance Object to evaluate. | |
* @param {[type]} type Card type. | |
* @return {Boolean} true if card inherits from card of specified type. | |
*/ | |
isInstanceOf: function(instance, type) { | |
var prototype = this.prototypes[type]; | |
if (!prototype) { | |
throw new Error('Invalid card type.'); | |
} | |
return prototype.isPrototypeOf(instance); | |
} | |
}); | |
// Usage: | |
var c = cardFactory.create(); | |
var g = c.createCard('generic'); | |
g.identify(); // undefined | |
console.log(g + ''); // [object GenericCard] | |
var e = c.createCard('enhanced'); | |
e.identify(); // undefined | |
console.log(e + ''); // [object EnhancedCard] | |
e.walk(); // Before one can run, one must walk. | |
var s = c.createCard('super'); | |
console.log(s + ''); // [object SuperCard] | |
s.identify(); // undefined | |
s.walk(); // Before one can run, one must walk. | |
s.fly(); // Look, Ma. No hands! | |
console.assert(c.isInstanceOf(s, 'super'), 'card is not super'); // pass | |
console.assert(c.isInstanceOf(s, 'enhanced'), 'card is not enhanced'); // pass | |
console.assert(c.isInstanceOf(s, 'generic'), 'card is not generic'); // fail | |
var s2 = c.createCard('super', { name: 'Yoni' }); | |
s2.identify(); // Yoni | |
// Bindable | |
s.on('fly', function() { | |
console.log('It\'s a bird! It\'s a plane! It\'s BINDABLE!'); | |
}); | |
s.fly(); | |
})(window, document, jQuery); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment