Skip to content
Permalink
 
 
Cannot retrieve contributors at this time
230 lines (218 sloc) 7.64 KB
/* vim:set ts=2 sw=2 sts=2 expandtab */
/*jshint undef: true es5: true node: true devel: true evil: true
forin: true latedef: false supernew: true */
/*global define: true */
!(typeof define !== "function" ? function(_, $) {
$(null, typeof exports !== "undefined" ? exports : window);
} : define)("selfish", function(require, exports) {
"use strict";
/**
* Merges all the properties of the passed objects into `this` instance (This
* method can be used on instances only as prototype objects are frozen).
*
* If two or more argument objects have own properties with the same name,
* the property is overridden, with precedence from right to left, implying,
* that properties of the object on the left are overridden by a same named
* property of the object on the right.
*
* @examples
*
* var Pet = Dog.extend({
* initialize: function initialize(options) {
* this.name = options.name;
* },
* call: function(name) {
* return this.name === name ? this.bark() : '';
* },
* name: null
* });
* var pet = new Pet({ name: 'Benzy', breed: 'Labrador' });
* pet.call('Benzy'); // 'Ruff! Ruff!'
*/
var merge = function selfishMerge() {
var descriptor = {}, property;
var that = Array.prototype.shift.call(arguments) || {};
that.descriptors = {};
Array.prototype.forEach.call(arguments, function(properties) {
Object.getOwnPropertyNames(properties).forEach(function(name) {
property = Object.getOwnPropertyDescriptor(properties, name);
// This happens to be a problem for the constructor variable
// A proper solution has to be found. This is only a fix
if (property.enumerable) {
descriptor[name] = property;
descriptor[name].configurable = true;
descriptor[name].enumerable = true;
descriptor[name].writable = true;
that.descriptors[name] = descriptor[name];
}
});
});
Object.defineProperties(this, descriptor);
return this;
};
exports.merge = merge;
/**
* This is the main constructor. It should be used by any other object.
* Always calling the `initialize` function makes it easier to keep
* a coherent inheritance tree.
*/
var Base = function BaseConstructor() {
this.initialize.apply(this, arguments);
};
/**
* When new instance of the this prototype is created its `initialize`
* method is called with all the arguments passed to the `new`. You can
* override `initialize` to set up an instance.
*/
Base.prototype.initialize = function BaseInitialize() {};
/**
* Constructs an instance. An alternative maker function to using new keyword.
*
* @examples
*
* var Pet = Dog.extend({
* initialize: function initialize(options) {
* this.name = options.name;
* },
* call: function(name) {
* return this.name === name ? this.bark() : '';
* },
* name: null
* });
* var pet = Pet.new({ name: 'Benzy', breed: 'Labrador' });
* pet.call('Benzy'); // 'Ruff! Ruff!'
*/
Base.new = function() {
var instance = Object.create(Base.prototype);
Base.apply(instance, arguments)
return instance;
};
/**
* Takes any number of argument objects and returns frozen, composite object
* that inherits from `this` object and combines all of the own properties of
* the argument objects. (Objects returned by this function are frozen as
* they are intended to be used as types).
*
* If two or more argument objects have own properties with the same name,
* the property is overridden, with precedence from right to left, implying,
* that properties of the object on the left are overridden by a same named
* property of the object on the right.
*
* When extending selfish prototypes remember to call it on their `prototype`
* instead of the type itself.
*
* This is the only function that should not be placed in the prototype as it
* is relative to the object.
* @examples
*
* // ## Object composition ##
*
* var HEX = Base.extend({
* hex: function hex() {
* return '#' + this.color;
* }
* });
*
* var RGB = Base.extend({
* red: function red() {
* return parseInt(this.color.substr(0, 2), 16);
* },
* green: function green() {
* return parseInt(this.color.substr(2, 2), 16);
* },
* blue: function blue() {
* return parseInt(this.color.substr(4, 2), 16);
* }
* });
*
* var CMYK = Base.extend(RGB.prototype, {
* black: function black() {
* var color = Math.max(Math.max(this.red(), this.green()), this.blue());
* return (1 - color / 255).toFixed(4);
* },
* cyan: function cyan() {
* var K = this.black();
* return (((1 - this.red() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
* },
* magenta: function magenta() {
* var K = this.black();
* return (((1 - this.green() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
* },
* yellow: function yellow() {
* var K = this.black();
* return (((1 - this.blue() / 255).toFixed(4) - K) / (1 - K)).toFixed(4);
* }
* });
*
* var Color = Base.extend(HEX.prototype, RGB.prototype, CMYK.prototype, {
* initialize: function Color(color) {
* this.color = color;
* }
* });
*
* // ## Prototypal inheritance ##
*
* var Pixel = Color.extend({
* initialize: function Pixel(x, y, hex) {
* Color.initialize.call(this, hex);
* this.x = x;
* this.y = y;
* },
* toString: function toString() {
* return this.x + ':' + this.y + '@' + this.hex();
* }
* });
*
* var pixel = new Pixel(11, 23, 'CC3399');
* pixel.toString(); // 11:23@#CC3399
*
* pixel.red(); // 204
* pixel.green(); // 51
* pixel.blue(); // 153
*
* pixel.cyan(); // 0.0000
* pixel.magenta(); // 0.7500
* pixel.yellow(); // 0.2500
*
*/
Base.extend = function selfishExtend() {
var dependencies = [];
/*
* It is actually important to redefine a constructor here. Even if
* the body is always the same
*/
var constructor = function() { // Call initialize by default if possible
var name;
if (constructor.descriptors) {
for (name in constructor.descriptors) {
if (constructor.descriptors.hasOwnProperty(name)) {
Object.defineProperty(this, name, constructor.descriptors[name]);
}
}
}
this.initialize.apply(this, arguments);
};
Array.prototype.forEach.call(arguments, function(dependency) {
dependencies.push(dependency);
});
// Copy the prototype methods that allows us to do inheritance
constructor.extend = this.extend;
constructor.new = function() {
var instance = Object.create(constructor.prototype);
constructor.apply(instance, arguments)
return instance;
};
// Generate the prototype and extend it
var descriptor = Object.create(this.prototype);
// We want to keep the prototype properties too
dependencies.unshift(this.prototype);
dependencies.unshift(constructor);
merge.apply(descriptor, dependencies);
// Freeze the new created prototype
constructor.prototype = Object.freeze(descriptor);
return Object.freeze(constructor);
};
// Freeze the prototype as well as the constructor
Base.prototype = Object.freeze(Base.prototype);
exports.Base = Object.freeze(Base);
});