1
\$\begingroup\$

Warning: The code snippet does run two alerts, just so you know.

I wrote a javascript class system with the idea of making declaration simple and clean and instantiation even simpler, along with having static methods.

And I know. I know. Don't reinvent the wheel. I didn't like the current wheels, so I made a new one.

I included example usage for your convenience, which is in the code snippet below the 'manager' code. The 'manager' code is what I want reviewed.

Class Definition - Define a class with manager.Class('ClassName',instanceFunction,staticFunction) where instance function contains the methods that will apply to new ClassName() and static functions are callable by ClassName.someFunc()

Inheritance - child classes have a parent object which is the class its extending with all its functions wrapped, so the child is passed as the thisArg to any parent function. That magic happens thanks to manager.setThisArg. Call a parent method with this.parent.funcName()

Static inheritance - not supported, and I don't care about it at this time.

Usage 1 - assign a class to an html element by placing class="jdom ClassName" on the element, where the element inherits all props/methods from new ClassName() (including parent stuff). the instance method ClassName.attach() is called after attaching. This is through manager.setClasses

Usage 2 - Create new class by calling new MyClass(). The constructor accepts paramaters, too.

Usage 3 - call static functions by calling MyClass.staticFunc().

My test (which is in the snippet) works how I've described. I want the code reviewed for reliability and performance, plus any general suggestions for improvement.

//---------
// this is the javascript class system code
//---------
var manager = new function(){
    
    var pending = [];
    
    this.setThisArg = function(thisArg,func){
        var newProp = function(){
                        func.apply(thisArg,arguments);
                    };
        newProp.isThisWrapped = true;
        return newProp;
    }
    this.construct = function(){
        var className = arguments.shift();
        
        //within this code block, 'this' refers to the new instance being created
        var parent = window[className].prototype;
        var hierarchy = [];
        
        while ((typeof parent)=='function'){
            hierarchy.push(parent);
            if ('parent' in parent){
                   parent = parent.parent;
            }
        }
        hierarchy.reverse();
        for (clazz in hierarchy){
            clazz.apply(this,arguments);
        }
        window[className].apply(this,arguments);
        for (var propName in this){
            
            var oldProp = this[propName];
            if ((typeof oldProp)==typeof(function(){})){
                var newProp = manager.setThisArg(this,oldProp);
                
                this[propName] = newProp;
            } 
        }
    }
    
    this.Class = function(className,instanceClosure,staticClosure){

        var constructor = function(){
            var className = this.className;
            var func = window[this.className];
            if (func==undefined){
                console.log(this.className);
            } else if ('prototype' in func
                &&'parent' in func.prototype
                &&typeof func.prototype.parent == typeof 'string'){
                var parentClassName = func.prototype.parent
                parent = window[parentClassName];
                this.className = parentClassName;
                parent.apply(this,[]);
                this.className = className;
                var parentObj = new function(){};
                manager.apply(parentObj,this);
                parentObj.className = func.prototype.parent;
                this.parent = parentObj;
            }
            func.instance.apply(this,[]);
            if (typeof this.parent==typeof 'string'){
                this.parent = undefined;
            }
            if ('construct' in this &&typeof this.construct == typeof function(){}){
                this.construct.apply(this,arguments);
            }
        };
        constructor.prototype.className = className;  
        constructor.instance = instanceClosure;
        //static extend
        constructor.Extend = function(className){
            constructor.prototype.parent = className;
        }
        if (typeof staticClosure == typeof constructor)
            manager.apply(constructor,new staticClosure());
      
        window[className] = constructor;
        return constructor;
    }
    this.apply = function(object,instantiatedFunction){
        for (var propName in instantiatedFunction){
            if (propName in object){
                console.log('yes');
                if (!('parent' in object)||object.parent==null)object.parent = function(){};
                object.parent[propName] = instantiatedFunction[propName];
            } else {
               object[propName] = instantiatedFunction[propName]; 
            }
        }
    }
    this.setThisRecursive = function(thisArg,fromObject){
        for (propName in fromObject){
            if ((typeof fromObject[propName]) === (typeof new function(){})){
                manager.setThisRecursive(thisArg,fromObject[propName]);
              //  console.log('recursive thisify '+propName+ ' which has type '+(typeof fromObject[propName]));
            } else if ((typeof fromObject[propName]) === (typeof function(){})){
             //   console.log('thisify '+propName);
                fromObject[propName] = manager.setThisArg(thisArg,fromObject[propName]);
            } else {
             //   console.log('no thisify '+propName+ ' which has type '+(typeof fromObject[propName]));
            }
        }
    }
    this.override = function(child,parent,thisArg){
            for (propName in parent){
            //console.log('propName is type: '+typeof parent[propName]);
            
            // console.log('setRaw for '+propName+' with type '+typeof parent[propName]); 
             child[propName] = parent[propName]; 
                if (typeof child[propName]=== (typeof new function(){}) ){
                    console.log('setFunc', 'also');
                    manager.setThisRecursive(thisArg,child[propName]);
                }
            }
        child.parent = parent;
    }
    this.setClasses = function(rootElement){
	  	var rootElement = rootElement || document.body;
		var elementList = rootElement.getElementsByClassName('jdom');	
 var funcType = typeof new function(){};
        for (var i = 0;i<elementList.length;i++){
			var element = elementList[i];
			var parts = element.className.split("jdom")[1].trim();
            
            var clazz = parts.split(" ")[0];
            if (clazz in window)
                var newInstance = new window[clazz]();
            else {
                console.log("Error: class "+clazz+" is not defined");
            }
            manager.override(element,newInstance,element);
            

            if ('attach' in element
                &&typeof element['attach']==typeof function(){})
                element.attach();
		}
	}
}




