function Spot(asGlobals) { self = this; this.consts = asGlobals.consts; this.consts.hash_sep = '-'; this.consts.title = 'Spotty'; this.consts.default_page = 'project'; this.consts.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone || this.consts.default_timezone; /* Initialization */ this.init = function() { //Variables & constants from php self.vars('tmp', 'object'); self.vars('page', 'string'); self.updateVars(asGlobals.vars); //page elem self.elem = {}; self.elem.container = $('#container'); self.elem.main = $('#main'); self.resetTmpFunctions(); //On Key down $('html').on('keydown', function(oEvent){self.onKeydown(oEvent);}); //on window resize $(window).on('resize', function(){self.onResize();}); //Setup menu //self.initMenu(); //Hash management $(window) .bind('hashchange', self.onHashChange) .trigger('hashchange'); }; this.updateVars = function(asVars) { $.each(asVars, function(sKey, oValue){self.vars(sKey, oValue)}); }; /* Variable Management */ this.vars = function(oVarName, oValue) { var asVarName = (typeof oVarName == 'object')?oVarName:[oVarName]; //Set, name & type / default value (init) if(typeof oValue !== 'undefined') setElem(self.vars, copyArray(asVarName), oValue); //Get, only name parameter return getElem(self.vars, asVarName); }; this.tmp = function(sVarName, oValue) { var asVarName = (typeof sVarName == 'object')?sVarName:[sVarName]; asVarName.unshift('tmp'); return self.vars(asVarName, oValue); }; /* Interface with server */ this.get = function(sAction, fOnSuccess, oVars, fOnError, fonProgress) { if(!oVars) oVars = {}; fOnError = fOnError || function(sError) {console.log(sError);}; fonProgress = fonProgress || function(sState){}; fonProgress('start'); oVars['a'] = sAction; oVars['t'] = self.consts.timezone; return $.ajax( { url: self.consts.process_page, data: oVars, dataType: 'json' }) .done(function(oData) { fonProgress('done'); if(oData.desc.substr(0, self.consts.lang_prefix.length)==self.consts.lang_prefix) oData.desc = self.lang(oData.desc.substr(5)); if(oData.result==self.consts.error) fOnError(oData.desc); else fOnSuccess(oData.data, oData.desc); }) .fail(function(jqXHR, textStatus, errorThrown) { fonProgress('fail'); fOnError(textStatus+' '+errorThrown); }); }; this.lang = function(sKey, asParams) { var sParamType = $.type(asParams); if(sParamType == 'undefined') asParams = []; else if($.type(asParams) != 'array') asParams = [asParams]; var sLang = ''; if(sKey in self.consts.lang) { sLang = self.consts.lang[sKey]; for(i in asParams) sLang = sLang.replace('$'+i, asParams[i]); } else { console.log('missing translation: '+sKey); sLang = sKey; } return sLang; }; /* Page Switch - Trigger & Event catching */ this.onHashChange = function() { var asHash = self.getHash(); if(asHash.hash !='' && asHash.page != '') self.switchPage(asHash); //page switching else if(self.vars('page')=='') self.setHash(self.consts.default_page); //first page }; this.getHash = function() { var sHash = self.hash(); var asHash = sHash.split(self.consts.hash_sep); var sPage = asHash.shift() || ''; return {hash:sHash, page:sPage, items:asHash}; }; this.setHash = function(sPage, asItems, bReboot) { bReboot = bReboot || false; sPage = sPage || ''; asItems = asItems || []; if(typeof asItems == 'string') asItems = [asItems]; if(sPage != '') { var sItems = (asItems.length > 0)?self.consts.hash_sep+asItems.join(self.consts.hash_sep):''; self.hash(sPage+sItems, bReboot); } }; this.hash = function(hash, bReboot) { bReboot = bReboot || false; if(!hash) return window.location.hash.slice(1); else window.location.hash = '#'+hash; if(bReboot) location.reload(); }; this.updateHash = function(sType, iId) { sType = sType || ''; iId = iId || 0; var asHash = self.getHash(); if(iId) self.setHash(asHash.page, [asHash.items[0], sType, iId]); }; this.flushHash = function(asTypes) { asTypes = asTypes || []; var asHash = self.getHash(); if(asHash.items.length > 1 && (asTypes.length == 0 || asTypes.indexOf(asHash.items[1]) != -1)) self.setHash(asHash.page, [asHash.items[0]]); }; /* Page Switch - DOM Replacement */ this.getActionLink = function(sAction, oVars) { if(!oVars) oVars = {}; sVars = ''; for(i in oVars) { sVars += '&'+i+'='+oVars[i]; } return self.consts.process_page+'?a='+sAction+sVars; }; this.resetTmpFunctions = function() { self.pageInit = function(asHash){console.log('no init for the page: '+asHash.page)}; self.onSamePageMove = function(asHash){return false}; self.onQuitPage = function(){return true}; self.onResize = function(){}; self.onFeedback = function(sType, sMsg){}; self.onKeydown = function(oEvent){}; }; this.switchPage = function(asHash) { var sPageName = asHash.page; var bSamePage = (self.vars('page') == sPageName); var bFirstPage = (self.vars('page') == ''); if(!self.consts.pages[sPageName]) { //Page does not exist if(bFirstPage) self.setHash(self.consts.default_page); else self.setHash(self.vars('page'), self.vars(['hash', 'items'])); } else if(self.onQuitPage(bSamePage) && !bSamePage || self.onSamePageMove(asHash)) { //Delete tmp variables self.vars('tmp', {}); //disable tmp functions self.resetTmpFunctions(); //Officially a new page self.vars('page', sPageName); self.vars('hash', asHash); //Update Page Title this.setPageTitle(sPageName+' '+(asHash.items[0] || '')); //Replacing DOM var $Dom = $(self.consts.pages[sPageName]); if(bFirstPage) { self.splash($Dom, asHash, bFirstPage); //first page } else { self.elem.main.stop().fadeTo('fast', 0, function(){self.splash($Dom, asHash, bFirstPage);}); //Switching page } } else if(bSamePage) self.vars('hash', asHash); }; this.setPageTitle = function(sTitle) { document.title = self.consts.title+' - '+sTitle; }; this.splash = function($Dom, asHash, bFirstPage) { //Switch main content self.elem.main.empty().html($Dom); //Page Bootstrap self.pageInit(asHash, bFirstPage); //Show main var $FadeInElem = bFirstPage?self.elem.container:self.elem.main; $FadeInElem.hide().fadeTo('slow', 1); }; this.getNaturalDuration = function(iHours) { var iTimeMinutes = 0, iTimeHours = 0, iTimeDays = Math.floor(iHours/8); //8 hours a day if(iTimeDays > 1) iTimeDays = Math.round(iTimeDays * 2) / 2; //Round down to the closest half day else { iTimeDays = 0; iTimeHours = Math.floor(iHours); iHours -= iTimeHours; iTimeMinutes = Math.floor(iHours * 4) * 15; //Round down to the closest 15 minutes } return '~ ' +(iTimeDays>0?(iTimeDays+(iTimeDays%2==0?'':'½')+' '+self.lang(iTimeDays>1?'unit_days':'unit_day')):'') //Days +((iTimeHours>0 || iTimeDays==0)?iTimeHours+self.lang('unit_hour'):'') //Hours +((iTimeDays>0 || iTimeMinutes==0)?'':iTimeMinutes) //Minutes }; this.checkClearance = function(sClearance) { return (self.vars(['user', 'clearance']) >= sClearance); } } /* Common Functions */ function copyArray(asArray) { return asArray.slice(0); //trick to copy array } function getElem(aoAnchor, asPath) { return (typeof asPath == 'object' && asPath.length > 1)?getElem(aoAnchor[asPath.shift()], asPath):aoAnchor[(typeof asPath == 'object')?asPath.shift():asPath]; } function setElem(aoAnchor, asPath, oValue) { var asTypes = {boolean:false, string:'', integer:0, int:0, array:[], object:{}}; if(typeof asPath == 'object' && asPath.length > 1) { var nextlevel = asPath.shift(); if(!(nextlevel in aoAnchor)) aoAnchor[nextlevel] = {}; //Creating a new level if(typeof aoAnchor[nextlevel] !== 'object') debug('Error - setElem() : Already existing path at level "'+nextlevel+'". Cancelling setElem() action'); return setElem(aoAnchor[nextlevel], asPath, oValue); } else { var sKey = (typeof asPath == 'object')?asPath.shift():asPath; return aoAnchor[sKey] = (!(sKey in aoAnchor) && (oValue in asTypes))?asTypes[oValue]:oValue; } } $.prototype.addInput = function(sType, sName, sValue, aoEvents) { aoEvents = aoEvents || []; var $Input = $('', {type: sType, name: sName, value: sValue}).data('old_value', sValue); $.each(aoEvents, function(iIndex, aoEvent) { $Input.on(aoEvent.on, aoEvent.callback); }); return $(this).append($Input); }; $.prototype.addButton = function(sIcon, sText, sName, fOnClick, sClass) { sText = sText || ''; sClass = sClass || ''; var $Btn = $('