/*********************************************************************************************/
/* BlakeMedia Library
/*
/* DESC:    	BlakeMedia JavaScript library
/* AUTHOR:		Jared Williams (BlakeMedia)
/* VERSION:		2.3
/* CREATED: 	16 April 2013
/* MODIFIED:	15 April 2014
/*
/* (c) Copyright 2002-2014 BlakeMedia & MediaIQ Group PLC
/*********************************************************************************************/
var LOG_ERROR_FATAL=999,LOG_ERROR=888,LOG_WARNING=777,LOG_NOTICE=222,LOG_SYSTEM=111;var bm=function(_Options){var self=this;var bm=self;this.online=true;this.debug=false;this.events=true;this.page={url:"",protocol:"",domain:"",port:"",path:"",filename:"",ext:"",query:"",queries:[],hash:""};this.user={ip:"",agent:""};var _CookieDomain="",_CookiePath="/",_CookiePrefix="",_Plugins=[],_PluginAutoload=null,_PluginWarnings=true,_PluginOnLoad={},_ConsoleLogs=[],_Events=[],_EventQueueDelay=300,_EventTimeout=4000,_AllowOutputMsg=true,_OutputMethods={},_DefOutputMethod="",_AllowBasicOutput=true,_OnPluginLoad=function(){},_OnPluginUnload=function(){},_OnEventFire=function(){},_OnEventCatch=function(){},_OnEventTimeout=function(){},_OnEventFailed=function(){},_OnFireQueuedEvents=function(){},_OnOutputLoad=function(){},_OnConsoleLog=function(){},_SelSerializeInputs="INPUT, TEXTAREA, SELECT",_AllowRedirects=true,_SendReportOn=[];this.about=function(){return{name:"Library",version:"2.3",author:"Jared Williams (BlakeMedia)",datecreated:"16 Apr 2013",datemodified:"11 Apr 2014"}};this.setVars=function(Options){Options=bm.isPassed(Options,{});if("cookie" in Options){if("path" in Options.cookie){_CookiePath=Options.cookie.path}if("prefix" in Options.cookie){_CookiePrefix=Options.cookie.prefix}}if("reporton" in Options){_SendReportOn=Options.reporton}if("queuedelay" in Options){_EventQueueDelay=Options.queuedelay}if("page" in Options){bm.page=Options.page}if("timeout" in Options){_EventTimeout=Options.timeout}if("user" in Options){self.user=Options.user}if("serialize" in Options){_SelSerializeInputs=Options.serialize}if("events" in Options){bm.events=Options.events}if("debug" in Options){bm.debug=Options.debug}if("pluginwarn" in Options){_PluginWarnings=Options.pluginwarn}if("output" in Options){_AllowOutputMsg=Options.output}if("allowbasic" in Options){_AllowBasicOutput=Options.allowbasic}if("redirects" in Options){_AllowRedirects=Options.redirects}if("onpluginload" in Options){_OnPluginLoad=Options.onpluginload}if("onpluginunload" in Options){_OnPluginUnload=Options.onpluginunload}if("oneventfire" in Options){_OnEventFire=Options.oneventfire}if("oneventcatch" in Options){_OnEventCatch=Options.oneventcatch}if("oneventtimeout" in Options){_OnEventTimeout=Options.oneventtimeout}if("oneventfailed" in Options){_OnEventFailed=Options.oneventfailed}if("onfirequeued" in Options){_OnFireQueuedEvents=Options.onfirequeued}if("onoutputload" in Options){_OnOutputLoad=Options.onoutputload}if("onconsolelog" in Options){_OnConsoleLog=Options.onconsolelog}return true};this.onload=function(){bm.setVars(_Options);bm.log("BlakeMedia library starting up... (version "+bm.about().version+")",LOG_SYSTEM,bm);_getPageValues();_getUserValues();bm.onDocumentReady(function(){bm.log("Document is ready",LOG_SYSTEM,bm)})};function _getPageValues(){var page,filename,ext,queries={};filename=location.href.split("/");filename=filename[filename.length-1];ext=filename.split("?")[0];ext=ext.split("#")[0];ext=ext.split(".")[1];if(!ext){ext=""}page={url:location.href,protocol:location.protocol,domain:location.hostname,port:location.port,path:location.pathname,filename:filename,ext:ext,query:location.search,queries:_parseQueryString(),hash:location.hash.substring(1)};for(var prop in page){if(!bm.page[prop]){bm.page[prop]=page[prop]}}if(!_CookieDomain&&bm.page.domain){_CookieDomain="."+bm.page.domain}}function _getUserValues(){if(!self.user.agent&&"userAgent" in navigator){self.user.agent=navigator.userAgent}}function _parseQueryString(Query){Query=bm.isPassed(Query,window.location.search.substring(1));var queries={},match,pl=/\+/g,search=/([^&=]+)=?([^&]*)/g,decode=function(s){return decodeURIComponent(s.replace(pl," "))};while(match=search.exec(Query)){queries[decode(match[1])]=decode(match[2])}return queries}function _unsetPlugin(Rawname){bm[Rawname]=undefined}function _formatPluginName(Name){return Name.toLowerCase().replace(/[^0-9a-z-]/g,"")}function _chkPluginReq(Plugin,Strict){var passed=true,about;if(Plugin){if("about" in Plugin){about=Plugin.about()}else{about={}}var name=about.name,library=about.library,strict=about.strict;library=parseFloat(about.library).toFixed(1);if(!_chkPluginLibReq(library)){if(strict){bm.log("Failed to load plugin: incorrect library version!",LOG_ERROR,Plugin);passed=false}else{if(_PluginWarnings){bm.log("Plugin may not load correctly: check library version!",LOG_WARNING,Plugin)}}}if(!_chkPluginDepend(Plugin)){if(strict){bm.log("Plugin failed to load: check dependencies!",LOG_ERROR,Plugin);passed=false}else{if(_PluginWarnings){bm.log("Plugin may not load correctly: check dependencies!",LOG_WARNING,Plugin)}}}}else{padded=false}return passed}function _chkPluginLibReq(Version){if(Version==parseFloat(bm.about().version)){return true}}function _chkPluginDepend(Plugin){var about=bm.getPluginInfo(Plugin);var depends=about.depend,depended={},passed=true;for(var idx in depends){depended=depends[idx];if(bm.isPluginLoaded(depended.name)){loaded=bm.getPluginInfo(depended.name)[0].about;if(parseFloat(loaded.version)<parseFloat(depended.version)){bm.log('Plugin "'+about.shortname+'" requires version '+depended.version+" not "+loaded.version+' of plugin "'+depended.name+'"!',LOG_WARNING,bm);passed=false}}else{bm.log('Plugin "'+about.shortname+'" requires plugin "'+depended.name+'"!',LOG_WARNING,bm);passed=false}}return passed}function _fireQueuedEvents(Log){Log=bm.isPassed(Log,true);var loop,events,event,idx=0,queue=[];events=bm.getEventLog();for(var i=0;i<events;i++){event=events[i];if(event.queued){_Events[i].queued=false;queue[queue.length]=event}}_OnFireQueuedEvents(queue,events,_EventQueueDelay);if(queue.length>0){loop=setInterval(function(){if(queue[idx]){event=queue[idx];bm.refireEvent(event,false);idx++}else{clearInterval(loop);bm.log(""+idx+" queued events have been re-fired",LOG_NOTICE,bm);return true}},_EventQueueDelay)}else{if(Log){bm.log("No queued events to fire",LOG_NOTICE,bm)}}}function _formatEventData(Data){var elems={length:0},query={},extra="";if(typeof Data=="string"){elems=$('FORM[name="'+Data+'"]').find(_SelSerializeInputs);if(elems.length){query=elems.serializeJSON();extra=" (with form)"}else{elems=$(Data).find(_SelSerializeInputs);if(elems.length){query=elems.serializeJSON();extra=" (with form inputs)"}}if(!elems.length){query=Data;extra=" (with a string)"}}else{if(typeof Data=="object"){query=Data;extra=" (with data)"}}return{query:query,extra:extra}}function _performEventActions(Actions,Response){var action;for(var i in Actions){action=Actions[i];_performEventAction(action,Response)}}function _performEventAction(Action,Response){Action=bm.isPassed(Action,{});Response=bm.isPassed(Response,{});var badaction=false;if(Response.log){var logextra="";if(Action.action){logextra+=Action.action}if(Action.property){logextra+=" "+Action.property}if(Action.element){logextra+=' on element "'+Action.element+'"'}bm.log("Performing "+logextra+"...",LOG_NOTICE,"AJAX")}if(Action.element){var elem={};if(Action.element.indexOf("#")===-1){elem=$("#"+Action.element)}if(!elem.length){elem=$(Action.element)}if(elem.length){switch(Action.property){case"HTML":case"innerHTML":switch(Action.action){case"assign":elem.html(Action.data);break;case"empty":elem.empty();break;case"append":elem.append(Action.data);break;default:badaction=true}break;case"class":switch(Action.action){case"assign":elem.attr("class",Action.data);break;case"add":elem.addClass(Action.data);break;case"remove":elem.removeClass(Action.data);break;default:badaction=true}break;case"style":switch(Action.action){case"assign":elem.attr("style",Action.data);break;default:badaction=true}break;default:switch(Action.action){case"assign":elem.attr(Action.property,Action.data);break;case"remove":case"delete":elem.remove();break;case"trigger":elem.trigger(Action.data);break;default:badaction=true}break}}else{bm.log('Failed to use element for action: element "'+Action.element+'" does not exist!',LOG_ERROR,"AJAX")}}else{switch(Action.action){case"eval":eval(Action.data);break;case"redirect":bm.redirectPage(Action.data);break;case"call":_performCallAction(Action.data);break;case"notify":bm.outputMessage(Action.data.message,Action.data.title,Action.data.type,Action.data.method);break;default:badaction=true}}if(badaction){bm.log('Failed to perform action "'+Action.action+'": action not supported!',LOG_ERROR,"AJAX")}else{return true}}function _performCallAction(Func){if(Func){var funcname,func,allprops,props,obj;funcname=Func.split("(")[0],func=eval(funcname);allprops=funcname.split(".");obj=window[allprops[0]];allprops.shift();props=allprops.join(".");if(typeof func=="function"||bm.isProperty(obj,props)){eval(Func);return true}else{bm.log('Failed to perform call action: "'+funcname+'" does not exist!',LOG_ERROR,"AJAX")}}else{bm.log("Failed to perform call action: no function name provided!",LOG_ERROR,"AJAX")}}this.getLibraryInfo=function(){return{about:bm.about(),params:_Options}};this.loadResource=function(URL,Type,Callback,IfFailed){Type=bm.isPassed(Type,"");Callback=bm.isPassed(Callback,function(){console.log("Test")});IfFailed=bm.isPassed(IfFailed,function(){});var elem,onload,onerror,loaded=false;onload=function(Event){if(!loaded&&(elem&&(!elem.readyState||elem.readyState=="complete"))){loaded=true;bm.log('Loaded resource "'+URL+'"',LOG_NOTICE,bm);Callback(elem)}else{if(elem&&elem.readyState=="loaded"){onerror(Event)}}};onerror=function(Msg){Msg=bm.isPassed(Msg,"Unknown error");bm.log('Failed to load resource "'+URL+'": '+Msg,LOG_ERROR,bm);IfFailed(elem)};if(URL){if(!Type){Type=URL.split(".").pop()}switch(Type.toLowerCase()){case"js":case"script":case"javascript":elem=document.createElement("script");elem.type="text/javascript";elem.onreadystatechange=onload;elem.onload=onload;elem.onerror=onerror;elem.ontimeout=onerror;elem.src=URL;document.getElementsByTagName("head")[0].appendChild(elem);break;case"css":case"stylesheet":case"style":elem=document.createElement("link");elem.onerror=onerror;elem.ontimeout=onerror;elem.rel="stylesheet";elem.type="text/css";elem.href=URL;onload();break;case"jpg":case"jpeg":case"png":case"gif":case"image":case"img":elem=new Image;elem.onload=onload;elem.onerror=onerror;elem.ontimeout=onerror;elem.src=URL;break;case"html":case"txt":case"php":default:onload=function(Response){Callback(Response)};$.get(URL,{},function(Response){onload(Response)}).fail(onerror)}}else{bm.log("Failed to load resource: no URL passed!",LOG_ERROR,bm)}};this.isPassed=function(Param,Default){if(typeof Param==="undefined"||Param===null){if(typeof Default!=="undefined"&&Default!==null){Param=Default}else{return undefined}}else{if(typeof Param==="object"&&typeof Default==="object"){for(var key in Default){if(!(key in Param)){Param[key]=Default[key]}}}}return Param};this.getPlugin=function(Plugin){if(typeof Plugin=="string"){return bm[Plugin]}else{if(typeof Plugin=="object"){if("shortname" in Plugin){return bm[Plugin.shortname]}else{return Plugin}}}bm.log('Failed to get plugin: invalid identifier (type "'+typeof Plugin+'")!',LOG_ERROR,bm)};this.loadPlugin=function(PluginName,Params,LoadAs){Params=bm.isPassed(Params,{});LoadAs=bm.isPassed(LoadAs,"");var name,loadname,as="",plugin,about,disallowed=[],betamode=false;if(PluginName){if(typeof PluginName=="string"){PluginName=PluginName.toString();if(LoadAs){loadname=_formatPluginName(LoadAs);as=' as "'+loadname+'"'}else{loadname=PluginName}if(!bm.isPluginLoaded(loadname)){var fn=window[PluginName];if(typeof fn!="undefined"){bm[loadname]=new fn(Params);plugin=bm.getPlugin(loadname);if(plugin){if("about" in plugin){about=plugin.about();if(typeof about!="object"){bm.log('Plugin "'+PluginName+'" meta information not found!',LOG_WARNING,bm);about={name:PluginName,shortname:PluginName}}if(_chkPluginReq(plugin,about.strict)){var pluginid=_Plugins.length,meta,version="";meta={name:PluginName,shortname:about.shortname,loadas:loadname,about:about,params:Params};if("version" in about){version=about.version}_Plugins[pluginid]=meta;if("beta" in about||version.toString().indexOf("b")!==-1){betamode=true}if("options" in plugin){plugin.options(Params)}if("debug" in Params||"debug" in about){if(Params.debug||about.debug){bm.enableDebugMode()}}if("onload" in plugin){plugin.onload()}plugin.unload=function(){bm.unloadPlugin(meta.shortname)};plugin.reload=function(){bm.reloadPlugin(meta.shortname)};if(meta.shortname in _PluginOnLoad){var onload=_PluginOnLoad[meta.shortname];onload(plugin,meta)}_OnPluginLoad(plugin,meta);bm.log('Loaded plugin "'+PluginName+'"'+as,LOG_NOTICE,bm);if(betamode){bm.log('Plugin "'+PluginName+'" is in BETA and is NOT intended for production use!',LOG_WARNING,bm)}return true}else{_unsetPlugin(loadname)}}else{bm.log('Failed to load plugin "'+PluginName+'": no about() function!',LOG_ERROR,bm);_unsetPlugin(loadname)}}else{bm.log('Failed to load plugin "'+PluginName+'": failed to assign to object property!',LOG_ERROR_FATAL,bm)}}else{bm.log('Failed to load plugin "'+PluginName+'": not found!',LOG_ERROR,bm)}}else{bm.log('Failed to load plugin "'+PluginName+'": namespace in use!',LOG_ERROR,bm)}}else{bm.log("Failed to load plugin: invalid plugin name provided!",LOG_ERROR,bm)}}else{bm.log("Failed to load plugin: no name provided!",LOG_ERROR,bm)}};this.unloadPlugin=function(Name){var complete=false,plugin,unload;if(bm.isPluginLoaded(Name)){plugin=bm.getPlugin(Name);if("onunload" in plugin){plugin.onunload()}_unsetPlugin(Name);for(var i=0;i<_Plugins.length;i++){plugin=_Plugins[i];if(plugin.loadas==Name){_Plugins.splice(i,1);complete=true}}if(complete){_OnPluginUnload(plugin);bm.log('Unloaded plugin "'+Name+'"',LOG_NOTICE,bm);return true}else{bm.log('Failed to unload plugin "'+Name+'": unknown error!',LOG_ERROR,bm)}}else{bm.log('Failed to unload plugin "'+Name+'": not loaded!',LOG_ERROR,bm)}};this.reloadPlugin=function(Name){var plugin,params,loadas;if(bm.isPluginLoaded(Name)){plugin=bm.getPluginInfo(Name);if(bm.unloadPlugin(Name)){bm.loadPlugin(Name,plugin.params,plugin.loadas)}}else{bm.log('Failed to reload plugin "'+Name+'": not loaded!',LOG_ERROR,bm)}};this.isPluginLoaded=function(Name){var raw,saved;if(Name){Name=_formatPluginName(Name);raw=bm.getPlugin(Name);saved=bm.getPluginInfo(Name);if(typeof raw!=="undefined"&&saved.length>0){return true}else{if(typeof raw!=="undefined"){bm.log('Plugin "'+Name+'" in namespace but not loaded as plugin!',LOG_WARNING,bm);return true}else{if(saved.length>0){bm.log('Plugin "'+Name+'" not in namespace but loaded as plugin!',LOG_ERROR,bm)}else{}}}}else{bm.log("Failed to check if plugin is loaded: no plugin name provided!",LOG_ERROR,bm)}return false};this.getPluginInfo=function(Name){var info=[];for(var i=0;i<_Plugins.length;i++){var plugin=_Plugins[i];if(Name==plugin.loadas||!Name){info[info.length]=_Plugins[i]}}return info};this.getPluginNames=function(LoadNames){LoadNames=bm.isPassed(LoadNames,false);var plugins,names=[],get="name";if(LoadNames){get="loadas"}plugins=bm.getPluginInfo();for(var i=0;i<plugins.length;i++){names[names.length]=plugins[i][get]}return names};this.onPluginLoad=function(Name,Call,IfLoaded){IfLoaded=bm.isPassed(IfLoaded,true);if(Name){if(typeof Call=="object"){var params=Call;Call=function(Plugin){Plugin.options(params)}}_PluginOnLoad[Name]=Call;if(bm.isPluginLoaded(Name)&&IfLoaded){Call(bm.getPlugin(Name),bm.getPluginInfo(Name))}}else{bm.log("Failed to call function on plugin load: no plugin name!",LOG_ERROR,self)}};this.getEvent=function(Event){Event=bm.isPassed(Event,"");if(typeof Event=="object"){return Event}var eventid,events=self.getEventLog(),event;for(eventid in events){event=events[eventid];if(event.name==Event||eventid==Event){return event}}return false};this.getLastEvent=function(){var events=self.getEventLog();return events[events.length-1]};this.fireEvent=function(Name,Data,Call,Flag,FireQueued){Data=bm.isPassed(Data,{});Call=bm.isPassed(Call,{Catcher:function(){},Failed:function(){},Timeout:function(){}});Flag=bm.isPassed(Flag,{Queue:false,Async:true,Actions:true,Log:true});FireQueued=bm.isPassed(FireQueued,true);var data,eventid,event,timestamp,queue="";if(Name){Name=Name.toString();data=_formatEventData(Data);eventid=_Events.length,timestamp=bm.getTimestamp();event={id:eventid,eventid:eventid,name:Name,data:data.query,rawdata:Data,oncatch:Call.Catcher,onfail:Call.Failed,ontimeout:Call.Timeout,queue:Flag.Queue,async:Flag.Async,doactions:Flag.Actions,log:Flag.Log,queued:false,fired:timestamp,received:0,response:""};_Events[eventid]=event;if(bm.events){if(FireQueued){_fireQueuedEvents(false)}$.ajax({type:"POST",url:bm.page.url,data:{ajax:{name:Name,eventid:eventid,timestamp:timestamp,data:data.query}},cache:false,success:function(Response,Status){Call.Catcher(event,Response,Status);_OnEventCatch(event,Response,Status);bm.catchEventResponse(event,Response,Status,Flag.Log)},error:function(Request,Status,ErrorThrown){var errordata={request:Request,status:Status,error:ErrorThrown};switch(Status.toLowerCase()){case"timeout":Call.Timeout(event,errordata);_OnEventTimeout(event,errordata);break;case"parsererror":bm.log("Failed to parse event response! Make sure event responder has been set up correctly!",LOG_ERROR,self);break;case"error":case"abort":default:Call.Failed(event,errordata);_OnEventFailed(event,errordata)}bm.catchEventResponse(event,errordata,Status,Event.Log)},async:Flag.Async,timeout:_EventTimeout});_OnEventFire(event);if(Flag.Log){bm.log('Fired event "'+Name+'"'+data.extra,LOG_NOTICE,"AJAX")}return true}else{if(Flag.Queue){_Events[eventid].queued=true;queue=" (queued)"}if(Flag.Log){bm.log('Failed to fire event "'+Name+'": events disabled'+queue,LOG_NOTICE,"AJAX")}}}else{bm.log("Failed to fire event: no event name provided!",LOG_ERROR,"AJAX")}};this.event=function(Name,Data,Call,Flag,FireQueued){bm.fireEvent(Name,Data,Call,Flag,FireQueued)};this.fire=function(Name,Data,Call,Flag,FireQueued){bm.fireEvent(Name,Data,Call,Flag,FireQueued)};this.refireEvent=function(Event,FireQueued){var event=bm.getEvent(Event);if(event){bm.fireEvent(event.name,event.rawdata,{Catcher:event.oncatch,Failed:event.onfail,Timeout:event.ontimeout},{Queue:event.queue,Async:event.async,Log:event.log,Actions:event.doactions},FireQueued)}else{bm.logError('Failed to re-fire event "'+Event+'": could not find event!',bm)}return false};this.catchEventResponse=function(Event,Response,Status,Log){Response=bm.isPassed(Response,{eventid:0,actions:false,error:""});Status=bm.isPassed(Status,"success");Log=bm.isPassed(Log,true);var event,eventid,actionscount=0,error="";if(Response&&typeof Response=="object"){if(Response.error||Response.status){if(Response.error){error=Response.error}else{if(Response.status){error=Response.status}}bm.log("Failed to catch event response: "+error,LOG_ERROR,"AJAX")}else{if(Response.actions){actionscount=Response.actions.length;_performEventActions(Response.actions,Response)}else{if(Log){bm.log("No actions to perform",LOG_NOTICE,"AJAX")}}}eventid=Response.eventid,event=_Events[eventid];if(event){event.received=bm.time();event.response=Response;if(Log){bm.log('Received response for "'+event.name+'" ('+actionscount+" action(s))",LOG_NOTICE,"AJAX")}}else{bm.log('Failed to parse response for event "'+eventid+'": unknown event!',LOG_ERROR,"AJAX")}}else{bm.log("Failed to parse event response: received no or invalid server response!",LOG_ERROR,"AJAX")}};this.getEventLog=function(){return _Events};this.fireQueuedEvents=function(){return _fireQueuedEvents()};this.onDocumentReady=function(Script){$(document).ready(Script)};this.closePage=function(){bm.log("Closing page...",LOG_NOTICE,self);window.close()};this.printPage=function(){bm.log("Attempting to print page...",LOG_NOTICE,self);window.print()};this.scrollTop=function(){window.scroll(0,0)};this.backPage=function(){bm.log("Navigating to previous page...",LOG_NOTICE,self);history.back()};this.refreshPage=function(Delay){bm.redirectPage("",Delay)};this.disableRedirects=function(){_AllowRedirects=false;return true};this.enableRedirects=function(){_AllowRedirects=true;return true};this.redirectPage=function(URL,Delay){Delay=bm.isPassed(Delay,0);if(_AllowRedirects){setTimeout(function(){if(URL){bm.log('Redirecting page to "'+URL+'"...',LOG_NOTICE,bm);window.location=URL}else{bm.log("Refreshing page...",LOG_NOTICE,bm);location.reload()}},Delay);return true}else{bm.log("Failed to redirect page: page redirecting disabled!",LOG_WARNING,bm)}};this.convertToStr=function(Raw){var type=typeof Raw;if(Raw instanceof Array){type="array"}switch(type){case"object":return bm.convertObjToStr(Raw);break;case"array":return bm.convertArrToStr(Raw);break;case"function":return bm.convertFuncToStr(Raw);break;default:return Raw.toString()}};this.convertObjToStr=function(Obj){var parse=function(obj){var a=[],t,objstr,returnstr,thisa,keyvalue,newa;for(var prop in obj){if(obj.hasOwnProperty(prop)){t=obj[prop];newa=prop+": ";if(typeof t=="string"){newa+='"'+t+'"'}else{newa+=bm.convertToStr(t)}a.push(newa)}}return a};return"{ "+parse(Obj).join(", ")+" }"};this.convertArrToStr=function(Arr){var a=[];for(var i=0,len=Arr.length;i<len;i++){a[a.length]=bm.convertToStr(Arr[i])}return"["+a.join(", ")+"]"};this.convertFuncToStr=function(Func){return Func.toString()};this.loadOutputMethod=function(Name,Func,Default){Default=bm.isPassed(Default,false);if(!Name){bm.log("Failed to load output method: no method name provided!",LOG_ERROR,bm);return}if(!Func){bm.log("Failed to load output method: no method function provided!",LOG_ERROR,bm);return}if(Default){_DefOutputMethod=Name;var ifdefault=" as default"}else{var ifdefault=""}bm.log('Loaded output method "'+Name+'"'+ifdefault,LOG_NOTICE,bm);var method={name:Name,call:Func};_OutputMethods[Name]=method;_OnOutputLoad();return true};this.outputMessage=function(Message,Title,Type,Method){Title=bm.isPassed(Title,"Message");var first,def,method,call;if(_AllowOutputMsg){for(var idx in _OutputMethods){if(!first){first=idx}}if(_DefOutputMethod){def=_DefOutputMethod}else{if(first){def=first}}if(_OutputMethods[Method]){call=_OutputMethods[Method].call}else{if(def){call=_OutputMethods[def].call}else{if(_AllowBasicOutput){bm.log("No output method loaded, using basic method...",LOG_WARNING,bm);call=function(Message,Title){alert(Title+"\n\n"+Message)}}else{call=function(Message,Title){bm.log("No output method loaded!",LOG_ERROR,bm)}}}}call(Message,Title,Type);return true}};this.output=function(Message,Title,Type,Method){return bm.outputMessage(Message,Title,Type,Method)};this.getOutputMethods=function(){return _OutputMethods};this.getStylesheet=function(URL){if(URL){var sheet=$('LINK[href="'+URL+'"][rel="stylesheet"], LINK[data-href="'+URL+'"][rel="stylesheet"]');if(sheet.length==1){return sheet}else{if(sheet.length>1){bm.log('Found multiple stylesheets with URL "'+URL+'", using first...',LOG_WARNING,bm);return $(sheet[0])}else{bm.log('Failed to get stylesheet "'+URL+'": not found!',LOG_WARNING,bm)}}}else{bm.log("Failed to get stylesheet: no URL provided!",LOG_ERROR,bm)}};this.getStylesheets=function(URLs){URLs=bm.isPassed(URLs,[]);var sheets,sheet,urls=[],url;sheets=$('LINK[rel="stylesheet"]');if(sheets.length){if(URLs.length){sheets.each(function(){sheet=$(this);url=sheet.attr("data-href");if(!url){url=sheet.attr("href")}if(url){urls[urls.length]=url}});sheets=urls}}else{bm.log("Failed to get stylesheets: none found!",LOG_WARNING,bm)}return sheets};this.refreshStylesheet=function(URL){URL=bm.isPassed(URL);var error="",old={length:0};if(URL){if(typeof URL=="object"){old=$(URL);if(old.length){URL=old.attr("data-href");if(!URL){URL=old.attr("href")}}}else{if(typeof URL=="string"){old=$('LINK[href="'+URL+'"][rel="stylesheet"], LINK[data-href="'+URL+'"][rel="stylesheet"]')}}if(old.length){if(!old.attr("data-old")){var sheet=old.clone();old.attr("data-old","true");var newURL=URL+"?"+bm.randNum(0,1000);sheet.attr("href",newURL);sheet.on("load",function(){old.remove()});sheet.appendTo("BODY");bm.log('Stylesheet "'+URL+'" refreshed',LOG_NOTICE,bm);return newURL}else{error="stylesheet already refreshing!"}}else{error='no stylesheet found with URL "'+URL+'"!'}}else{error="no URL provided!"}bm.log("Failed to refresh stylesheet: "+error,LOG_ERROR,bm)};this.refreshStylesheets=function(URLs){URLs=bm.isPassed(URLs,[]);if(!URLs.length){URLs=bm.getStylesheets(true)}if(URLs){for(var i=0;i<URLs.length;i++){bm.refreshStylesheet(URLs[i])}bm.log(i+" stylesheets refreshed",LOG_NOTICE,bm)}else{bm.log("Failed to refresh stylesheets: no stylesheets found!",LOG_WARNING,bm)}};this.removeStylesheet=function(URL){var sheet;URL=bm.isPassed(URL,"");sheet=bm.getStylesheet(URL);if(sheet){sheet.remove();return true}else{bm.log('Failed to remove stylesheet "'+URL+'": does not exist!',LOG_ERROR,bm);return false}};this.removeStylesheets=function(){var sheets;sheets=bm.getStylesheets();if(sheets){sheets.remove()}return true};this.getKeyName=function(Key){var keycodes={8:"Backspace",9:"Tab",13:"Enter",16:"Shift",17:"Ctrl",18:"Alt",19:"Pause",20:"Caps Lock",27:"Esc",32:"Space",33:"Page Up",34:"Page Down",35:"End",36:"Home",37:"Left",38:"Up",39:"Right",40:"Down",45:"Insert",46:"Delete",48:"0",49:"1",50:"2",51:"3",52:"4",53:"5",54:"6",55:"7",56:"8",57:"9",65:"A",66:"B",67:"C",68:"D",69:"E",70:"F",71:"G",72:"H",73:"I",74:"J",75:"K",76:"L",77:"M",78:"N",79:"O",80:"P",81:"Q",82:"R",83:"S",84:"T",85:"U",86:"V",87:"W",88:"X",89:"Y",90:"Z",91:"Windows",92:"Windows",93:"Right Click",96:"Numpad 0",97:"Numpad 1",98:"Numpad 2",99:"Numpad 3",100:"Numpad 4",101:"Numpad 5",102:"Numpad 6",103:"Numpad 7",104:"Numpad 8",105:"Numpad 9",106:"Numpad *",107:"Numpad +",109:"Numpad -",110:"Numpad .",111:"Numpad /",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"Num Lock",145:"Scroll Lock",182:"My Computer",183:"My Calculator",186:";",12:"Clear",61:"=",187:"=",188:",",173:"-",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'",224:"Command"};if(typeof Key=="object"){if("which" in Key){return self.getKeyName(Key.which)}}else{if(Key=keycodes[parseInt(Key)]){return Key}}return false};this.serializeJSON=function(Elem){Elem=$(Elem);var json={};$.map(Elem.serializeArray(),function(n,i){json[n.name]=n.value});return json};this.randNum=function(From,To){From=bm.isPassed(From,0);To=bm.isPassed(To,100);return From+Math.floor(Math.random()*To)};this.ucFirst=function(Str){Str=bm.isPassed(Str,"");return Str.charAt(0).toUpperCase()+Str.slice(1)};this.getTimestamp=function(){return Math.round((new Date()).getTime()/1000)};this.time=function(){return bm.getTimestamp()};this.decodeRot13=function(Str){if(typeof Str==="string"){var a,A,b=[],c,i=Str.length,z,Z;var rot=function(t,u,v){return String.fromCharCode(((t-u+v)%(v*2))+u)};a="a".charCodeAt(),z=a+26,A="A".charCodeAt(),Z=A+26;while(i--){c=Str.charCodeAt(i);if(c>=a&&c<z){b[i]=rot(c,a,13)}else{if(c>=A&&c<Z){b[i]=rot(c,A,13)}else{b[i]=Str.charAt(i)}}}return b.join("")}else{bm.log('Failed to decode Rot13 string "'+Str+'": variable is of type "'+(typeof Str)+'"!',LOG_ERROR,bm);return""}};this.isObjectEmpty=function(Obj,IgnoreLength){IgnoreLength=bm.isPassed(IgnoreLength,false);if(typeof Obj=="object"){if(Obj==null){return true}if(!IgnoreLength){if(Obj.length&&Obj.length>0){return false}if(Obj.length===0){return true}}for(var key in Obj){if(hasOwnProperty.call(Obj,key)){return false}}}else{bm.log('Failed to check if object "'+Obj+'" is empty: variable is of type "'+(typeof Obj)+'"!',LOG_ERROR,bm)}return true};this.isProperty=function(Obj,Prop){var parts=Prop.split(".");for(var i=0;i<parts.length;i++){var part=parts[i];if(Obj!==null&&typeof Obj==="object"&&part in Obj){Obj=Obj[part]}else{return false}}return true};this.pwidth=function(Elem){return parseFloat((parseFloat(Elem.outerWidth())/parseFloat(Elem.parent().outerWidth())*100).toFixed(2))};this.outerHTML=function(Elem,Str){return Str?Elem.before(Str).remove():jQuery("<p>").append(Elem.eq(0).clone()).html()};this.disableSelect=function(Elem){return Elem.attr("unselectable","on").css("user-select","none").on("selectstart",false)};this.elemExists=function(Elem){if(typeof Elem=="string"){Elem=$(Elem)}return(Elem.length>0)};this.deprecate=function(OldName,NewName){OldName=bm.isPassed(OldName,"");NewName=bm.isPassed(NewName,"");if(OldName){OldName=OldName+"()"}else{OldName="This function"}if(NewName){NewName=" Use "+NewName+"() instead!"}bm.log(OldName+" is deprecated!"+NewName,LOG_WARNING,bm);return true};this.enableDebugMode=function(){return bm.debug=true};this.disableDebugMode=function(){return bm.debug=false};this.logConsole=function(Message,Priority,Caller,Timestamp){Message=bm.isPassed(Message,"No message was provided");Priority=bm.isPassed(Priority,LOG_NOTICE);Caller=bm.isPassed(Caller,"Library");Timestamp=bm.isPassed(Timestamp,bm.getTimestamp());Message=Message.toString();var log,logid=_ConsoleLogs.length,prioritylabel="";if(typeof Caller=="object"){if("about" in Caller){Caller=Caller.about();if(typeof Caller!="object"){Caller={name:"Unknown"}}Caller=Caller.name}}else{if(typeof Caller!="string"){Caller="Unknown"}}switch(Priority){case LOG_ERROR_FATAL:prioritylabel="fatal";break;case LOG_ERROR:prioritylabel="error";break;case LOG_WARNING:prioritylabel="warning";break;case LOG_SYSTEM:prioritylabel="system";break;case LOG_NOTICE:prioritylabel="notice";break}var log={id:logid,logid:logid,message:Message,priority:Priority,prioritylabel:prioritylabel,caller:Caller,timestamp:Timestamp,logged:bm.debug};_ConsoleLogs[logid]=log;if(!("console" in window)){return false}if(bm.debug!=true){return false}var cslmsg=Message,cslcaller="["+Caller+"]",cslpriority="";switch(Priority){case LOG_ERROR_FATAL:cslpriority="FATAL";break;case LOG_ERROR:cslpriority="ERROR";break;case LOG_WARNING:cslpriority="WARNING";break;case LOG_SYSTEM:cslpriority="SYSTEM";break;case LOG_NOTICE:default:cslpriority=""}while(cslcaller.length<20){cslcaller+=" "}while(cslpriority.length<10){cslpriority+=" "}cslmsg=cslcaller+cslpriority+cslmsg;console.log(cslmsg);_OnConsoleLog(log,cslmsg);if(Priority==LOG_ERROR_FATAL){var newbm={},key;for(key in bm){newbm[key]=function(){}}bm=window.bm=newbm}return true};this.log=function(Message,Priority,Caller,Timestamp){return bm.logConsole(Message,Priority,Caller,Timestamp)};this.logWarning=function(Message,Caller,Timestamp){return bm.logConsole(Message,LOG_WARNING,Caller,Timestamp)};this.logError=function(Message,Caller,Timestamp){return bm.logConsole(Message,LOG_ERROR,Caller,Timestamp)};this.error=function(Message,Caller,Timestamp){return bm.logError(Message,Caller,Timestamp)};this.getConsoleLogs=function(){return _ConsoleLogs};bm.onload()};$.fn.serializeJSON=function(){bm.deprecate("$.fn.serializeJSON","bm.serializeJSON");return bm.serializeJSON($(this))};$.fn.pwidth=function(){bm.deprecate("$.fn.pwidth","bm.pwidth");return bm.pwidth($(this))};$.fn.externalHTML=function(a){bm.deprecate("$.fn.externalHTML","bm.outerHTML");return bm.outerHTML($(this),a)};$.fn.disableSelect=function(){bm.deprecate("$.fn.disableSelect","bm.disableSelect");return bm.disableSelect($(this))};/*********************************************************************************************/
/* BlakeMedia Library
/* "Library UI" plugin
/*
/* DESC:    	Advanced user interfaces.
/* AUTHOR:		Jared Williams (BlakeMedia)
/* VERSION:		1.33
/* CREATED: 	16 December 2014
/* MODIFIED:	02 February 2015
/*
/* (c) Copyright 2002-2015 BlakeMedia & MediaIQ Group PLC
/*********************************************************************************************/

/* TODO 1.4:
	- modal + slideshow next/prev btns
	- modal size adapts to image sizes
	- find modal template using modal name
*/

var ui = function() {
		var self = this;
		
		
		
		//*************************************************************************[ PUBLIC VARIABLES ]
		this.instantiated = false;
		
		
		
		//************************************************************************[ PRIVATE VARIABLES ]
		var
			_LoadAsDefault					= false,
			_LastSlideshowID				= 0,
			
			//Storage...
			_Modals									= [],
			_Cover									= null,
			_ModalOutput						= null,
			
			//Defaults...
			_DefModalAnim						= 'slide',
			_DefModalSpeed					= 1000,
			
			_DefModalCoverAnim			= 'fade',
			_DefModalCoverSpeed			= 1000,
			
			//Flags...
			_AutoBtn								= true,
			_AutoToggleBtn					= true,
			_AutoSlideshow					= true,
			_AutoDropdown						= true,
			_AutoModal							= true,
			_AutoResizeModals				= true,
			
			//Selectors...
			_SelectToggleGroup			= '.toggle-radio-btn-group',
			_SelectBtn							= '.btn',
			_SelectClickBtn					= '.click-btn',
			_SelectToggleBtn				= '.toggle-btn',
			
			//Slideshow...
			_SelectSlideshow				= '.slideshow',
			_SelectSlide						= '.slide',
			_SelectSlideGroup				= '.slides',
			_SelectSlideNav					= '.slide-nav',
			_SelectSlideBtn					= '.slide-btn',
			_SelectSlideNext				= '.next-btn',
			_SelectSlidePrev				= '.prev-btn',
			
			//Dropdown...
			_SelectDropdown					= '.dropdown',
			_SelectDropdownBtn			= '.dropdown-btn',
			_SelectDropdownMenu			= '.dropdown-menu',
			
			//Modals...
			_SelectModal						= '.modal',
			_SelectCover						= '.modal-cover',
			_SelectModalTrigger			= '[data-modal-content]', //TODO: data-trigger="modal" or data-ui="modal" ?

			//Other...
			_SelectPlaceholder			= 'INPUT[placeholder]',
			_SelectRot13						= '[data-email]',

			//Classes...
			_ClassOn								= 'on',
			_ClassDisabled					= 'disabled',
			_ClassVisible 					= 'visible',
			_ClassClip							= 'clip',

			//Classes - modals...
			_ClassModal							= 'modal',
			_ClassModalContent			= 'modal-content',
			_ClassModalCtrl					= 'modal-ctrl',
			_ClassModalClose				= 'modal-close',
			_ClassModalInvis				= 'invisible',
			_ClassModalNav					= 'modal-nav',
			_ClassModalPrev					= 'modal-prev',
			_ClassModalNext					= 'modal-next',
			_ClassModalCover				= 'modal-cover',
			_ClassModalCoverInvis		= 'invisible',
			
			//Handlers... TODO: Use these as the default and allow dev to REPLACE them (not provide as alternative)!
			_ShowModal							= function() {},
			_HideModal							= function() {},
			_ShowCover							= function() {},
			_HideCover							= function() {},
			
			//Events...
			_OnModalCreate					= function() {},
			_OnModalShow						= function() {},
			_OnModalHide						= function() {};
			
		
		
		//******************************************************************************[ PLUGIN META ]
		
		/************************************************************
		FUNCTION: about()
		DESC:			Gets basic info about this plugin.
		************************************************************/
		this.about = function() {
			return {
				name: 				'Library UI',
				shortname:		'ui',
				description:	'Advanced user interfaces.',
				version:			'1.33',
				library:			'2.3',
				author:				'Jared Williams (BlakeMedia)',
				datecreated:	'16 Dec 2014',
				datemodified:	'02 Feb 2015'
			};
		} //END about()
		
		
		/************************************************************
		FUNCTION: options()
		DESC:			Fills internal variables.
		************************************************************/
		this.options = function(Options) {
			Options = bm.isPassed(Options, {});
			
			if (Options) {
				if ('asdefault' in Options) {
					_LoadAsDefault = Options.asdefault;
				}
				
				//Modal...
				if ('modalanim' in Options) {
					_DefModalAnim = Options.modalanim;
				}
				if ('modalspeed' in Options) {
					_DefModalSpeed = Options.modalspeed;
				}
				if ('coveranim' in Options) {
					_DefModalCoverAnim = Options.coveranim;
				}
				if ('coverspeed' in Options) {
					_DefModalCoverSpeed = Options.coverspeed;
				}
				
				//Others...
				if ('placeholder' in Options) {
					_SelectPlaceholder = Options.placeholder;
				}
				if ('rot13' in Options) {
					_SelectRot13 = Options.rot13;
				}
				
				//Classes...
				if ('on' in Options) {
					_ClassOn = Options.on;
				}
				if ('disabled' in Options) {
					_ClassDisabled = Options.disabled;
				}
				if ('visible' in Options) {
					_ClassVisible = Options.visible;
				}
				if ('clip' in Options) {
					_ClassClip = Options.clip;
				}
				
				//Modals...
				if ('modalclass' in Options) {
					_ClassModal = Options.modalclass;
				}
				if ('modalcontent' in Options) {
					_ClassModalContent = Options.modalcontent;
				}
				if ('modalctrl' in Options) {
					_ClassModalCtrl = Options.modalctrl;
				}
				if ('modalclose' in Options) {
					_ClassModalClose = Options.modalclose;
				}
				if ('modalinvis' in Options) {
					_ClassModalInvis = Options.modalinvis;
				}
				if ('coverinvis' in Options) {
					_ClassModalCoverInvis = Options.coverinvis;
				}
				
				//Flags...
				if ('auto' in Options) {
					if ('btn' in Options.auto) {
						_AutoBtn = Options.auto.btn;
					}
					if ('togglebtn' in Options.auto) {
						_AutoToggleBtn = Options.auto.togglebtn;
					}
					if ('slideshow' in Options.auto) {
						_AutoSlideshow = Options.auto.slideshow;
					}
					if ('dropdown' in Options.auto) {
						_AutoDropdown = Options.auto.dropdown;
					}
					if ('resizemodals' in Options.auto) {
						_AutoResizeModals = Options.auto.resizemodals;
					}
				}
				
				//Selectors - modal...
				if ('modal' in Options) {
					_SelectModal = Options.modal;
				}
				if ('modaltrigger' in Options) {
					_SelectModalTrigger = Options.modaltrigger;
				}
				if ('cover' in Options) {
					_SelectCover = Options.cover;
				}
				
				//Selectors - slideshow...
				if ('slideshow' in Options) {
					_SelectSlideshow = Options.slideshow;
				}
				if ('slide' in Options) {
					_SelectSlide = Options.slide;
				}
				if ('slidegroup' in Options) {
					_SelectSlideGroup = Options.slidegroup;
				}
				if ('slidenav' in Options) {
					_SelectSlideNav = Options.slidenav;
				}
				if ('slidebtn' in Options) {
					_SelectSlideBtn = Options.slidebtn;
				}
				if ('nextslide' in Options) {
					_SelectSlideNext = Options.nextslide;
				}
				if ('prevslide' in Options) {
					_SelectSlidePrev = Options.prevslide;
				}
				
				//Selectors - buttons...
				if ('togglegroup' in Options) {
					_SelectToggleGroup = Options.togglegroup;
				}
				if ('btn' in Options) {
					_SelectBtn = Options.btn;
				}
				if ('clickbtn' in Options) {
					_SelectClickBtn = Options.clickbtn;
				}
				if ('togglebtn' in Options) {
					_SelectToggleBtn = Options.togglebtn;
				}
				
				//Selectors - dropdown...
				if ('dropdown' in Options) {
					_SelectDropdown = Options.dropdown;
				}
				if ('dropdownbtn' in Options) {
					_SelectDropdownBtn = Options.dropdownbtn;
				}
				if ('dropdownmenu' in Options) {
					_SelectDropdownMenu = Options.dropdownmenu;
				}
				
				//Handlers...
				if ('showmodal' in Options) {
					_ShowModal = Options.showmodal;
				}
				if ('hidemodal' in Options) {
					_HideModal = Options.hidemodal;
				}
				if ('showcover' in Options) {
					_ShowCover = Options.showcover;
				}
				if ('hidecover' in Options) {
					_HideCover = Options.hidecover;
				}
				
				//Events...
				if ('onmodalcreate' in Options) {
					_OnModalCreate = Options.onmodalcreate;
				}
				if ('onmodalshow' in Options) {
					_OnModalShow = Options.onmodalshow;
				}
				if ('onmodalhide' in Options) {
					_OnModalHide = Options.onmodalhide;
				}
	
				//Special...
				if ('autoload' in Options) {
					_Autoload = Options.autoload;
				}
			}
		} //END _fillVars()
		
		
		/************************************************************
		FUNCTION: onload()
		DESC:			Called on load.
		************************************************************/
		this.onload = function() {
			//Assign events... TODO: Use .live()? TODO: Better var names!
			if (_AutoBtn)				self.assignBtn();
			if (_AutoToggleBtn)	self.assignToggleBtn();
			if (_AutoSlideshow)	self.assignSlideshow();
			if (_AutoDropdown)	self.assignDropdown();
			if (_AutoModal)			self.assignModal();
			
			//Outputting...
			_loadOutputMethods();

			//Assign extra events...
			_assignModalEvents();
			_assignPlaceholderEvents();
			_assignRot13Events();
		} //END onload()
		
		
		
		//************************************************************************[ PRIVATE FUNCTIONS ]
		
		/************************************************************
		FUNCTION: _loadOutputMethods()
		DESC:			Loads output methods.
		************************************************************/
		function _loadOutputMethods() {		
			bm.loadOutputMethod('modal', function(Message, Title, Type) {
				var modal = {};
				
				if (_ModalOutput) {
					modal = _ModalOutput;
					
					//Update our modal...
					modal.content(Message);
					modal.move();
					modal.show();
				} else {
					//Create our modal...
					modal = self.createModal(Message, { Autoshow: true });
					_ModalOutput = modal;
				}
				
				return modal;
			}, _LoadAsDefault);
		} //END _loadOutputMethods()
		                                                                                                                                                                                                                                                                                                                                                                                                 
		
		/************************************************************
		FUNCTION: _getElems()
		DESC:			Gets elements.
		************************************************************/
		function _getElems(Elem, Parent, Type) {	
			var elems = {}
		
			//Just this element...
			if (Elem) {
				elems = $(Elem);
				
			//Children of a parent...
			} else if (Parent) {
				elems = $(Parent).find(Type);
				
			//Entire document...
			} else {
				elems = $(document).find(Type);
			}
			
			return elems;
		} //END _getElems()
	
		
		
		//********************************************************[ EVENTS ]
		
		/************************************************************
		FUNCTION: _assignDropdownEvents()
		DESC:			Assigns dropdown events.
		************************************************************/
		function _assignDropdownEvents() {
			//Assign click-away...
			$(document).click(function(e) {
				//Stop looping...
				e.stopPropagation();
				 
				//Turn off dropdown...
				$('[data-ui="dropdown"] ' + _SelectDropdownBtn).removeClass(_ClassOn);
				$('[data-ui="dropdown"] ' + _SelectDropdownMenu).removeClass(_ClassVisible);
			});
		} //END _assignDropdownEvents()
		
		
		/************************************************************
		FUNCTION: _assignModalEvents()
		DESC:			Assigns modal events.
		************************************************************/
		function _assignModalEvents() {
			//Cover...
			$(_SelectCover).on('click', function() {
				//Cover...
				var cover = $(this);
				
				var coverhide = function(e) {
					//Unbind this event to prevent spam...
					cover.unbind('click', coverhide);
				}
			
				//Make cover remove this particular modal...
				cover.bind('click', coverhide);
			});
		} //END _assignModalEvents()
		
		
		/************************************************************
		FUNCTION: _assignPlaceholderEvents()
		DESC:			Assigns placeholder events.
		************************************************************/
		function _assignPlaceholderEvents() {
			if (_SelectPlaceholder) {
				//Test if browser supports placeholders...
				var test = document.createElement('input'), supported = ('placeholder' in test), inputs;
				
				//If not...
				if (!supported) {
					//TODO: Log this? Do it here?
					bm.log('Native input placeholder support not found, adding...', LOG_NOTICE, self);
				
					//Start our magic...
					inputs = $(_SelectPlaceholder);
				
					//TODO: Use .on()?
					inputs.val(inputs.attr('placeholder'));
					
					$(document).on('focus', _SelectPlaceholder, function() {
						var input = $(this);
						
						if (input.val() == input.attr('placeholder')) {
							input.val('').removeClass('placeholder');
						}
					});
					
					$(document).on('blur', _SelectPlaceholder, function() {
						var input = $(this);
						
						if (input.val() == '' || input.val() == input.attr('placeholder')) {
							input.val(input.attr('placeholder')).addClass('placeholder');
						}
					});
				}
			}
		} //END _assignPlaceholderEvents()
		
		
		/************************************************************
		FUNCTION: _assignRot13Events()
		DESC:			Assigns rot 13 decoding events.
		************************************************************/
		function _assignRot13Events() {
			if (_SelectRot13) {
				$(document).on('click', _SelectRot13, function() {
					var elem = $(this), rot13 = elem.attr('data-email'), email, decoded = elem.attr('data-decoded');
					
					//If not already decoded...
					if (!decoded) {
						//Decode...
						email = bm.decodeRot13(rot13);
						
						//Update label...
						elem.text(email);
						
						//If an anchor, set the href...
						if ('href' in elem[0]) {
							elem.attr('href', 'mailto:' + email);
						}
						
						//Mark as decoded...
						elem.attr('data-decoded', 'true');
						
						//TODO: Log?
						bm.log('Rot13 decoded "' + rot13 + '" to "' + email + '"', LOG_NOTICE, self);
					}
				});
			}
		} //END _assignRot13Events()
		
		
		//*********************************************************[ BINDS ]
		
		/************************************************************
		FUNCTION: _showDropdown()
		DESC:			Shows the dropdown menu.
		************************************************************/
		function _showDropdown(Event) {
			var
				button 	= Event.data.button,
				menu		= Event.data.menu;
		
			if ($(this).is(button)) {
				//Stop looping...
				Event.stopPropagation();
			}
			
			if (!button.hasClass(_ClassDisabled)) { 
				//Turn off all dropdowns...
				$('[data-ui="dropdown"] ' + _SelectDropdownBtn).not(button).removeClass(_ClassOn);
				$('[data-ui="dropdown"] ' + _SelectDropdownMenu).not(menu).removeClass(_ClassVisible);
				 
				//Toggle...
				button.toggleClass(_ClassOn);
				menu.toggleClass(_ClassVisible);
			}
		} //END _showDropdown()
		
		
		/************************************************************
		FUNCTION: _toggleBtn()
		DESC:			Toggles the button.
		************************************************************/
		function _toggleBtn(Event) {
			var button = Event.data.button;
			
			if (!button.hasClass(_ClassDisabled)) {
				//If part of a toggle group, disable the others...
				if (button.parent().is(_SelectToggleGroup)) {
					var allbuttons = button.parent().find(_SelectBtn);
					
					//Disable them...
					allbuttons.removeClass(_ClassOn);
					allbuttons.attr('data-state', '');
					
					//Enable this button...
					button.attr('data-state', 'on');
				} else {
					if (button.attr('data-state') == 'on') {
						button.attr('data-state', '');
					} else {
						button.attr('data-state', 'on');
					}
				}
					
				//Toggle state...
				if (button.hasClass(_ClassOn)) {
					button.removeClass(_ClassOn);
				} else {
					button.addClass(_ClassOn);
				}
			}
		} //END _toggleBtn()
		
		
		/************************************************************
		FUNCTION: _onSlideBtnClick()
		DESC:			Called on slide btn click.
		************************************************************/
		function _onSlideBtnClick(Event) {		
			var 
				idx						= Event.data.idx,
				button 				= Event.data.button, 
				slides 				= Event.data.slides, 
				slidebuttons 	= Event.data.slidebuttons,
				prevbutton		= Event.data.prevbutton,
				nextbutton		= Event.data.nextbutton,
				slideshowid		= Event.data.slideshowid;
		
			if (!button.hasClass(_ClassDisabled)) {
				//Perform switch...
				self.switchToSlide(idx, slideshowid);
				
				//Update buttons...
				slidebuttons.removeClass(_ClassOn);
				button.addClass(_ClassOn);
				
				//Start off active...
				prevbutton.removeClass(_ClassDisabled);
				nextbutton.removeClass(_ClassDisabled);
				
				//Disable the prev/next...
				if (!idx || idx == 0) {
					prevbutton.addClass(_ClassDisabled);							
				} else if (idx == slidebuttons.length-1) {
					nextbutton.addClass(_ClassDisabled);
				}
			}
		} //END _onSlideBtnClick()
		
		
		/************************************************************
		FUNCTION: _nextprevSlide()
		DESC:			Changes the slideshow active slide to the previous or next slide.
		************************************************************/
		function _nextprevSlide(Event) {
			var 
				idx						= Event.data.idx,
				button 				= Event.data.button,
				group 				= Event.data.group, 
				slidebuttons 	= Event.data.slidebuttons, 
				slidecount 		= Event.data.slidecount;
			
			var 
				currentid = parseInt(group.find(_SelectSlide + '[data-state="on"]').attr('data-slideid')),
				newid			= currentid;
			
			if (!button.hasClass(_ClassDisabled)) {
				//If previous...
				if (idx === 0 && currentid !== 0) {
					newid = currentid - 1;
				}
				
				//If next...
				if (idx > 0 && currentid < slidecount) {
					newid = currentid + 1;
				}
				
				//Switch to it...
				if (newid != currentid) {
					//Click the slide button...
					slidebuttons.eq(newid).trigger('click');
				}
			}
		} //END _nextprevSlide()
		

		
		//*************************************************************************[ PUBLIC FUNCTIONS ]
		
		/************************************************************
		FUNCTION: switchToSlide()
		PARAMS:		int - slide ID
							int - slideshow ID
		DESC:			Switches the slideshow to a specific slide.
		************************************************************/
		this.switchToSlide = function(SlideID, SlideshowID) {
			SlideID 		= bm.isPassed(SlideID, false);
			SlideshowID = bm.isPassed(SlideshowID, false);
			
			var container, slide, slides;
			
			if (SlideshowID) {
				container = $(_SelectSlideshow).filter('[data-slideshowid="' + SlideshowID + '"]');
			} else {
				container = $(_SelectSlideshow).first();
			}
			
			slides = container.find(_SelectSlide);
			
			if (SlideID !== false) {
				slide = slides.filter('[data-slideid="' + SlideID + '"]');
			
				//Disable all slides...
				slides.removeClass(_ClassVisible);
				slides.attr('data-state', '');
		
				//Show the selected slide...
				slide.addClass(_ClassVisible);
				slide.attr('data-state', 'on');
				
				bm.log('Switched to slide ' + SlideID + '', LOG_NOTICE, self);
				
				return true;
			}
			
			return false;
		} //END switchToSlide()
		
		
		/************************************************************
		FUNCTION: getModalCenteredPos()
		PARAMS:		obj  - modal
							bool - is position fixed
		DESC:			Gets the calculated position of a modal when centered in the viewport.
		************************************************************/
		this.getModalCenteredPos = function(Modal, IsFixed) {
			IsFixed = bm.isPassed(IsFixed, true);

			//Setup...
			var scrolltop = $(window).scrollTop(), scrollleft = $(window).scrollLeft();

			//If modal is fixed to the viewport...
			if (IsFixed) {
				scrolltop = 0;
				scrollleft = 0;
			}

			//Return the computed position...
			return {
				top: 	Math.max(0, (($(window).height() - Modal.outerHeight()) / 2) + scrolltop) + "px",
				left:	Math.max(0, (($(window).width() - Modal.outerWidth()) / 2) + scrollleft) + "px"
			}
		} //END getModalCenteredPos()
		
		
		//****************************************************[ ASSIGNMENT ]
		
		/************************************************************
		FUNCTION: assignBtn()
		DESC:			Assigns basic events to buttons.
		************************************************************/
		this.assignBtn = function(Elem, Parent) {	
			var elems = _getElems(Elem, Parent, _SelectClickBtn);
					
			//If we actually have elements...
			if (elems) {
				//Loop through each one...
				elems.each(function() {
					//Get the button within the group...
					var button 	= $(this);
					
					//Assign events...
					button.mousedown(function() {
						button.attr('data-state','on');
						button.addClass(_ClassOn);
					});
					button.bind('mouseup mouseleave',function() {
						button.attr('data-state','');
						button.removeClass(_ClassOn);
					});
				});
				
				return true;
			}
		} //END assignBtn()
		
		
		/************************************************************
		FUNCTION: btn()
		DESC:			ALIAS for assignBtn()
		************************************************************/
		this.btn = function(Elem, Parent) {
			self.assignBtn(Elem, Parent);
		} //END btn()
		
		
		/************************************************************
		FUNCTION: assignToggleBtn()
		DESC:			Assigns toggle events to buttons.
		************************************************************/
		this.assignToggleBtn = function(Elem, Parent) {
			var elems = _getElems(Elem, Parent, _SelectToggleBtn), input = null;
					
			//If we actually have elements...
			if (elems) {
				//Loop through each one...
				elems.each(function() {
					//Get the button within the group...
					var button 	= $(this);
					
					//(Re-)Assign an event to it...
					button.unbind('mousedown', _toggleBtn);
					button.bind('mousedown', { button: button, input: input }, _toggleBtn);
				});
				
				bm.log('Toggle button events assigned', LOG_NOTICE, self);
				return true;
			}
		} //END assignToggleBtn()
		
		
		/************************************************************
		FUNCTION: toggleBtn()
		DESC:			ALIAS for assignToggleBtn()
		************************************************************/
		this.toggleBtn = function(Elem, Parent) {
			self.assignToggleBtn(Elem, Parent);
		} //END toggleBtn()
		
		
		/************************************************************
		FUNCTION: assignSlideshow()
		DESC:			Assigns events for slideshows.
		************************************************************/
		this.assignSlideshow = function(Elem, Parent) {
			var elems = _getElems(Elem, Parent, _SelectSlideshow), count = 0;
			
			//If we actually have elements... TODO: Assign elements to seemingly random elems that have data attrs!
			if (elems) {
				//Loop through each one...
				elems.each(function(idx) {
					//TODO: Rename "group"!
					//TODO: Don't find first level children - search deeper!
					//TODO: Save internally?
					var 
						group 					= $(this),
						slidegroup			= group.find(_SelectSlideGroup + ':first'),
						slides 					= slidegroup.children(_SelectSlide), 
						slidecount			= slides.length,
						slidenav				= group.find(_SelectSlideNav + ':first').add(slidegroup.children(_SelectSlideNav + ':first')),
						prevbutton 			= slidenav.find(_SelectSlidePrev),
						nextbutton 			= slidenav.find(_SelectSlideNext),
						prevnextbuttons = prevbutton.add(nextbutton),
						slidebuttons 		= slidenav.find(_SelectSlideBtn).not(prevnextbuttons),
						slideshowid			= 0;
					
					//Generate new slideshow ID...
					_LastSlideshowID = _LastSlideshowID + 1;
					slideshowid = _LastSlideshowID;
						
					//Give ID...
					group.attr('data-slideshowid', slideshowid);

					//Pages...
					slides.each(function(idx) {
						//Get the slide...
						var slide 	= $(this);
						
						//Assign it an ID...
						slide.attr('data-slideid', idx);
					});
					
					//Prev/next... TODO: Different event? mousedown?
					prevnextbuttons.each(function(idx) {
						var button = $(this);
						
						button.unbind('click', _nextprevSlide);
						button.bind('click', { 
							idx: 					idx, 
							button: 			button, 
							group: 				group, 
							slidegroup:		slidegroup,
							slidebuttons: slidebuttons, 
							slidecount: 	slidecount 
						}, _nextprevSlide);
					});
					
					//Page buttons...
					slidebuttons.each(function(idx) {
						var button = $(this);
						
						//Assign it a slide to link to...
						button.attr('data-slidefor', idx);
					
						//Assign an event to it...
						button.unbind('click', _onSlideBtnClick);
						button.bind('click', {
							idx:					idx, 
							button:				button, 
							group: 				group, 
							slides:				slides,
							slidebuttons: slidebuttons, 
							prevbutton:		prevbutton,
							nextbutton:		nextbutton,
							slideshowid:	slideshowid
						}, _onSlideBtnClick);	
						
						//Start off clicked...
						if (!idx || idx == 0) {
							//TODO: Trigger one not both!
							button.trigger('click').trigger('mousedown');
						}
					});
					
					count++;
				});
				
				bm.log(count + ' slideshow events assigned', LOG_NOTICE, self);
				return true;
			}
		} //END assignSlideshow()
		
		
		/************************************************************
		FUNCTION: slideshow()
		DESC:			ALIAS for assignSlideshow()
		************************************************************/
		this.slideshow = function(Elem, Parent) {
			self.assignSlideshow(Elem, Parent);
		} //END slideshow()
		
		
		/************************************************************
		FUNCTION: assignDropdown()
		PARAMS:		obj - apply dropdown to this element
							obj - apply dropdown to children of this parent
		DESC:			Assigns dropdown events for an element.
		************************************************************/
		this.assignDropdown = function(Elem, Parent) {
			var elems = _getElems(Elem, Parent, _SelectDropdown), count = 0;
			
			//If we actually have elements...
			if (elems) {
				//Loop through each one...
				elems.each(function() {
					//Get the button within the group...
					var 
						$this		= $(this),
						button 	= $this.find(_SelectDropdownBtn),
						menu		= $this.find(_SelectDropdownMenu),
						trigger	= $this.find(_SelectDropdownBtn + ', ' + _SelectDropdownMenu);
				
					//Add attributes...
					$this.attr('data-ui', 'dropdown');
					
					//(Re-)Assign an event to it...
					trigger.unbind('click', _showDropdown);
					trigger.bind('click', { button: button, menu: menu }, _showDropdown);
					
					count++;
				});
				
				//Assign dropdown related events...
				_assignDropdownEvents();
				
				bm.log(count + ' dropdown events assigned', LOG_NOTICE, self);
				return true;
			}
		} //END assignDropdown()
		
		
		/************************************************************
		FUNCTION: dropdown()
		DESC:			ALIAS for assignDropdown()
		************************************************************/
		this.dropdown = function(Elem, Parent) {
			self.assignDropdown(Elem, Parent);
		} //END dropdown()
		

		
		//********************************************************[ MODALS ]

		/************************************************************
		FUNCTION: getModal()
		PARAMS:		str|obj - modal identifier
		DESC:			Gets a modal.
		************************************************************/
		this.getModal = function(Var) {
			if (typeof Var == 'string') {
				var modals = self.getModals(), modal;
				
				for (var i=0, len=modals.length; i<len; i++) {
					modal = modals[i];
					
					if (modal.Name == Var) {
						return modal;
					}
				}
				
			} else if (typeof Var == 'object') {
				//TODO: Verify if modal!
				return Var;
			}
			
			return false;
		} //END getModal()
		
		
		/************************************************************
		FUNCTION: getModals()
		DESC:			Gets all modals.
		************************************************************/
		this.getModals = function() {
			return _Modals;
		} //END getModals()
		
		
		/************************************************************
		FUNCTION: areModalsVisible()
		DESC:		 	Checks if any modals are visible.
		************************************************************/
		this.areModalsVisible = function() {
			var modals = self.getModals(), visible = false;
			
			for (var i=0; i<modals.length; i++) {
				var modal = modals[i];
				
				if (modal.visible()) {
					visible = true;
				}
			}
			
			return visible;
		} //END areModalsVisible()
		
		
		/************************************************************
		FUNCTION: createModal()
		PARAMS:		var - HTML string or jQuery object
							obj - settings
							obj - flags
							obj - control flags
		DESC:			Creates a modal box.
		************************************************************/
		this.createModal = function(Content, Settings, Controls) {
			Content = bm.isPassed(Content, '');
			
			Settings = bm.isPassed(Settings, { //TODO (1.4): Function to set these settings? Flag to set as default?
				Animation:		_DefModalAnim,
				Speed:				_DefModalSpeed,
				Name:					'',
				Closable:			true,
				Autoshow:			false,
				Resize:				_AutoResizeModals,
				Navigatable:	false,
				OnNavigate:		function() {},
/*
				Next:					function() {},
				Prev:					function() {},
*/
				OnShow:				function() {},
				OnHide:				function() {}
			});
			
			Controls = bm.isPassed(Controls, {
				Close:				true
			});
			
			//Cover...
			if (!_Cover) {
				self.createCover();
			}
			
			//Setup...
			var cover = _Cover, name = '', images = [];
			
			//If a DOM object... TODO (1.4): Support .modal-template by default
			if (typeof Content == 'object' && Content.length > 0) {
				var obj = Content;
				Content = obj.html();
				obj.remove(); //TODO: Flag to do this?
			}
			
			//Create our modal... NOTE: Must be before controls! TODO: Use handler to override?
			var modal = $(
			'<div class="' + _ClassModal + ' ' + _ClassModalInvis + '" data-ui="modal" data-name="' + Settings.Name + '">' + 
			'	<div class="' + _ClassModalContent + '"></div>' + 
			'</div>');
			
			//Insert...
			$('body').append(modal);
			
			
			//Logging...
			if (Settings.Name) {
				name = ' "' + Settings.Name + '"'; //TODO: Dump all settings in array for logging at end
				modal.Name = Settings.Name;
			} else {
				name = ' (unnamed)';
			}
			

			//Add a close button...
			if (Settings.Closable) {
				var closer = $('<div class="' + _ClassModalClose + '"></div>');
				modal.append(closer);
				closer.click(function() {
					//Trigger close...
					modal.close();
				});
			}
			
			
			//Add a next/prev button...
			if (Settings.Navigatable) {
				var nav = $('<div class="' + _ClassModalNav + ' ' + _ClassModalPrev + '"></div>');
				modal.append(nav);
				nav.click(function() {
					//Trigger close...
					modal.prev();
				});
				
				var nav = $('<div class="' + _ClassModalNav + ' ' + _ClassModalNext + '"></div>');
				modal.append(nav);
				nav.click(function() {
					//Trigger close...
					modal.next();
				});
				
				//The current "source" button...
				var current;
			
			
				//Generate content...
				if (Settings.Source) {
					var src = Settings.Source;
					
					if (typeof Settings.Source == 'string') {
						src = $(Settings.Source);
					}
					
					src.click(function() {
						var btn = $(this), content, old = current;
						
						current = btn;
						
						content = current.html();
						
						//If we need to disable the nav button...
						if (current.prev().length) {
							modal.find('.' + _ClassModalNav + '.' + _ClassModalPrev + '').removeClass(_ClassDisabled);
						} else {
							modal.find('.' + _ClassModalNav + '.' + _ClassModalPrev + '').addClass(_ClassDisabled);
						}
						
						//If we need to disable the nav button...
						if (current.next().length) {
							modal.find('.' + _ClassModalNav + '.' + _ClassModalNext + '').removeClass(_ClassDisabled);
						} else {
							modal.find('.' + _ClassModalNav + '.' + _ClassModalNext + '').addClass(_ClassDisabled);
						}
					
						modal.content(content);
						modal.show();
						
						Settings.OnNavigate(modal, current, old, content);
					});
				}
				
				
				//Method: Prev modal
				modal.prev = function() {
					var content, old = current, prev = current.prev();
					
					if (content = prev.html()) {
						current = prev;
						
						//If we need to disable the nav button...
						if (current.prev().length) {
							modal.find('.' + _ClassModalNav + '.' + _ClassModalPrev + '').removeClass(_ClassDisabled);
						} else {
							modal.find('.' + _ClassModalNav + '.' + _ClassModalPrev + '').addClass(_ClassDisabled);
						}
						
						modal.find('.' + _ClassModalNav + '.' + _ClassModalNext + '').removeClass(_ClassDisabled);
						
						modal.content(content);
						
						Settings.OnNavigate(modal, current, old, content);
					}
				}
				
				
				//Method: Next modal
				modal.next = function() {
					var content, old = current, next = current.next();
					
					if (content = next.html()) {
						current = next;
						
						//If we need to disable the nav button...
						if (current.next().length) {
							modal.find('.' + _ClassModalNav + '.' + _ClassModalNext + '').removeClass(_ClassDisabled);
						} else {
							modal.find('.' + _ClassModalNav + '.' + _ClassModalNext + '').addClass(_ClassDisabled);
						}
						
						modal.find('.' + _ClassModalNav + '.' + _ClassModalPrev + '').removeClass(_ClassDisabled);
						
						modal.content(content);
						
						Settings.OnNavigate(modal, current, old, content);
					}
				}
			}
			
			
			//Method: Set/get content...
			modal.content = function(HTML) {
				HTML 	= bm.isPassed(HTML, '');
				//Repos = bm.isPassed(Repos, false); //TODO: Automatically reposition modal on content change
				
				var images, image;
			
				//NOTE: Must call html() twice otherwise returns object!
				modal.find('.' + _ClassModalContent).html(HTML).html();
				
				//Find images...
				images = modal.find('IMG');
				
				//Adjust modal
				images.each(function() {
					var image = $(this);
					
					//If browser supports calling an event on load... TODO: Callback so we can show modal after load, show a loading indicator, etc.
					if ('load' in image) {
						image.load(function() {
							//Re-position...
							modal.move();
						});
					} else {
						//Re-position after a delay...
						setTimeout(function() {
							modal.move();
						}, 10);
					}
				});
				
				return modal;
			}
			modal.contents = modal.content;
			
			
			//Method: Append content...
			modal.append = function(HTML) {
				HTML = bm.isPassed(HTML, '');
				
				modal.find('.' + _ClassModalContent).append(HTML).html();
				
				return modal;
			}

			
			//Method: Move...
			modal.move = function(LocA, LocB, Speed, Callback, Log) {				
				LocA 			= bm.isPassed(LocA, 		'center');
				LocB 			= bm.isPassed(LocB, 		0);
				Callback 	= bm.isPassed(Callback, function() {});
				Log 			= bm.isPassed(Log, 			false);
				
				var to = '', x = 0, y = 0;

				switch (LocA) {
					case 'center':
					case 'middle':
						//Get values...
						var pos = self.getModalCenteredPos(modal);
						x = pos.left;
						y = pos.top;
						to = 'center at ';
						break;
						
					default:
						//Assume they are co-ordinates...
						x = LocA;
						y = LocB;
				}
				
				if (Log) {
					//TODO: Log by default?
					bm.log('Moving modal to ' + to + parseInt(x) + ',' + parseInt(y), LOG_NOTICE, self);
				}
				
				//Move to new position... TODO: Support moving on one axis only? TODO: Custom mover!
				if (Speed) {
					//Slide...
					modal.stop().animate({ top: y, left: x }, Speed, Callback);
				} else {
					//Snap...
					modal.css({ top: y, left: x });
				}
				
				return modal;
			}
			
			
			//Method: Show...
			modal.show = function(Method, Speed, Callback) {
				Method 			= bm.isPassed(Method, 		Settings.Animation);
				Speed 			= bm.isPassed(Speed, 			Settings.Speed);
				Callback 		= bm.isPassed(Callback, 	Settings.OnShow);
			
				if (modal.attr('data-visible') != 'true') {
					bm.log('Showing modal' + name, LOG_NOTICE, self);
							
					//Stop animating...
					modal.stop();
					
					//Our modal is officially visible...
					modal.attr('data-visible', 'true');
					
					//Get our modal's resting position...
					var resting = self.getModalCenteredPos(modal);
										
					//Give it base styling...
					if (Method != 'custom') {
						modal.css({
							top:			resting.top,
							left:			resting.left,
							right:		'',
							bottom:		''
						});
						
						modal.removeClass(_ClassModalInvis);
					}
					
					//Show modal...
					switch (Method) {
						case 'drop':
						case 'slide':
							var startat = '-' + modal.outerHeight() + 'px';
							
							//Apply new style...
							modal.css({
								top:			startat,
								opacity:	0,
							});
							
							//Apply new style...
							modal.animate({
								top:			resting.top,
								opacity:	1
							}, Speed, function() {
								Callback();
							});
							
							break;
							
						case 'snap':
						case 'cut':
						case 'none':
							modal.css({
								opacity:	1,
							});
							
							Callback();
							break;
							
						case 'custom':
							_ShowModal(modal, Speed, Callback);
							break;
							
						case 'fade':
						default:
							if (typeof Method == 'function') {
								Method(modal);
							} else {	
								//Apply new style...
								modal.css({
									opacity:	0,
								});
								
								//Fade in the box...
								modal.animate({
									opacity: 1
								}, Speed, function() {
									Callback();
								});
							}
					}
				}

				//Show cover...
				if (!cover.visible()) {
					cover.show();
					
					if (Settings.Closable) {
						cover.click(function() {
							modal.close();
						});
					}
				}
				
				_OnModalShow(modal, Method, Speed);
				
				return modal;
			}
			
			
			//Method: Open...
			modal.open = modal.show;
			
			
			//Method: Hide...
			modal.hide = function(Method, Speed, Callback) {
				Method 			= bm.isPassed(Method, 		Settings.Animation);
				Speed 			= bm.isPassed(Speed, 			Settings.Speed);
				Callback 		= bm.isPassed(Callback, 	Settings.OnHide);
			
				if (modal.attr('data-visible') != 'false') {		
					bm.log('Hiding modal' + name, LOG_NOTICE, self);
					
					modal.stop();
					modal.attr('data-visible', 'false');
						
					switch (Method) {
						case 'drop':
						case 'slide':
							var outside = '-' + modal.outerHeight() + 'px';

							//Apply new style...
							modal.animate({
								top:			outside,
								opacity:	0
							}, Speed, function() {
								modal.addClass(_ClassModalInvis);
								
								Callback();
							});

							break;
							
						case 'snap':
						case 'cut':
						case 'none':
							modal.css({
								opacity:	0,
							});
							
							modal.addClass(_ClassModalInvis);
							
							Callback();
							break;
							
						case 'custom':
							_HideModal(modal, Speed, Callback);
							break;
							
						case 'fade':
						default:
							if (typeof Method == 'function') {
								Method(modal);
							} else {	
								//Fade in the box...
								modal.animate({
									opacity: 0
								}, Speed, function() {
									modal.addClass(_ClassModalInvis);
									
									Callback();
								});
							}
					}
				}
				
				//Hide cover...
				if (cover.visible() && !self.areModalsVisible()) {
					cover.hide();
				}
				
				_OnModalHide(modal, Method, Speed);
				
				return modal;
			}
			
			
			//Method: Close...
			modal.close = modal.hide;
			
			
			//Method: Visible
			modal.visible = function() {
				if (modal.attr('data-visible') == 'true') {
					return true;
				} else {
					return false;
				}
			}
			
			
			//Method: Bind
			modal.bind = function(Action, Call) {
				modal.find('[data-action="' + Action + '"]').click(Call);
				
				return modal;
			}
			
			
			//Method: Control
			modal.control = function(Label, Action, Call, Keys, Classes) {
				Keys		= bm.isPassed(Keys, 		[]);
				Classes	= bm.isPassed(Classes, 	[]);
				
				//Try and find container...
				var container = modal.find('.' + _ClassModalCtrl);
				
				//If no controls container found, add...
				if (!container.length) {
					container = $('<div class="' + _ClassModalCtrl + '"></div>');
					container.appendTo(modal);
				}

				//Build...
				var button = $('<button type="button">' + Label + '</button>');
				
				//Keys...
				for (var i=0, key, len = Keys.length; i<len; i++) {
					key = Keys[i];
				
					//Assign...
					$(document).keydown(function(e) {
						//TODO: If modal is visible!
						if (bm.getKeyName(e) == key) {
							button.trigger('click');
						}
					});
				}
				
				//Classes...
				for (var i=0, len = Classes.length; i<len; i++) {
					button.addClass(Classes[i]);
				}
				
				if (Action) {
					//Action...
					button.attr('data-action', Action);
				}
				
				//Add...
				button.appendTo(container);
				
				//If a special button...
				if (!Call && Action) {
					switch (Action.toString().toLowerCase()) {
						case 'close':
						case 'hide':
							Call = function() {
								modal.close();
							}
							break;
					}
				} else if (Call && Action) {

					//Do nothing, no binding will occur...
				}
				
				//Function to call... NOTE: Must be AFTER appending!
				if (Call) {
					//Bind...
					button.bind('click', Call);
				}
				
				return modal;
			}
			
			
			//Method: Button
			modal.button = modal.control;
			
			
			//Add content...
			modal.content(Content);
			
			
			//Controls...
			if (Controls instanceof Array) {
				for (var control, i=0, len = Controls.length; i<len; i++) {
					control = Controls[i];
					
					modal.control(control.label, control.action, control.call, control.keys, control.classes);
				}
			} else if (typeof Controls == 'object') {
				for (var key in Controls) {
					control = Controls[key];
					
					if (typeof control == 'function') {
						var call = control;
						
						control = {
							label: 		key,
							action: 	null,
							call:			call,
							keys:			null,
							classes: 	null
						};
					} else if (key == 'Close') {
						modal.bind('close', function() {
							modal.close();
						});
						
						continue;
					}
					
					modal.control(control.label, control.action, control.call, control.keys, control.classes);
				}
			}
			
			
			//If to reposition with window...
			if (Settings.Resize) {
				$(window).resize(function() {
					if (modal.visible()) {
						modal.move();
					}
				});
			}
			
			
			//If to automatically show...
			if (Settings.Autoshow) {
				modal.show();
			}
			
			
			//Store...
			_Modals[_Modals.length] = modal;
			
			
			_OnModalCreate(modal, Content, Settings, Controls);


			return modal;
		} //END createModal()
		
		
		/************************************************************
		FUNCTION: modal()
		ALIAS:		createModal()
		************************************************************/
		this.modal = function(Content, Settings, Controls) {
			return self.createModal(Content, Settings, Controls);
		} //END modal()
		
		
		/************************************************************
		FUNCTION: assignModal()
		PARAMS:		obj - apply modal to this element
							obj - apply modal to children of this parent
		DESC:			Assigns modal trigger events for an element.
		************************************************************/
		this.assignModal = function(Elem, Parent) {
			var elems = _getElems(Elem, Parent, _SelectModalTrigger);
			
			//If we actually have elements...
			if (elems) {
				elems.click(function() {
					var btn = $(this), content = btn.attr('data-modal-content'), name = btn.attr('data-modal-name');
					
					bm.ui.modal(content, {
						Name: 			name,
						Autoshow: 	true
					});
				});
				
				bm.log('Modal events assigned', LOG_NOTICE, self);
				return true;
			} else {
				return false;
			}
		} //END assignModal()
		
		
		/************************************************************
		FUNCTION: createCover()
		PARAMS:		bool - if to store internally
		DESC:			Creates a modal cover.
		************************************************************/
		this.createCover = function(Store) {
			Store = bm.isPassed(Store, true);
			
			//Build... TODO: Apply these styles? Use classes!
			var cover = $('<div class="' + _ClassModalCover + ' ' + _ClassModalCoverInvis + '"></div>');
			
			//Insert...
			cover.appendTo('BODY');
			
			//Method: Show
			cover.show = function(Method, Speed) {
				Method = bm.isPassed(Speed, _DefModalCoverAnim);
				Speed = bm.isPassed(Speed, _DefModalCoverSpeed);
			
				cover.attr('data-visible', 'true');

				$('BODY').addClass(_ClassClip);
				
				switch (Method) {
					case 'custom':
						_ShowCover(cover, Speed);
						break;
				
					case 'fade':
					default:
						//Fade in the box...
						cover.stop().css({
							display: 'block'
						}).animate({
							opacity: 1
						}, Speed);
				}
			}
			
			//Method: Hide
			cover.hide = function(Method, Speed) {
				Method = bm.isPassed(Speed, _DefModalCoverAnim);
				Speed = bm.isPassed(Speed, _DefModalCoverSpeed);
				
				cover.attr('data-visible', 'false');

				$('BODY').removeClass(_ClassClip);
				
				switch (Method) {
					case 'custom':
						_HideCover(cover, Speed);
						break;
				
					case 'fade':
					default:
						cover.stop().animate({
							opacity: 0
						}, Speed, function() {
							//Apply new style...
							cover.css({
								display: 'none'
							});
						});
				}
			}
			
			//Method: Visible
			cover.visible = function() {
				if (cover.attr('data-visible') == 'true') {
					return true;
				} else {
					return false;
				}
			}
			
			//If to store internally...
			if (Store) {
				_Cover = cover;
			}
		
			return cover;
		} //END createCover()

} //END ui()


/**************************************************************************[ REVISION HISTORY ]
2012 Dec 21 (Jared Williams) - ui() 0.9
	- Started revision history
	- Updated to library 2.1
	- Added ability to UI classes
	
2013 Mar 20 (Jared Williams) - ui() 1.0
	- Out of beta
	
2013 Apr 05 (Jared Williams) - ui() 1.0
	- Converted private event funcs to public
	- Added _getModalPos()
	
2013 Apr 10 (Jared Williams) - ui() 1.0
	- Added formatModal()
	
2013 May 07 (Jared Williams) - ui() 1.0
	- Cleaned up all functions
	- Added on window resize re-position modal
	
2013 May 21 (Jared Williams) - ui() 1.0
	- Added check if Options was passed
	
2013 Jun 20 (Jared Williams) - ui() 1.1
	- Updated to library 2.2
	- Added internal storage of modals
	- Added pre-defined modal types
	- Added modal callbacks
	- Fixed modals appearing off-center
	- Added _stackModals()
	- Added jQuery UI check before making modals draggable
	- Added logging when events assigned
	
2013 Jul 03 (Jared Williams) - ui() 1.1
	- Added $.addClass()
	- Added support for radio input toggle buttons
	
2013 Jul 10 (Jared Williams) - ui() 1.1
	- Modified getModal() to return a passed object
	- Added btn parameter
	- Fixed autoshow flag now working in createModal()
	
2013 Jul 25 (Jared Williams) - ui() 1.1
	- Added loading of modal output method
	
2013 Aug 23 (Jared Williams) - ui() 1.2
	- Removed getModal(), getModals(), hideModal(), hideModals()
	- Added _assignModalEvents()
	
2013 Sep 04 (Jared Williams) - ui() 1.2
	- Removed formatModal(), showModal(), showCover()
	- Added .control, .button methods to modals
	- Added createCover()
	- Added default modal animation settings
	
2013 Sep 12 (Jared Williams) - ui() 1.2
	- Modified how it loads output method
	- Added modal name setting
	- Added content method for modals
	
2013 Sep 26 (Jared Williams) - ui() 1.2
	- Added special modal control action "close"
	- Added callback to modal.move()
	- Modified modal.show() and modal.hide() to accept custom methods
	- Modified modal.move() for animated movement
	- Modified _calcModalPos() to default to fixed modal positions
	
2013 Oct 04 (Jared Williams) - ui() 1.2
	- Modified createModal() to store each modal internally
	- Added getModals()
	- Added modal events
	
2013 Oct 24 (Jared Williams) - ui() 1.2
	- (Re-)Added body clipping for modal covers
	- Added modal-specific classes
	- Added key binding to modal controls
	- Added callbacks to modal.show(), modal.hide()

2013 Oct 31 (Jared Williams) - ui() 1.2
	- Modified modal.show() to not force modal height
	- Modified createModal() to not require modal content to create
	- Fixed modal.content() not setting/getting HTML content of modal
	- Fixed modal.move() not moving to center
	- Removed _stackModals(), _getBodyScroll(), _BodyScroll
	
2014 Feb 26 (Jared Williams) - ui() 1.3
	- Updated to library 2.3
	- Change "on" option to "onmodalshow", "onmodalhide", etc.
	- Modified createCover() to use modal speed as default
	
2014 Mar 11 (Jared Williams) - ui() 1.3
	- Added "custom" modal & cover hide/show methods
	- Modified modal methods to return modal object

2014 Mar 18 (Jared Williams) - ui() 1.3
	- Added _outputMethod()
	- Renamed togglebtn() to toggleBtn()
	- Renamed selector vars from _XSel to _SelectX
	- Removed _TriggerDelay
	
2014 Mar 25 (Jared Williams) - ui() 1.3
	- Updated with 1.21 changes
	- Added assignModal(), getModal()
	- Modified createModal() to store modal passed properties
	
2014 Apr 11 (Jared Williams) - ui() 1.3
	- Added _LoadAsDefault to load as default output method
	- Fixed _outputMethod() not showing modal on modal creation
	
2014 Apr 15 (Jared Williams) - ui() 1.3
	- Added automatic placeholder support for input placeholders
	- Added automatic rot13 email decoding support
	- Added _assignPlaceholderEvents(), _assignRot13Events()
	
2014 Apr 29 (Jared Williams) - ui() 1.3
	- Fixed createModal() not assigning control events correctly
	
2014 Sep 17 (Jared Williams) - ui() 1.3
	- Applied 1.21 & 1.22 changes
	- Added slideshow ID to all slideshows
	- Changed switchToSlide from jQuery plugin to public function
	- Modified assignSlideshow() to search through ALL decendants for slide nav and slides
	- Modified modal() to allow passing object as controls
	- Removed _outputMethod()
	
2014 Oct 13 (Jared Williams) - ui() 1.3
	- Renamed _changeSlide() to _onSlideBtnClick()

2014 Oct 23 (Jared Williams) - ui() 1.3
	- Fixed _loadOutputMethod() not using _LoadAsDefault
	- Modified _assignModalEvents() to use .on() instead of .live() for better jQuery support
	
2014 Nov 03 (Jared Williams) - ui() 1.3
	- Added resizemodals option flag
	- Added modal cover class variable
	- Added modal cover default animation & speed options
	- Fixed modalcover.hide() not using the correct handler
	
2014 Dec 08 (Jared Williams) - ui() 1.31
	- Fixed _showDropdown() not letting .on() events work correctly
	
2014 Dec 16 (Jared Williams) - ui() 1.32
	- Fixed modals not displaying properly with images

2015 Feb 02 (Jared Williams) - ui() 1.33
	- Renamed _calcModalPos() to getModalCenteredPos() and made public
	
2015 Feb 26 (Jared Williams) - ui() 1.33
	- Added count to logging when events are assigned
*/
//------------------------------------END OF JAVASCRIPT CODE---------------------------------->