//-----------------------------------------
//
// begin USAGE
// 
//-----------------------------------------




var content = document.getElementById("content");
//usage 1, attaching to an HTML elements:
//
//  defines the class 'Background'. 
manager.Class("Background",
              function(){
                  this.attach = function(){
                         this.style.cursor = 'pointer';
                  }
                  this.changeBackground = function(){
                      this.style.background = this.getBackgroundColor();
                  }
                  this.getBackgroundColor = function(){
                    throw 'Child class must declare getBackgroundColor';                      
                  }
              }
            );
//defines Background_Cycle class, which is intended to be like PHP namespaces... 
// but in PHP namespaces, you have backslashes, which don't work for JS, so I used underscores
//
manager.Class("Background_Cycle",
 function(){
     var colors;
     var index = -1;
     this.construct = function(){
         colors = ['red','green','blue'];
     }
     this.attach = function(){       
       this.parent.attach();
         
     }
    this.onclick = function(){
        this.changeBackground();
    }
    this.getBackgroundColor = function(){
       index++;
       if (index>=colors.length)index = 0;
       var color = colors[index];
       return color;
    }
}
)
.Extend('Background');


//attaches classes to the DOM
manager.setClasses();
  
  
//------ usage 2
//
// here, I will create new free standing objects (not attached to DOM)
  
manager.Class('Alerter',
   function(){
       this.activate = function(){
          alert(this.message);
       }
  
   }
);
manager.Class('Alerter_Funny',
   function(){
      
      this.message = 'Im a funny message.';
      this.construct = function(){
        this.message = 'haha. I changed the message in the constructor';
      }
  },
  function(){
      this.annoy = function(){
          alert('I am sure annoying');
      }
  }
)
.Extend('Alerter');
  
//now, I create a new funny alerter and, well, alert:
  
var funnyAlerter = new Alerter_Funny();
funnyAlerter.activate();

Alerter_Funny.annoy();
<div class="jdom Background_Cycle">
  I am a div. I know that because I'm smart.
</div>

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.