/** * Contains basic inheritance mechanism * * Copyright (C) 2010 Mike Gerwitz * * This file is part of ease.js. * * ease.js is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free * Software Foundation, either version 3 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . * * @author Mike Gerwitz * @package core */ // whether getters/setters are supported var getset = ( Object.prototype.__defineGetter__ === undefined ) ? false : true ; /** * Creates a class, inheriting either from the provided base class or the * default base class * * @param {Object} base object to extend (extends Class by default) * * @return {Object} extended class */ exports.extend = function( base ) { return extend.apply( this, arguments ); } /** * Default class implementation * * @return undefined */ function Class() {}; /** * Copies properties to the destination object * * If the method already exists, it will be overridden and accessible via either * the parent prototype or by invoking this.__super(). * * The destination object is directly modified. * * @param {Object} props properties to copy * @param {Object} dest destination object * * @return undefined */ function prop_copy( props, dest ) { // copy each of the properties to the destination object for ( property in props ) { // if the property already exists, then it's being overridden (we only // care about methods - properties will simply have their values // overwritten) var pre = dest[ property ], getter = ( ( getset ) ? props.__lookupGetter__( property ) : null ), setter = ( ( getset ) ? props.__lookupSetter__( property ) : null ); // check for getter/setter overrides if ( getter || setter ) { if ( getter ) { dest.__defineGetter__( property, getter ); } if ( setter ) { dest.__defineSetter__( property, setter ); } } // check for method overrides else if ( ( pre !== undefined ) && ( pre instanceof Function ) ) { dest[ property ] = ( function( method, super_method ) { // this is the method that will be invoked when the requested // method is called, so note that in the context of this // function, `this` will represent the current class instance return function() { var tmp = this.__super; // assign _super temporarily for the method invocation so // that the method can call the parent method this.__super = super_method; var retval = method.apply( this, arguments ); this.__super = tmp; return retval; } })( props[ property ], dest[ property ] ); } else { dest[ property ] = props[ property ]; } } } /** * Mimics class inheritance * * This method will mimic inheritance by setting up the prototype with the * provided base class (or, by default, Class) and copying the additional * properties atop of it. * * The class to inherit from (the first argument) is optional. If omitted, the * first argument will be considered to be the properties list. * * @return {Object} extended class */ function extend() { var args = Array.prototype.slice.call( arguments ), props = args.pop() || {}, base = args.pop() || Class, prototype = new base(), new_class = function() { if ( this.__construct instanceof Function ) { this.__construct.apply( this, arguments ); } }; // copy the given properties into the new prototype prop_copy( props, prototype ); // reference to the parent prototype (for more experienced users) prototype.parent = base.prototype; // set up the new class attach_extend( new_class ); new_class.prototype = prototype; new_class.constructor = new_class; return new_class; } /** * Attaches extend method to the given function's prototype * * @param {Function} func function to attach method to * * @return undefined */ function attach_extend( func ) { /** * Shorthand for extending classes * * This method can be invoked on the object, rater than having to call * Class.extend( this ). * * @param {Object} props properties to add to extended class * * @return {Object} extended class */ var ext_method = function( props ) { return extend( this, props ); }; // if defineProperty is unsupported, do it the old fashioned way (it's just // less restrictive) if ( Object.defineProperty === undefined ) { func.extend = ext_method; } else { Object.defineProperty( func, 'extend', { value: ext_method, enumerable: false, writable: false, configurable: false, } ); } }