Hi,
I recently wrote a class creation script (with the help of Crockford's and Resig's code for certain parts) that automates certain tasks such as: merging defaults with passed in params, requiring params, creating a bridge between DOM and class instance, binding custom events with a common namespace, inheritance with access to super method, and the ability for mixins.
My 1st question is, what are the pros and cons of creating your classes this way? My concern is performance and portability (all my classes depend on this script now).
My 2nd question is, what would be better ways to write a class creation script? Code is below.
(function($){
// Crockford's way of creating new objects
function F(){};
function _createObject(o) {
F.prototype = o;
return new F();
};
$.Class = function(){
var
prototype = {}
,_super = {}
,requiredError = ''
,extendedInits = []
,fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// copy properties/methods to prototype
for (var i = 0, ln = arguments.length; i < ln; ++i){
var obj = arguments[i];
// if extending another class
if ($.isFunction(obj)) _super = obj = obj.prototype;
if (typeof obj == 'object'){
for (var prop in obj){
var objMethod = obj[prop];
// Resig's Simple Javascript Inheritance
// if method already exists, map old method to this._super()
prototype[prop] = typeof objMethod == "function" && typeof _super[prop] == "function" && fnTest.test(obj[prop]) ?
(function(prop, fn){
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[prop];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(prop, objMethod) :
// or overwrite the method/property
objMethod;
// if __init method is defined on any of the objects passed in, they will be automatically run at instantiation
if (prop == '__init') extendedInits.push(obj[prop]);
}
}
}
prototype.__initialize = function(){
var
self = this
,dataNS = this.__dataNS
,requiredParams = this.__requiredParams;
// check required parameters
if (requiredParams){
for (var i = 0, ln = requiredParams.length; i < ln; ++i){
var param = requiredParams[i];
if (arguments.length == 0 || !arguments[0][param]) requiredError = requiredError + param + ', ';
}
}
// if all required params are passed in
if (requiredError.length == 0){
// set defaults
this.cfg = $.extend(true, {}, this.__defaults, arguments[0]);
this.$elem = this.cfg.$elem;
// create bridge between dom and instance
if (dataNS) this.$elem.data(dataNS, this);
// init class instance
if (this.init) this.init.apply(this, arguments);
// init objects instance was extended with
if (extendedInits.length > 0){
$.each(extendedInits, function(k, fn){
fn.apply(self, arguments);
});
}
// init instance level
if (this.cfg.__init) this.cfg.__init.apply(this, arguments);
}
else {
// alert missing properties
requiredError = requiredError.substring(0, requiredError.lastIndexOf(','));
var str = 'Required Parameters: ' + requiredError;
if (dataNS) str = dataNS + ' - ' + str;
alert(str);
}
};
function _Class(){
this.__initialize.apply(this, arguments);
}
_Class.prototype = _createObject(prototype);
_Class.constructor = _Class;
return _Class;
}
})(jQuery);
And this is how it would be used:
$.Accordion = new $.Class({
__requiredParams: ['$elem']
,__defaults: {
speed: 200,
onlyOneOpen: true,
classNames: {
panel: 'panel', panelHeader: 'panelHeader', panelContent: 'panelContent'
}
}
,panels: {}
,init: function(opts){
console.log('1st init');
var self = this;
// loop thru panels
this.$elem.find('.' + this.cfg.classNames.panel).each(function(){
var $this = $(this);
var panelName = $this.attr('id');
// uses panel dom element id as name
self.panels[panelName] = new $.Panel({$elem: $this});
});
// panel header on click
this.$elem.find('.' + this.cfg.classNames.panelHeader).click(function(e){
e.preventDefault();
var panelName = $(this).parents('.' + self.cfg.classNames.panel).attr('id');
var action = (self.panels[panelName].isOpen()) ? 'close' : 'open';
self[action](panelName);
});
}
,closeAll: function(){
$.dispatch('close.panel.accordion');
}
,openAll: function(){
$.dispatch('open.panel.accordion');
}
,open: function(panelName){
if (this.cfg.onlyOneOpen) this.closeAll();
if (typeof this.panels[panelName] != 'undefined') $.dispatch('open.' + panelName + '.accordion');
}
,close: function(panelName){
if (typeof this.panels[panelName] != 'undefined') $.dispatch('close.' + panelName + '.accordion');
}
}, $.ClassUtils);
Note: $.ClassUtils in the example above is a set of public methods mixed into the $.Accordion class