Object Oriented JavaScript Tutorial

< html>

Example

/*
 * xbObjects.js
 * $Revision: 1.2 $ $Date: 2003/02/07 16:04:20 $
 */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Bob Clary code.
 *
 * The Initial Developer of the Original Code is
 * Bob Clary.
 * Portions created by the Initial Developer are Copyright (C) 2000
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s): Bob Clary 
 *
 * ***** END LICENSE BLOCK ***** */
/*
ChangeLog: 2001-12-19 - bclary - changed xbException init method to
           remove possible exception due to permission denied issues
           in gecko 0.9.5+
*/
function _Classes()
{
  if (typeof(_classes) != 'undefined')
    throw('Only one instance of _Classes() can be created');
  function registerClass(className, parentClassName)
  {
    if (!className)
      throw('xbObjects.js:_Classes::registerClass: className missing');
    if (className in _classes)
      return;
    if (className != 'xbObject' && !parentClassName)
      parentClassName = 'xbObject';
    if (!parentClassName)
      parentClassName = null;
    else if ( !(parentClassName in _classes))
      throw('xbObjects.js:_Classes::registerClass: parentClassName ' + parentClassName + ' not defined');
    // evaluating and caching the prototype object in registerClass
    // works so long as we are dealing with 'normal' source files
    // where functions are created in the global context and then
    // statements executed. when evaling code blocks as in xbCOM,
    // this no longer works and we need to defer the prototype caching
    // to the defineClass method
    _classes[className] = { 'classConstructor': null, 'parentClassName': parentClassName };
  }
  _Classes.prototype.registerClass = registerClass;
  function defineClass(className, prototype_func)
  {
    var p;
    if (!className)
      throw('xbObjects.js:_Classes::defineClass: className not given');
    var classRef = _classes[className];
    if (!classRef)
      throw('xbObjects.js:_Classes::defineClass: className ' + className + ' not registered');
    if (classRef.classConstructor)
      return;
    classRef.classConstructor = eval( className );
    var childPrototype  = classRef.classConstructor.prototype;
    var parentClassName = classRef.parentClassName;
    if (parentClassName)
    {
      var parentClassRef = _classes[parentClassName];
      if (!parentClassRef)
        throw('xbObjects.js:_Classes::defineClass: parentClassName ' + parentClassName + ' not registered');
      if (!parentClassRef.classConstructor)
      {
        // force parent's prototype to be created by creating a dummy instance
        // note constructor must handle 'default' constructor case
        var dummy;
        eval('dummy = new ' + parentClassName + '();');
      }
      var parentPrototype = parentClassRef.classConstructor.prototype;
      for (p in parentPrototype)
      {
        switch (p)
        {
        case 'isa':
        case 'classRef':
        case 'parentPrototype':
        case 'parentConstructor':
        case 'inheritedFrom':
          break;
        default:
          childPrototype[p] = parentPrototype[p];
          break;
        }
      }
    }
    prototype_func();
    childPrototype.isa        = className;
    childPrototype.classRef   = classRef;
    // cache method implementor info
    childPrototype.inheritedFrom = new Object();
    if (parentClassName)
    {
      for (p in parentPrototype)
      {
        switch (p)
        {
        case 'isa':
        case 'classRef':
        case 'parentPrototype':
        case 'parentConstructor':
        case 'inheritedFrom':
          break;
        default:
          if (childPrototype[p] == parentPrototype[p] && parentPrototype.inheritedFrom[p])
          {
            childPrototype.inheritedFrom[p] = parentPrototype.inheritedFrom[p];
          }
          else
          {
            childPrototype.inheritedFrom[p] = parentClassName;
          }
          break;
        }
      }
    }
  }
  _Classes.prototype.defineClass = defineClass;
}
// create global instance
var _classes = new _Classes();
// register root class xbObject
_classes.registerClass('xbObject');
function xbObject()
{
  _classes.defineClass('xbObject', _prototype_func);
  this.init();
  function _prototype_func()
  {
    // isa is set by defineClass() to the className
    // Note that this can change dynamically as the class is cast
    // into it's ancestors...
    xbObject.prototype.isa        = null;
    // classref is set by defineClass() to point to the
    // _classes entry for this class. This allows access
    // the original _class's entry no matter how it has
    // been recast.
    // *** This will never change!!!! ***
    xbObject.prototype.classRef      = null;
    xbObject.prototype.inheritedFrom = new Object();
    function init() { }
    xbObject.prototype.init        = init;
    function destroy() {}
    xbObject.prototype.destroy      = destroy;
    function parentMethod(method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10)
    {
      // find who implemented this method
      var className       = this.isa;
      var parentClassName = _classes[className].classConstructor.prototype.inheritedFrom[method];
      var tempMethod      = _classes[parentClassName].classConstructor.prototype[method];
      // 'cast' this into the implementor of the method
      // so that if parentMethod is called by the parent's method,
      // the search for it's implementor will start there and not
      // cause infinite recursion
      this.isa   = parentClassName;
      var retVal = tempMethod.call(this, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
      this.isa   = className;
      return retVal;
    }
    xbObject.prototype.parentMethod    = parentMethod;
    function isInstanceOf(otherClassConstructor)
    {
      var className = this.isa;
      var otherClassName = otherClassConstructor.prototype.isa;
      while (className)
      {
        if (className == otherClassName)
          return true;
        className = _classes[className].parentClassName;
      }
      return false;
    }
    xbObject.prototype.isInstanceOf    = isInstanceOf;
  }
}
// eof: xbObjects.js




_classes.registerClass("Shape");
function Shape(iSides) {
    _classes.defineClass("Shape", prototypeFunction);
    this.init(iSides);
    function prototypeFunction() {
        Shape.prototype.init = function(iSides) {
            this.parentMethod("init");
            this.sides = iSides;
        };
        Shape.prototype.getArea = function () {
            return 0;
        };
    }
}
_classes.registerClass("Triangle", "Shape");
function Triangle(iBase, iHeight) {
    _classes.defineClass("Triangle", prototypeFunction);
    this.init(iBase,iHeight);
    function prototypeFunction() {
        Triangle.prototype.init = function(iBase, iHeight) {
            this.parentMethod("init", 3);
            this.base = iBase;
            this.height = iHeight;
        };
        Triangle.prototype.getArea = function () {
            return 0.5 * this.base * this.height;
        };
    }
}
_classes.registerClass("Rectangle", "Shape");
function Rectangle(iLength, iWidth) {
    _classes.defineClass("Rectangle", prototypeFunction);
    this.init(iLength, iWidth);
    function prototypeFunction() {
        Rectangle.prototype.init = function(iLength, iWidth) {
            this.parentMethod("init", 4);
            this.length = iLength;
            this.width = iWidth;
        }
        Rectangle.prototype.getArea = function () {
            return this.length * this.width;
        };
    }
}
var triangle = new Triangle(10, 40);
var rectangle = new Rectangle(20, 50);
alert(triangle.sides);
alert(triangle.getArea());
alert(rectangle.sides);
alert(rectangle.getArea());