/*!
* vEwe
* https://github.com/mattrohland/vEwe
*
* Copyright (c) 2013 Matt Rohland
* Released under the MIT license
*/
/* global define:true,$:false */
/* jshint strict:false,globalstrict:false */
/**
* The vEwe module
* @exports vEweFactory
* @namespace vewe
*/
(function(){
// AMDless define method
if(typeof define !== 'function') define = function(a,b){ window.vEweFactory = b($); };
// AMD define wrapper
define(
[
'3rdparty/jquery'
],
function($){
'use strict';
var VEweFactory,
Element,
ShepHeard;
/**
* A factory that assists in defining and instantiating views.
*
* @memberof vewe
* @class
*/
VEweFactory = function(){
this.shepHeard = new ShepHeard();
return this;
};
/**
* @memberof vewe
* @lends VEweFactory.prototype
*/
VEweFactory.prototype = {
/**
*
* A scoped instance of jQuery.
* @static
*/
'$': $,
/**
* A method used to creates an instance of a VEwe.
*
* @function
* @param {string} [arguments[0]=inherit] - The method to use when merging prototypes.
* @param {...object} arguments - One or more prototypes to use when constructing a VEwe instance.
* @returns {Function} VEwe instance
*/
'create': function(){
var me = this,
VEwe;
return (VEwe = me.define.apply(me, arguments))? new VEwe() : false ;
},
/**
* A method used to construct an uninstantiated definition of a view.
*
* @function
* @param {string} [arguments[0]=inherit] - The method to use when merging prototypes. Supported values include: inherit, inheritAndMergeEvents
* @param {...object} arguments - One or more prototypes to use when constructing a VEwe class.
* @returns {Function} VEwe class
*/
'define': function(){
var me = this,
proto = {},
inheritanceMethod = 'inherit',
inheritanceArguments,
/**
* @class
*/
VEwe = function(){
this.shepHeard = me.shepHeard;
return this;
};
// Process Arguments
if(arguments.length === 0){
return false;
}else if(typeof arguments[0] === 'object'){
inheritanceArguments = (Array.prototype.slice.call(arguments));
}else{
inheritanceMethod = arguments[0];
inheritanceArguments = (Array.prototype.slice.call(arguments)).slice(1);
}
// All items inherit from vEweDefaultPrototype
inheritanceArguments.unshift({});
inheritanceArguments.unshift(this.vEweDefaultPrototype);
// Begin inheritance if we need to
proto = (typeof inheritanceMethod == 'string')? this[inheritanceMethod].apply(this,inheritanceArguments) : inheritanceMethod.apply(this,inheritanceArguments);
// All prototypes start from the vEweDefaultPrototype
VEwe.prototype = proto;
VEwe.prototype.factory = me;
// Return "Class" definition
return VEwe;
},
'inherit': function(){
var extendArgs = (Array.prototype.slice.call(arguments));
// Insure that we start with a new object (we don't want accidental inheritance)
extendArgs.unshift({});
// Runs an extend on all arguments
return this.$.extend.apply(this,extendArgs);
},
'inheritAndMergeEvents': function(){
// Runs an extend on all arguments
// Also merges all the events down the inheritance chain
var i,
protos = Array.prototype.slice.call(arguments),
proto,
events = [];
for(i=0;i<protos.length;i++){
if(typeof protos[i].events === 'object')
events = this._merge(events, protos[i].events);
}
proto = this.inherit.apply(this, arguments);
proto.events = events;
return proto;
},
'_merge': function(){
// A basic array merge method that takes an unlimited # of arguments
var i,
ii,
merged = [];
for(i=0;i<arguments.length;i++){
for(ii=0;ii<arguments[i].length;ii++){
merged.push(arguments[i][ii]);
}
}
return merged;
},
/**
* The default view prototype on which all views are based
*
* @lends vewe.VEweFactory#define~VEwe.prototype
*/
'vEweDefaultPrototype': {
/**
* A scoped instance of jQuery.
*
* @static
*/
'$': $,
/**
* The top-level selector used to scope this view's interaction within the DOM.
* @type {string}
*/
'selector': 'body',
/**
* The list of event instructions used to describe what event listeners to turn on / off.
*
* @type {object}
*/
'events': [],
/**
* A boolean indicator of the view's on / off state.
*
* @type {boolean}
*/
'isOn': false,
/**
* A method used to turn on a view's event listeners.
*
* @function
*/
'on': function(){
this._elementRefresh();
this._eventsOn();
this.$el.trigger('vEwe.on');
},
/**
* A method used to turn off a view's event listeners.
* @function
*/
'off': function(){
this._eventsOff();
this.$el.trigger('vEwe.off');
},
'_elementRefresh': function(){
delete this.element;
this.element = new Element(this.selector);
this.$el = this.element.get(); // For shortcut sake.
},
'_eventStandardize': function(rawEve){
var me = this,
eve = me.factory._merge([], rawEve),
handlerMaybe = eve[eve.length-1];
if(typeof handlerMaybe == 'string' && typeof me[handlerMaybe] == 'function')
eve[eve.length-1] = me[handlerMaybe];
// In jQuery's on method data is always the second to last argument.
// More hacktastic than I would like.
eve.splice(eve.length-1, 0, me);
return eve;
},
'_eventsOn': function(){
var i,
eve;
for(i=0;i<this.events.length;i++){
eve = this._eventStandardize(this.events[i]);
if(eve[0].indexOf(this.shepHeard.eventNamePrefix) === 0) this.shepHeard.element.$el.on.apply(this.shepHeard.element.$el, eve);
else this.$el.on.apply(this.$el, eve);
}
this.isOn = true;
},
'_eventsOff': function(){
var i,
eve;
for(i=0;i<this.events.length;i++){
eve = this._eventStandardize(this.events[i]);
if(eve[0].indexOf(this.shepHeard.eventNamePrefix) === 0) this.shepHeard.element.$el.off.apply(this.shepHeard.element.$el, [this.events[i][0]]);
else this.$el.off.apply(this.$el, [this.events[i][0]]);
}
this.isOn = false;
}
}
};
// Element
// Enhances the VEwe's top level DOM element.
// Note: This is currently overkill.
Element = function(selector){
this.options = {
'selector': selector
};
this.findAndSet();
return this;
};
Element.prototype = {
'$': $,
'find': function(){
return this.$(this.options.selector);
},
'findAndSet': function(){
this.$el = this.find();
},
'get': function(){
return this.$el;
}
};
/**
* The ShepHeard is an object used for cross VEwe communication.
* All views in a view set have access to a shared ShepHeard and may subscribe, unsubscribe, and publish events to the ShepHeard in a fairly typical pub / sub fashion.
* In a sense, the ShepHeard shepherds the views.
*
* @class
* @memberof vewe
*/
ShepHeard = function(){
this.element = new Element({});
return this;
};
/**
* @memberof vewe
* @lends ShepHeard.prototype
*/
ShepHeard.prototype = {
/**
* A scoped instance of jQuery.
*
* @static
*/
'$': $,
/**
* The string prefix used for all events in this ShepHeard names.
* Used to prevent event name collision.
*
* @type {string}
*/
'eventNamePrefix': 'shepheard_',
/**
* A method used to publish /trigger an event within this ShepHeard's scope.
*
* @function
* @param {string} eventName - The event name (un-prefixed) that is being published / triggered.
* @param {object} [options={}] - The options / data made available to the event.
*/
'publish': function(eventName, options){
if(typeof options == 'undefined') options = {};
this.element.get().trigger(this.eventNamePrefix + eventName, [options]);
},
/**
* A method used to subscribe the ShepHeard to an event and describe what action to take if the event is published / triggered.
*
* @function
* @param {string} eventName - The event name (un-prefixed) that is being subscribed to.
* @param {function} callback - The callback function that will be fired when the event is triggered.
*/
'subscribe': function(eventName, callback){
this.element.get().on(this.eventNamePrefix + eventName, callback);
},
/**
* A method used to un-subscribe the ShepHeard from an event.
*
* @function
* @param {string} eventName - The event name (un-prefixed) that is being subscribed to.
*/
'unsubscribe': function(eventName){
this.element.get().off(this.eventNamePrefix + eventName);
}
};
// The VEwe Factory is intended to be pseudo singleton in nature
// so we return an instance
return new VEweFactory();
}
);
})();