Skip to content

Instantly share code, notes, and snippets.

@furf
Last active August 29, 2015 14:11
Show Gist options
  • Save furf/43778b0cd449da1c8e5f to your computer and use it in GitHub Desktop.
Save furf/43778b0cd449da1c8e5f to your computer and use it in GitHub Desktop.
Prototypal factory
<!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