The Gravey 2.0 Framework

grvClasses.js

Summary

Alternate Collection of routines to emulate multiple-inheritance classes in JavaScript. This "multiple inheritance" has the earlier classes in the superclass list getting precedence over the later classes with regard to resolving both explicit and implicit-via-super references. A detailed explanation of the functionality of this file can be found in the AJAX from Scratch series of articles.
NOTE: Include this grvClasses.js file OR grvClass.js, but not both!

Version: 2.2

Requires:

Author: Bruce Wallace (PolyGlotInc.com)


Class Summary
GrvObject This is the root class for all "classes" implemented via the GLOBALS.Class() function (ala Java "Object").

Method Summary
static void grvImplements()
           This utility function sets up multiple "implements" inheritance with all of the interfaces/classes passed in as parameters.

// This file uses JSDoc-friendly comments [ http://jsdoc.sourceforge.net/ ]

/**
 * @file         grvClasses.js
 * @fileoverview Alternate Collection of routines to emulate multiple-inheritance
 * classes in JavaScript. This "multiple inheritance" has the earlier
 * classes in the superclass list getting precedence over the later classes
 * with regard to resolving both explicit and implicit-via-super references.
 * A detailed explanation of the functionality of this file can
 * be found in the <a target="_blank"
 * href="http://www.polyglotinc.com/AJAXscratch/Class">AJAX from Scratch</a>
 * series of articles.<br/>
 * <b>NOTE:</b> Include this grvClasses.js file OR grvClass.js, but not both!
 *
 * @author       Bruce Wallace  (PolyGlotInc.com)
 * @version      2.2
 * @requires     grvValidate.js (optional; only required if validating constructor args)
 */

//////////////////////////////////////////////////////////////////
// GRAVEY LEXICAL CODING CONVENTIONS:
// (*) All private variables or functions start with "_"
// (*) All variables and functions start with a lowercase letter
// (*) All Classes start with an uppercase letter
// (*) All Class methods and instance variables start with lowercase
// (*) All Class "static" methods and variables start with uppercase
// (*) All constants start with "k"
// (*) All global variables start with "g"
// (*) All event handler functions start with "on"
//
// (*) All Gravey utility global variables start with "gGrv"
// (*) All Gravey MVC     global variables start with "gMVC"
// (*) All Gravey EDO     global variables start with "gEDO"
// (*) All Gravey utility functions start with "grv"
// (*) All Gravey MVC     functions start with "mvc"
// (*) All Gravey MVC event handler functions start with "onMVC"
// (*) All Gravey EDO event handler functions start with "onEDO"
// (*) All Gravey MVC classes start with "MVC"
// (*) All Gravey EDO classes start with "EDO"
//////////////////////////////////////////////////////////////////

/**
 * This function returns the name of a given function; It does this by
 * using a regular expression to extract the function name from the
 * function source code.
 * @param {Function} f reference to a function
 * @return name of given function
 * @type String
 * @author Bruce Wallace (PolyGlotInc.com)
 * @version 2.0
 */
