/** * gamecore.js - Copyright 2012 Playcraft Labs, Inc. (see licence.txt) * class.js * Classes and objects */ /** * @Class * A modified version of class.js to cater to static inheritance and deep object cloning * Based almost completely on class.js (Javascript MVC -- Justin Meyer, Brian Moschel, Michael Mayer and others) * (http://javascriptmvc.com/contribute.html) * Some portions adapted from Prototype JavaScript framework, version 1.6.0.1 (c) 2005-2007 Sam Stephenson *
* Class system for javascript *
*
* var Fighter = gamecore.Base.extend('Fighter',
* {
* // static (this is inherited as well)
* firingSpeed: 1000
* },
* {
* // instance
*
* hp: 0,
* lastFireTime: 0,
*
* init: function(hp)
* {
* this.hp = hp;
* },
*
* fire: function()
* {
* this._super(); // super methods!
*
* // do firing!
* }
* });
*
* var gunship = new Fighter(100);
*
*
* Introspection:
*
* gamecore.Base.extend(‘Fighter.Gunship’);
* Fighter.Gunship.shortName; // ‘Gunship’
* Fighter.Gunship.fullName; // ‘Fighter.Gunship’
* Fighter.Gunship.namespace; // ‘Fighter’
*
*
* Setup method will be called prior to any init -- nice if you want to do things without needing the
* users to call _super in the init, as well as for normalizing parameters.
*
* setup: function()
* {
* this.objectId = this.Class.totalObjects++;
* this.uniqueId = this.Class.fullName + ':' + this.objectId;
* }
*
*/
// compatible with jquery classing
(function ($)
{
var regs = {
undHash: /_|-/,
colons: /::/,
words: /([A-Z]+)([A-Z][a-z])/g,
lowUp: /([a-z\d])([A-Z])/g,
dash: /([a-z\d])([A-Z])/g,
replacer: /\{([^\}]+)\}/g,
dot: /\./
},
getNext = function (current, nextPart, add)
{
return current[nextPart] || ( add && (current[nextPart] = {}) );
},
isContainer = function (current)
{
var type = typeof current;
return type && ( type == 'function' || type == 'object' );
},
getObject = function (objectName, roots, add)
{
var parts = objectName ? objectName.split(regs.dot) : [],
length = parts.length,
currents = $.isArray(roots) ? roots : [roots || window],
current,
ret,
i,
c = 0,
type;
if (length == 0)
{
return currents[0];
}
while (current = currents[c++])
{
for (i = 0; i < length - 1 && isContainer(current); i++)
{
current = getNext(current, parts[i], add);
}
if (isContainer(current))
{
ret = getNext(current, parts[i], add);
if (ret !== undefined)
{
if (add === false)
{
delete current[parts[i]];
}
return ret;
}
}
}
},
/**
* @class jQuery.String
*
* A collection of useful string helpers.
*
*/
str = $.String = $.extend($.String || {}, {
/**
* @function
* Gets an object from a string.
* @param {String} name the name of the object to look for
* @param {Array} [roots] an array of root objects to look for the name
* @param {Boolean} [add] true to add missing objects to
* the path. false to remove found properties. undefined to
* not modify the root object
*/
getObject: getObject,
/**
* Capitalizes a string
* @param {String} s the string.
* @return {String} a string with the first character capitalized.
*/
capitalize: function (s, cache)
{
return s.charAt(0).toUpperCase() + s.substr(1);
},
/**
* Capitalizes a string from something undercored. Examples:
* @codestart
* jQuery.String.camelize("one_two") //-> "oneTwo"
* "three-four".camelize() //-> threeFour
* @codeend
* @param {String} s
* @return {String} a the camelized string
*/
camelize: function (s)
{
s = str.classize(s);
return s.charAt(0).toLowerCase() + s.substr(1);
},
/**
* Like camelize, but the first part is also capitalized
* @param {String} s
* @return {String} the classized string
*/
classize: function (s, join)
{
var parts = s.split(regs.undHash),
i = 0;
for (; i < parts.length; i++)
{
parts[i] = str.capitalize(parts[i]);
}
return parts.join(join || '');
},
/**
* Like [jQuery.String.classize|classize], but a space separates each 'word'
* @codestart
* jQuery.String.niceName("one_two") //-> "One Two"
* @codeend
* @param {String} s
* @return {String} the niceName
*/
niceName: function (s)
{
return str.classize(s, ' ');
},
/**
* Underscores a string.
* @codestart
* jQuery.String.underscore("OneTwo") //-> "one_two"
* @codeend
* @param {String} s
* @return {String} the underscored string
*/
underscore: function (s)
{
return s.replace(regs.colons, '/').replace(regs.words, '$1_$2').replace(regs.lowUp, '$1_$2').replace(regs.dash, '_').toLowerCase();
},
/**
* Returns a string with {param} replaced values from data.
*
* $.String.sub("foo {bar}",{bar: "far"})
* //-> "foo far"
*
* @param {String} s The string to replace
* @param {Object} data The data to be used to look for properties. If it's an array, multiple
* objects can be used.
* @param {Boolean} [remove] if a match is found, remove the property from the object
*/
sub: function (s, data, remove)
{
var obs = [];
obs.push(s.replace(regs.replacer, function (whole, inside)
{
//convert inside to type
var ob = getObject(inside, data, typeof remove == 'boolean' ? !remove : remove),
type = typeof ob;
if ((type === 'object' || type === 'function') && type !== null)
{
obs.push(ob);
return "";
} else
{
return "" + ob;
}
}));
return obs.length <= 1 ? obs[0] : obs;
}
});
})(jQuery);
(function ($)
{
// if we are initializing a new class
var initializing = false,
makeArray = $.makeArray,
isFunction = $.isFunction,
isArray = $.isArray,
extend = $.extend,
/**
*
*/
cloneObject = function(object)
{
if (!object || typeof(object) != 'object')
return object;
// special case handling of array (deep copy them)
if (object instanceof Array)
{
var clone = [];
for (var c = 0; c < object.length; c++)
clone[c] = cloneObject(object[c]);
return clone;
}
else // otherwise, it's a normal object, clone it's properties
{
var cloneObj = {};
for (var prop in object)
cloneObj[prop] = cloneObject(object[prop]);
return cloneObj;
}
},
concatArgs = function (arr, args)
{
return arr.concat(makeArray(args));
},
// tests if we can get super in .toString()
fnTest = /xyz/.test(function ()
{
xyz;
}) ? /\b_super\b/ : /.*/,
// overwrites an object with methods, sets up _super
// newProps - new properties
// oldProps - where the old properties might be
// addTo - what we are adding to
inheritProps = function (newProps, oldProps, addTo)
{
addTo = addTo || newProps
for (var name in newProps)
{
// Check if we're overwriting an existing function
addTo[name] = isFunction(newProps[name]) &&
isFunction(oldProps[name]) &&
fnTest.test(newProps[name]) ? (function (name, fn)
{
return function ()
{
var tmp = this._super, ret;
// Add a new ._super() method that is the same method but on the super-class
this._super = oldProps[name];
// The method only need to be bound temporarily, so we remove it when we're done executing
ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, newProps[name]) : newProps[name];
}
},
/**
* @class jQuery.Class
* @plugin jquery/class
* @tag core
* @download dist/jquery/jquery.class.js
* @test jquery/class/qunit.html
*
* Class provides simulated inheritance in JavaScript. Use clss to bridge the gap between
* jQuery's functional programming style and Object Oriented Programming. It
* is based off John Resig's [http://ejohn.org/blog/simple-javascript-inheritance/|Simple Class]
* Inheritance library. Besides prototypal inheritance, it includes a few important features:
*
* - Static inheritance
* - Introspection
* - Namespaces
* - Setup and initialization methods
* - Easy callback function creation
*
*
* ## Static v. Prototype
*
* Before learning about Class, it's important to
* understand the difference between
* a class's __static__ and __prototype__ properties.
*
* //STATIC
* MyClass.staticProperty //shared property
*
* //PROTOTYPE
* myclass = new MyClass()
* myclass.prototypeMethod() //instance method
*
* A static (or class) property is on the Class constructor
* function itself
* and can be thought of being shared by all instances of the
* Class. Prototype propertes are available only on instances of the Class.
*
* ## A Basic Class
*
* The following creates a Monster class with a
* name (for introspection), static, and prototype members.
* Every time a monster instance is created, the static
* count is incremented.
*
* @codestart
* $.Class.extend('Monster',
* /* @static *|
* {
* count: 0
* },
* /* @prototype *|
* {
* init: function( name ) {
*
* // saves name on the monster instance
* this.name = name;
*
* // sets the health
* this.health = 10;
*
* // increments count
* this.Class.count++;
* },
* eat: function( smallChildren ){
* this.health += smallChildren;
* },
* fight: function() {
* this.health -= 2;
* }
* });
*
* hydra = new Monster('hydra');
*
* dragon = new Monster('dragon');
*
* hydra.name // -> hydra
* Monster.count // -> 2
* Monster.shortName // -> 'Monster'
*
* hydra.eat(2); // health = 12
*
* dragon.fight(); // health = 8
*
* @codeend
*
*
* Notice that the prototype init function is called when a new instance of Monster is created.
*
*
* ## Inheritance
*
* When a class is extended, all static and prototype properties are available on the new class.
* If you overwrite a function, you can call the base class's function by calling
* this._super
. Lets create a SeaMonster class. SeaMonsters are less
* efficient at eating small children, but more powerful fighters.
*
*
* Monster.extend("SeaMonster",{
* eat: function( smallChildren ) {
* this._super(smallChildren / 2);
* },
* fight: function() {
* this.health -= 1;
* }
* });
*
* lockNess = new SeaMonster('Lock Ness');
* lockNess.eat(4); //health = 12
* lockNess.fight(); //health = 11
*
* ### Static property inheritance
*
* You can also inherit static properties in the same way:
*
* $.Class.extend("First",
* {
* staticMethod: function() { return 1;}
* },{})
*
* First.extend("Second",{
* staticMethod: function() { return this._super()+1;}
* },{})
*
* Second.staticMethod() // -> 2
*
* ## Namespaces
*
* Namespaces are a good idea! We encourage you to namespace all of your code.
* It makes it possible to drop your code into another app without problems.
* Making a namespaced class is easy:
*
* @codestart
* $.Class.extend("MyNamespace.MyClass",{},{});
*
* new MyNamespace.MyClass()
* @codeend
*
* Class provides static and prototype initialization functions. * These come in two flavors - setup and init. * Setup is called before init and * can be used to 'normalize' init's arguments. *
*Setup functions are called before init functions. Static setup functions are passed * the base class followed by arguments passed to the extend function. * Prototype static functions are passed the Class constructor function arguments.
*If a setup function returns an array, that array will be used as the arguments * for the following init method. This provides setup functions the ability to normalize * arguments passed to the init constructors. They are also excellent places * to put setup code you want to almost always run.
** The following is similar to how [jQuery.Controller.prototype.setup] * makes sure init is always called with a jQuery element and merged options * even if it is passed a raw * HTMLElement and no second parameter. *
* @codestart * $.Class.extend("jQuery.Controller",{ * ... * },{ * setup: function( el, options ) { * ... * return [$(el), * $.extend(true, * this.Class.defaults, * options || {} ) ] * } * }) * @codeend * Typically, you won't need to make or overwrite setup functions. *Init functions are called after setup functions.
* Typically, they receive the same arguments
* as their preceding setup function. The Foo class's init
method
* gets called in the following example:
*
Similar to jQuery's proxy method, Class provides a
* [jQuery.Class.static.callback callback]
* function that returns a callback to a method that will always
* have
* this
set to the class or instance of the class.
*
this.name
is available in show
.
* @codestart
* $.Class.extend("Todo",{
* init: function( name ) { this.name = name }
* get: function() {
* $.get("/stuff",this.callback('show'))
* },
* show: function( txt ) {
* alert(this.name+txt)
* }
* })
* new Todo("Trash").get()
* @codeend
* Callback is available as a static and prototype method.
* ** Example: *
* Animal.extend('Tiger', {}, {}); * Tiger._types; // ['Animal', 'Tiger'] * Tiger._fullTypeName; // 'Animal | Tiger |" * Tiger.isA('Animal'); // true *
* @constructor Creating a new instance of an object that has extended jQuery.Class * calls the init prototype function and returns a new instance of the class. * */ clss = $.Class = function () { if (arguments.length) { return clss.extend.apply(clss, arguments); } }; /* @Static*/ extend(clss, { /** * @function callback * Returns a callback function for a function on this Class. * The callback function ensures that 'this' is set appropriately. * @codestart * $.Class.extend("MyClass",{ * getData: function() { * this.showing = null; * $.get("data.json",this.callback('gotData'),'json') * }, * gotData: function( data ) { * this.showing = data; * } * },{}); * MyClass.showData(); * @codeend * new Class( arguments ... )
).
*
* $.Class("MyClass",
* {
* setup: function( val ) {
* this.val = val;
* }
* })
* var mc = new MyClass("Check Check")
* mc.val //-> 'Check Check'
*
* Setup is called before [jQuery.Class.prototype.init init]. If setup
* return an array, those arguments will be used for init.
*
* $.Class("jQuery.Controller",{
* setup : function(htmlElement, rawOptions){
* return [$(htmlElement),
* $.extend({}, this.Class.defaults, rawOptions )]
* }
* })
*
* init
method is provided, it gets called when a new instance
* is created. Init gets called after [jQuery.Class.prototype.setup setup], typically with the
* same arguments passed to the Class
* constructor: ( new Class( arguments ... )
).
*
* $.Class("MyClass",
* {
* init: function( val ) {
* this.val = val;
* }
* })
* var mc = new MyClass(1)
* mc.val //-> 1
*
* [jQuery.Class.prototype.setup Setup] is able to modify the arguments passed to init. Read
* about it there.
*
*/
//Breaks up code
/**
* @attribute Class
* References the static properties of the instance's class.
*