/** * ElementController, Site, Page, and Component * This is my baby. * * @author Stephen Rushing * @version 2.8a (2011-06-23) * **/ (function(){ var $ = jQuery, undefined; var ElementController = window.ElementController = ResigClass.extend({ classPath:"", className:"ElementController", debug:false, construct:function(container, options){ var $container = $(container).first(), classKeyName = this.getFullClassName(true); //Check for an existing instance and set the container assert(!$container.data(classKeyName), $container.selector + " is already attached to an instance of " + this.getFullClassName() +"."); $container.data(classKeyName, this); this.el = { $container: $container }; //Merge the options this.options = $.extend(true, {}, this.options, options); //this.createDispatcher("setContainer", "beforeSetContainer", "afterSetContainer").createDo("setContainer", "_setContainer"); //this.createDispatcher("init", "beforeInit", "afterInit").createDo("init", "_init").createDo("beforeInit", "_beforeInit").createDo("afterInit", "_afterInit"); this.createDispatcher({init:"_init", beforeInit:"_beforeInit", afterInit:"_afterInit"}); }, initialized:false, el:null, options:null, bind: function () { arguments[0] = this._nameSpacify(arguments[0]); //$.fn.bind.apply(this.el.$container, arguments); this.log(this.getFullClassName(),"bind()", arguments); $.fn.bind.apply(this.el.$container, arguments); return this; }, unbind: function(){ arguments[0] = this._nameSpacify(arguments[0]); //$.fn.unbind.apply(this.el.$container, arguments); $.fn.unbind.apply(this.el.$container, arguments); return this; }, trigger: function () { //Using jQuery.trigger() causes an infinite loop...using jQuery.triggerHandler() until I figure out why. arguments[0] = this._nameSpacify(arguments[0]); this.log(this.getFullClassName(),"trigger()", arguments); //this.$I.triggerHandler.apply(this.el.$container, arguments); $.fn.trigger.apply(this.el.$container, arguments); return this; }, createDispatcher:function(dispatch){ var primary=dispatch, before=null, after=null, i=0; if(typeof primary !== "string"){ for(var name in dispatch){ switch(i){ case 0: primary = name; break; case 1: before = name; break; case 2: after = name; break; } //Create the do var doName = dispatch[name]; if(typeof doName === "string"){ //this.log(this.getFullClassName(), name, doName); this.createDo(name, doName); } i++; } } if(assert(!$.isFunction(this[primary]), this.getFullClassName() +" already has a \""+primary+"\" dispatcher.")){ //Create the dispatcher this[primary] = function(){ assert(this.el.$container && this.el.$container.length, this.getFullClassName() +" requires a container to dispatch events."); var evtBefore, evtPrimary, evtAfter; if(before){ evtBefore = $.Event(this._nameSpacify(before)); this.trigger(evtBefore, arguments); if(evtBefore.result === false){ return this; } } //Unless the "before" event already returned false, always run the primary event evtPrimary = $.Event(this._nameSpacify(primary)); if(evtBefore != undefined) evtPrimary.before = evtBefore; this.trigger(evtPrimary, arguments); if(evtPrimary.result === false){ return this; } //If "before" and "primary" events didn't return false, run the after event if(after){ evtAfter = $.Event(this._nameSpacify(after)); evtAfter.primary = evtPrimary; this.trigger(evtAfter, arguments); } return this; }; } return this; }, //A "do" is the primary handler of the current object's event. We create a proxy function for it so that it can be replaced with object._whatever = function(){} at any time and still maintain its position in the event handler stack. createDo:function(eventName, methodName){ var I = this; I.bind(eventName, function(evt){ //I.log(I.getFullClassName(), eventName, methodName); //There was a bug with jQuery 1.4.4 event namespaces on bubbled events, so we need to check the event namespace before calling our "do". if($.isFunction(I[methodName]) && evt.namespace === I.getFullClassName(true)){ result = I[methodName].apply(I, arguments); return result; } }); return this; }, _nameSpacify:function(name, isBasic){ if($.type(name)=="array"){ name = name[0]+"."+name[1]; } else if($.type(name) == "string" && name.indexOf(".") < 0){ name = name + "." + ((isBasic!==true) ? this.getFullClassName(true) : this.className); } return name; }, _afterInit:function(){ this.initialized = true; }, populate:function(data, context, selector, getKey){ if(!(context instanceof $)) context = this.el.$container; if(!selector) selector = "[data-key]"; if(!getKey) getKey = "data-key"; var I = this; //this.log(arguments); context.find(selector).each(function(){ var $el = $(this), key = (typeof getKey === "string") ? $el.attr(getKey) : getKey.call(this, this), keyData, $input; if (key in data){ keyData = data[key]; } //Look for an input within the context, if keyData does not contain it else if(($input = I.el.$inputs.filter("[name='"+key+"']")).length && $.trim($input.val()).length){ keyData = $input.val(); }else{ I.log("No data found in populate() for key \""+key+"\"", this, I); return; } //Set the element content if($el.is(":input")){ //TODO: Add logic for checkboxes and multi selects $el.val(keyData); }else{ $el.html(keyData); } //this.log($el, key, $input); }); return this; }, //This is a proxy for the ContextString class. For example, pass the link with an alias map of var obj = {Site:this, Component:this.comp} with the event and attribute you want to bind. //Usage Examples: // bindContextAttr("click", "#selector", "href", {window:window}, "#", true) // bindContextAttr("click", "button", "data-call", {window:window}, "$", false) bindContextAttr:function(eventName, elements, attr, aliases, prefix, cancelEvent){ var I = this; $(elements).bind(eventName, function(evt){ var $el = $(this), contextStr = ContextString($el.attr(attr), aliases, prefix); contextStr.eval(); //Cancel the link, if not told otherwise if(cancelEvent === true){ return false; } }); return this; }, destroy:function(){ //Need to unbind events and stuff here, but for now we just detach the instance from the element this.el.$container.data(this.getFullClassName(), null); this.el = {}; }, getFullClassName:function(isKeySafe){ var name = this.className; if(typeof this.classPath === "string" && this.classPath.length){ name = this.classPath+"."+this.className; } if(isKeySafe){ name = name.replace(/\./g, "_"); } return name; }, toString:function(){ return "Object [["+this.getFullClassName()+"]]"; }, log:function(){ if(this.debug){ if("apply" in console.log){ console.log.apply(console, arguments); }else{ console.log(Array.prototype.slice.call(arguments)); //Fallback for IE } } return this; } }); ElementController.get = function(controllerClass, el, options, autoInit, initArgs){ var ControllerClass = $.isFunction(controllerClass) ? controllerClass : eval(controllerClass), ClassKeyName = ControllerClass ? ControllerClass.prototype.getFullClassName(true) : null, instance = ClassKeyName ? $(el).data(ClassKeyName) : null; if(!ControllerClass){ ElementController.prototype.log("Invalid controller class provided to ElementController.get()", arguments); return; } //console.log(ClassKeyName, el, options, autoInit, initArgs, instance, $(el).data()); if(!instance){ //Create the new instance instance = new ControllerClass(el, options); }else if(options){ //Extend the options of an existing instance $.extend(instance.options, options); } if(instance && autoInit !== false && !instance.initialized){ //Initialize instance with initialize args instance.init.apply(instance, initArgs||[]); } return instance; }; //Create the contollers and controller jQuery plugins for ElementController $.fn.controllers = function(controllerClass, options, autoInit, initArgs){ var controllers = []; this.each(function(e, el){ var controller = ElementController.get(controllerClass, el, options, autoInit, initArgs); if(controller){ controllers.push(controller); } }); return controllers; }; $.fn.controller = function(controllerClass, options, autoInit, initArgs){ if(this.length){ var controller = ElementController.get(controllerClass, this[0], options, autoInit, initArgs);; return controller; } return null; }; var Site = window.Site = ElementController.extend({ className:"Site", construct: function(container){ if(!container) container = arguments[0] = document.documentElement; this._super.apply(this, arguments); var I = this; //Create an empty object for the pages and components of this instance I.page = {}; I.component = {}; //I.setContainer(document.documentElement); //Create event dispatchers and "do" methods I.createDispatcher({ initPages:"_initPages", beforeInitPages:"_beforeInitPages", afterInitPages:"_afterInitPages"}); I.createDispatcher({ initComponents:"_initComponents", beforeInitComponents:"_beforeInitComponents", afterInitComponents:"_afterInitComponents"}); return I; }, initialized:false, _init:function(evt){ }, _afterInit:function(evt){ this.initialized = true; //Initialize the pages this.initPages(this.page); }, _initPages:function(evt, pageControllers){ for(var pageName in pageControllers){ var pageClass = pageControllers[pageName]; if((pageClass.prototype.test === true || ($.isFunction(pageClass.prototype.test) && pageClass.prototype.test() === true))){ var pageController = $("body").controller(pageClass, null, true); } } }, _afterInitPages:function(evt, pages){ //Find and initialize the components var components = this.findComponents(); this.initComponents(components); }, page:null, _initComponents:function(evt, componentInstances){ for(var c=0; c < componentInstances.length; c++){ var component = componentInstances[c]; this.log("initComponents", component); if(!component.initialized){ component.init(); } } }, componentAttr:"data-component", componentOptionsAttr:"data-component-options", findComponents:function(context){ var I = this, instances = []; if(!context) context = I.el.$container; if(!(context instanceof $)) context = $(context); context.find("["+I.componentAttr+"]").each(function(e, el){ var $el = $(el), components = $el.attr(I.componentAttr).split(/\s*,\s*/g), options = $el.attr(I.componentOptionsAttr); if(options && $.trim(options).length){ try{ options = eval("["+options+"]"); }catch(err){ this.log(el, options, err.message); options = null; } } for(var c=0; c