function grvFuncName(f)
{
	var s = f.toString().match(/function\s*(\S*)\s*\(/)[1];
	return s ? s : "anonymous";
}

Class(GrvObject);
/**
 * @class This is the root class for all "classes" implemented
 * via the {@link GLOBALS#Class} function (ala Java "Object").
 * Class replaces the normal Javascript "constructor" function
 * with a new wrapper function that implements a robust
 * inheritence mechanism.
 * This mechanism supports "pretty" source code whereby
 * "methods" are declared inline, but the source code of
 * those methods are not copied into every object instance
 * as would otherwise be the case.<p>
 * The wrapper function also sets up a real "constructor"
 * that can use {@link GLOBALS#grvValidateArgs} to insure
 * that the required parameters are passed to it, throwing
 * an exception if not.<p>
 * The design pattern implemented by Class/Extends corrects
 * several problems that occur with the naive inheritence
 * mechanisms often used with Javascript.
 *<pre>
 *  The required coding convention for "GrvObject":
 *  (1) the "class" is declared via the utility
 *      function {@link GLOBALS#Class}
 *  (2) the "superclass" (if any) is declared via the
 *      .Extends method {@link GLOBALS#grvExtends}
 *  (3) the "constructor" logic for the class is placed
 *      in a "method" named "konstructor"
 *  (4) the konstructor method invokes the "super" constructor
 *      via its class name [rather than literally "super()"]
 *  (5) "interfaces" and extra "superclasses" (if any) are
 *      declared via the .Implements method {@link GLOBALS#grvImplements}
 *</pre>
 * @throws grvMissingArgException thrown by constructor wrapper
 * @see GLOBALS#Class
 * @see GLOBALS#grvExtends
 * @see GLOBALS#grvImplements
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.2
 */
function GrvObject()
{
	/** Invokes the "super" version of whatever method that is calling souper.
	 * @return either the return value of the super method or null if no super exists
	 */
	this.souper = function() {
		var superMethod = this.souper.caller.souper;
		return (superMethod) ? superMethod.apply( this, arguments ) : null;
	}

	/** Invokes the version of whatever method that is calling souper_ from
	 * the specified superclass.
	 * @return either the return value of the super method or null if no super exists
	 */
	this.souper_ = function( superclass ) {
		var superMethod = this[superclass.cname]._[this.souper_.caller.mname];
		var theArgs     = Array.prototype.slice.call(arguments,1);
		return (superMethod) ? superMethod.apply( this, theArgs ) : null;
	}

	/** Is "this" a descendent of the given class?.
	 * This implementation works with Gravey's multiple inheritence.
	 * @param {Function} aClass the class to verify
	 * @return true iff "this" is an "instanceof" aClass
	 */
	this.isInstanceOf = function( aClass ) {
		if (aClass.cname==null) return false;
		var m = this[ aClass.cname ];
		return( m && m instanceof Function );
	}
}


/**
 * This utility function sets up multiple "implements" inheritance
 * with all of the interfaces/classes passed in as parameters.
 * Multiple inheritance is implemented where method references
 * (either explicit or implicit-via-super) are resolved with
 * left-to-right precedence (with "Extends" superclass getting highest)
 * @see #Class
 * @see GrvObject
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.2
 */
function grvImplements()
{
	///////////////////////////////////////////////////////////////////
	// NOTE: At this point each superClass should have already had its
	// own Class and Extends calls performed. I.E. always "declare"
	// superclasses BEFORE any classes that extend them.
	///////////////////////////////////////////////////////////////////
	var N = arguments.length;
	for (var i=0; i<N; ++i)
	  if (!arguments[i].baseClass)
	    throw "Undeclared Super Class in .Implements";

	var s, m, superExemplar;
	var         rawExemplar = this.rawExemplar;
	var            exemplar = this.prototype;

	//foreach superclass, zip together "souper" links
	//to each ORIGINAL subclass method not already souper-linked.
	//
	//I.E. foreach superclass,
	//  foreach method in RAW exemplar,
	//    if method in CURRENT exemplar doesnt already have a souper link
	//    then if method exists in current superclass exemplar
	//         then zip together "souper" reference
	for (var i=0; i<N; ++i)
	{
		superExemplar = arguments[i].prototype;
		for (var property in rawExemplar)
		{
			if (!(rawExemplar[property] instanceof Function)) continue;
			m =      exemplar[property];  if (m.souper) continue; //already linked!
			s = superExemplar[property];
			if (s && (s instanceof Function)) m.souper = s;
		}
	}

	//foreach superclass, copy into exemplar a reference
	//to each superclass method not already in exemplar
	for (var i=0; i<N; ++i)
	{
		superExemplar = arguments[i].prototype;
		for (var property in superExemplar)
		{
			s = superExemplar[property];
			if ( exemplar[property]===void 0 && s instanceof Function )
			     exemplar[property] = s;
		}
	}
}


/**
 * This utility function sets up single inheritance.
 * @param {Function} superClass the "superclass constructor" function
 * @return the "class object" (an extended Function object)
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.2
 */
function grvExtends( superClass )
{
	///////////////////////////////////////////////////////////////////
	// NOTE: At this point superClass should have already had its own
	// Class and Extends calls performed. I.E. "declare" superclasses
	// BEFORE those classes that extend them.
	///////////////////////////////////////////////////////////////////
	if (!superClass.baseClass) throw "Undeclared Super Class in .Extends";

	//get superclass exemplar but avoid infinite recursion on implied root class
	var superexemplar = (this==GrvObject) ? null
					  : (superClass ? superClass.prototype : null);

	//Recall that one can't change the exemplar.__proto__ property directly
	//SO, one must create a new exemplar instance after baseClass.prototype is set.
	this.baseClass.prototype = superexemplar;
	var exemplar = new this.baseClass();

	//for each method in exemplar, if there is a different method with the
	//same name in superexemplar, setup the .souper link between them.
	//To support multiple inheritence disambiguation, save the method name too.
	var s,m;
	for (var property in exemplar)// iterate over all properties
	{
		m =      exemplar[property]; if (!(m instanceof Function)) continue;
		m.mname =         property;  if (! superexemplar         ) continue;
		s = superexemplar[property];
		if (s && (s!=m) && (s instanceof Function)) m.souper = s;
	}

	//Note that it would normally be a problem if Extends were called after
	//someone has already created an instance of "this" class (via new).
	//However, that situation should never occur since all the calls to Class()
	//and Extends() are normally done at web page load time, and application
	//logic should only start after the page "onload" event, hence all these
	//exemplars should be stable.
	this.prototype = exemplar;
	exemplar.name  = this.cname;//default value

	// while "souper" works with konstructor, some may still prefer to
	// use the superclass name explicitly when invoking its constructor
	// so we leave this in to continue support for that.
	if (exemplar.konstructor==null) exemplar.konstructor = function(){};
	exemplar[this.cname]   = exemplar.konstructor;
	// and it is required for multiple-inheritence to resolve ambiguity
	exemplar.konstructor._ = exemplar;
	//save the original (unextended) exemplar to support .Implements
	if (superClass==GrvObject) this.rawExemplar = exemplar;

	return this;
}


/**
 * Declare a class and create the glue objects and code
 * to allow "pretty" source; A mechanism is set up to insure
 * that required constructor parameters each have a defined value;
 * This function also defines the "name" attribute of the specified
 * class and initializes it with the class name.
 * @param {Function} theClass the "class constructor" function
 * @param {StringArray} optConstructorArgDescArray optional array
 * of strings describing required parameters for this class'
 * "constructor"; Note: Optional constructor parameters should
 * not be included in this array.
 * @return the "class object" (an extended Function object)
 * @type Function
 * @see #Extends
 * @see #grvValidateArgs
 * @see GrvObject
 * @author Bruce Wallace  (PolyGlotInc.com)
 * @version 2.2
 */
function Class( theClass, optConstructorArgDescArray )
{
	// The "class" as passed in will really be an abstract base class. We
	// will squirrel that away and replace theClass with a wrapper function
	// so that method source code is not copied into each object instance
	// as would otherwise be the case.
	var baseClass = theClass;

	// create the new wrapper function to replace theClass
	var  className  = grvFuncName( theClass );
	self[className] = theClass = function(){
		var C = arguments.callee;
		if (C.required && self["grvValidateArgs"]!==void 0)
		  grvValidateArgs( C.cname, C.required, arguments );
		C.prototype.konstructor.apply( this, arguments );
	}
	theClass.required  = optConstructorArgDescArray;
	theClass.cname     = className; //FYI,netscape&firefox break with ".name"
	theClass.baseClass = baseClass;
	theClass.Extends   = grvExtends;//so we can call as methods...
	theClass.Implements= grvImplements;
	theClass.Extends(GrvObject);	//required here even if overridden later
	return theClass;
}

The Gravey 2.0 Framework

Documentation generated by JSDoc on Sat Dec 8 21:51:44 2007