/*! iris - v0.5.7-SNAPSHOT - 2014-02-17 (http://thegameofcode.github.io/iris) licensed New-BSD */ var iris = {}; // Expose iris to the global object window.iris = iris; (function ($) { // static object to store all app callbacks var _events; function _init() { _events = {}; iris.on("iris-reset", _init); } iris.on = function (p_eventName, f_func) { if ( ! $.isFunction(f_func) ) { throw "invalid function"; } if ( !_events.hasOwnProperty(p_eventName) ) { _events[p_eventName] = []; } var callbacks = _events[p_eventName]; var index = $.inArray(f_func, callbacks); if ( index === -1 ) { callbacks.push(f_func); } }; iris.off = function (p_eventName, f_func){ if ( _events.hasOwnProperty(p_eventName) ){ if (f_func !== undefined) { if ( ! $.isFunction(f_func) ) { throw "invalid function"; } var index = $.inArray(f_func, _events[p_eventName]); if ( index !== -1 ) { _events[p_eventName].splice(index, 1); } } else { delete _events[p_eventName]; } } }; iris.notify = function (p_eventName, p_data){ if ( p_eventName === undefined ) { throw "event name undefined"; } if ( _events[p_eventName] ) { var callbacks = _events[p_eventName]; for ( var i=0; i < callbacks.length; i++ ) { callbacks[i](p_data); } } }; iris.destroyEvents = function (p_eventName, p_callbacks) { // Create an array copy, to prevent concurrent modification of _events[p_eventName] array. // This occur if an event that destroy uis is notified var callbacks = _events[p_eventName].concat([]); for ( var i=0; i < p_callbacks.length; i++ ) { var index = $.inArray(p_callbacks[i], callbacks); if ( index !== -1 ) { callbacks.splice(index, 1); } } _events[p_eventName] = callbacks; }; iris.Event = function () { this.events = {}; // { "event1" : [f1, f2], "event2" : [f3, f4, f5, f6] } }; var eventPrototype = iris.Event.prototype; eventPrototype.on = function (p_eventName, f_func) { if ( ! $.isFunction(f_func) ) { throw "invalid function"; } if ( !this.events.hasOwnProperty(p_eventName) ) { this.events[p_eventName] = []; } var callbacks = this.events[p_eventName]; var index = $.inArray(f_func, callbacks); if ( index === -1 ) { callbacks.push(f_func); iris.on(p_eventName, f_func); } }; eventPrototype.off = function (p_eventName, f_func) { // if f_func is undefined removes all callbacks if ( f_func !== undefined && ! $.isFunction(f_func) ) { throw "invalid function"; } var callbacks = this.events[p_eventName]; if ( callbacks ) { if (f_func !== undefined) { var index = $.inArray(f_func, callbacks); if ( index !== -1 ) { callbacks.splice(index, 1); iris.off(p_eventName, f_func); } } else { for (var i = 0; i < callbacks.length; i++ ) { iris.off(p_eventName, callbacks[i]); } this.events[p_eventName] = []; } } }; eventPrototype.notify = function (p_eventName, p_data){ iris.notify(p_eventName, p_data); }; // // Iris custom events // iris.BEFORE_NAVIGATION = "iris_before_navigation"; iris.AFTER_NAVIGATION = "iris_after_navigation"; iris.RESOURCE_ERROR = "iris_resource_error"; iris.SCREEN_NOT_FOUND = "iris_screen_not_found"; _init(); })(jQuery); (function($) { var _JQ_MIN_VER = 1.5, _appBaseUri, _cache, _cacheVersion, _log, _logEnabled, _isLocalEnv; // // Private // function _init() { if ( typeof jQuery === "undefined" ) { throw "jQuery " + _JQ_MIN_VER + "+ previous load required"; } else if($().jquery < _JQ_MIN_VER) { throw "jQuery " + $().jquery + " currently loaded, jQuery " + _JQ_MIN_VER + "+ required"; } var console = window.console; if ( typeof console !== 'undefined' && typeof console.log === 'object' ) { var bind = Function.prototype.bind; if ( bind ) { // Fix IE 9 Problem with console. // http://stackoverflow.com/questions/5538972/console-log-apply-not-working-in-ie9 _log = bind.call(console.log, console); } else { // Fix IE 8 Problem with console. // http://patik.com/blog/complete-cross-browser-console-log/ _log = function () { Function.prototype.call.call(console.log, console, Array.prototype.slice.call(arguments)); }; } } else if ( console && console.log ) { // Modern browser _log = console.log; } _isLocalEnv = urlContains("localhost", "127.0.0.1"); _logEnabled = _isLocalEnv; _cache = !_isLocalEnv; iris.on("iris-reset", _init); } function urlContains () { for(var i = 0 ; i< arguments.length; i++) { if ( document.location.href.indexOf(arguments[i]) > -1 ) { return true; } } return false; } // // Public // iris.baseUri = function (p_baseUri) { if ( p_baseUri !== undefined ) { _appBaseUri = p_baseUri; } else if ( _appBaseUri === undefined ) { var base = document.getElementsByTagName("base"); base = base.length > 0 ? base[0].attributes.href.value : "/"; _appBaseUri = document.location.protocol + "//" + document.location.host + base; } return _appBaseUri; }; iris.cache = function (p_value) { if(p_value !== undefined) { _cache = p_value; } else { return _cache; } }; iris.cacheVersion = function (p_value) { if(p_value !== undefined) { _cacheVersion = p_value; } else { return _cacheVersion; } }; iris.log = function () { if ( _logEnabled && _log ) { _log.apply(window.console, arguments); } }; iris.enableLog = function () { if ( typeof arguments[0] === "boolean" ) { _logEnabled = arguments[0]; } else if ( arguments.length > 0 ) { _logEnabled = urlContains.apply(this, arguments); } else { return _logEnabled; } }; iris.noCache = function () { if ( arguments.length > 0 ) { _cache = !urlContains.apply(this, arguments); } else { return !_cache; } }; iris.isLocalhost = function () { return _isLocalEnv; }; _init(); })(jQuery); (function($) { var _translations; function _init() { _translations = {}; } // // Private // function _addTranslations(p_locale, p_data) { iris.log("[translations]", p_locale, p_data); if(iris.locale() === undefined) { iris.locale(p_locale); } if(!_translations.hasOwnProperty(p_locale)) { _translations[p_locale] = {}; } $.extend(_translations[p_locale], p_data); } function _loadTranslations(p_locale, p_uri, p_settings) { iris.log("[translations]", p_locale, p_uri); var ajaxSettings = { url: p_uri, dataType: "json", async: false, cache: iris.cache() }; if(iris.cache() && iris.cacheVersion()) { ajaxSettings.data = "_=" + iris.cacheVersion(); } iris.ajax(ajaxSettings) .done(function (p_data) { _addTranslations(p_locale, p_data); iris.log("[translations]", p_data); if(p_settings && p_settings.hasOwnProperty("success")) { p_settings.success(p_locale); } }) .fail(function(p_err) { if(p_settings && p_settings.hasOwnProperty("error")) { p_settings.error(p_locale); } throw "Error " + p_err.status + " loading lang file[" + p_uri + "]"; }); } // // Public // iris.translations = function (p_locale, p_value, p_settings) { if(typeof p_value === "object") { _addTranslations(p_locale, p_value); } else { _loadTranslations(p_locale, p_value, p_settings); } }; iris.translate = function (p_label, p_locale) { var value; var locale = null; if (p_locale !== undefined) { locale = p_locale; } else { locale = iris.locale(); } var logPrefix = "[translate]"; if(_translations.hasOwnProperty(locale)) { value = iris.val(_translations[locale], p_label); if(value === undefined) { iris.log(logPrefix + " label '" + p_label + "' not found in Locale '" + locale + "'", _translations[locale]); } if(typeof value === "object") { iris.log(logPrefix + "label '" + p_label + "' is an object but must be a property in Locale '" + locale + "'", _translations[locale]); } } else { iris.log(logPrefix + " locale '" + locale + "' not loaded", this); } return (value !== undefined) ? value : "??" + p_label + "??"; }; _init(); })(jQuery); (function() { var _locale, _regional; function _init() { _locale = undefined; _regional = {}; iris.on("iris-reset", _init); } iris.locale = function (p_locale, p_regional) { if ( typeof p_regional === "object" ) { if ( !_regional[p_locale] ) { _regional[p_locale] = {}; } $.extend(_regional[p_locale], p_regional); if ( _locale === undefined ) { _locale = p_locale; } } else if ( p_locale !== undefined ) { _locale = p_locale; } else { return _locale; } }; iris.regional = function (p_label) { if(_regional.hasOwnProperty(_locale)) { if(typeof p_label === "undefined") { return _regional[_locale]; } else if(_regional[_locale].hasOwnProperty(p_label)) { return _regional[_locale][p_label]; } else { return undefined; //throw "[regional] setting '" + p_label + "' not found for locale '" + _locale + "'"; } } else { throw "[regional] for locale '" + _locale + "' not found"; } }; _init(); })(); (function($) { // // Private // var browser; function setBrowser (ua) { ua = ua.toLowerCase(); var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) || /(webkit)[ \/]([\w.]+)/.exec( ua ) || /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) || /(msie) ([\w.]+)/.exec( ua ) || ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || []; return { name: match[ 1 ] || "", version: match[ 2 ] || "0" }; } function leadingZero(p_number) { return(p_number < 10) ? "0" + p_number : p_number; } function formatDateChar(p_formatChar, p_date) { var regional = iris.regional(); switch(p_formatChar) { case "y": return String(p_date.getFullYear()).substring(2); case "Y": return p_date.getFullYear(); case "m": var m = p_date.getMonth() + 1; return leadingZero(m); case "n": return p_date.getMonth() + 1; case "M": return regional.monthNames[p_date.getMonth()].substring(0, 3); case "b": return regional.monthNames[p_date.getMonth()].substring(0, 3).toLowerCase(); case "F": return regional.monthNames[p_date.getMonth()]; case "d": var d = p_date.getDate(); return leadingZero(d); case "D": return regional.dayNames[p_date.getDay()].substring(0, 3); case "l": return regional.dayNames[p_date.getDay()]; case "s": var s = p_date.getSeconds(); return leadingZero(s); case "i": var i = p_date.getMinutes(); return leadingZero(i); case "H": var h = p_date.getHours(); return leadingZero(h); case "h": var hour = p_date.getHours(); hour = (hour % 12) === 0 ? 12 : hour % 12; return leadingZero(hour); case "a": return(p_date.getHours() > 12) ? "p.m." : "a.m."; case "A": return(p_date.getHours() > 12) ? "PM" : "AM"; case "U": return Math.floor(p_date.getTime() * 0.001); default: return p_formatChar; } } // // Public // iris.date = function (p_date, p_format) { if ( p_date === null ) { return ""; } if(!p_format) { p_format = iris.regional("dateFormat"); } if(typeof p_date !== "object") { p_date = new Date(p_date); } var dateFormat = ""; for(var f = 0, F = p_format.length; f < F; f++) { dateFormat += formatDateChar(p_format[f], p_date); } return dateFormat; }; iris.ajax = function (p_settings) { return $.ajax(p_settings); }; iris.currency = function (value, config) { var settings = { formatPos: "sn", formatNeg: "(sn)", symbol : "$" }; $.extend(settings, iris.regional("currency"), config); var num = iris.number(value, settings); return num.replace("s", settings.symbol); }; iris.number = function (value, config) { var settings = { formatPos: "n", formatNeg: "- n", decimal: ".", thousand: ",", precision: 2 }; $.extend(settings, iris.regional("number"), config); var val = Number(value); var format = (val >= 0) ? settings.formatPos : settings.formatNeg; var dec = val % 1; var num = String(Math.abs(val - dec)); for(var i = 0; i < Math.floor((num.length - (1 + i)) / 3); i++) { num = num.substring(0, num.length - (4 * i + 3)) + settings.thousand + num.substring(num.length - (4 * i + 3)); } if ( settings.precision > 0 ) { dec = String(Math.abs(dec).toFixed(settings.precision)); dec = dec.substr(2); num = num + settings.decimal + dec; } return format.replace("n", num); }; iris.val = function (p_obj, p_label) { var value; if(p_label.indexOf(".") > -1) { var labels = p_label.split("."); var f, F = labels.length; for(f = 0; f < F; f++) { if(p_obj !== undefined) { p_obj = p_obj[labels[f]]; } else { break; } } value = p_obj; } else { value = p_obj[p_label]; } return value; }; // The jQuery.browser() method has been deprecated since jQuery 1.3 and is removed in 1.9. If needed, it is available as part of the jQuery Migrate plugin // https://github.com/jquery/jquery-migrate/blob/master/src/core.js iris.browser = function () { if ( !browser ) { var matched = setBrowser( navigator.userAgent ); browser = {}; if ( matched.name ) { browser[ matched.name ] = true; browser.version = matched.version; } // Chrome is Webkit, but Webkit is also Safari. if ( browser.chrome ) { browser.webkit = true; } else if ( browser.webkit ) { browser.safari = true; } } return browser; }; iris.inherits = function (subClass, superClass) { var Aux = function() {}; Aux.prototype = superClass.prototype; subClass.prototype = new Aux(); }; })(jQuery); (function($) { /** * Settable class to manage object configurations. */ var Settable = function() { iris.Event.call(this); this.cfg = {}; }; iris.inherits(Settable, iris.Event); var pSettable = Settable.prototype; pSettable.settings = function(settings) { return $.extend(this.cfg, settings); }; pSettable.setting = function(label, value) { if ( value === undefined ) { if ( !this.cfg.hasOwnProperty(label) ) { iris.log("setting '" + label + "' missing", this.cfg, this); } return this.cfg[label]; } else { this.cfg[label] = value; } }; iris.Settable = Settable; })(jQuery); (function($) { var FORMAT_REG_EXP = /(date|currency|number)(?:\(([^\)]+)\))/, PATH_PARAM_REGEX = /:[\d\w_\-\.]+/g, SCREEN_PARAM_REGEX = '[;?&][^=]+=[^;\/&]+', // Validates matrix & url params: ';param=value' & '?param=value' & '¶m=value' HTML_COMMENT_REGEX = //g; var _screen, _includes, _welcomeCreated, _head = document.getElementsByTagName('head')[0], _paths, _loadJsCallback, _dependencyCount, _lastLoadedDependencies, _gotoCancelled, _prevNavigationHash, // The hash for the last navigation (finished) _navMap, _prevNav, _prevNavRaw, _jsUrlScreens, // To prevent multiple screen instances // _screenMetadata["#parent/hash/:id"] = { // hashFragment: "hash/:id", // js:"path.js", // return the js-url associated with hash (#parent/hash/:id) // container: $element, // return the parent container associated with hash (#parent/hash/:id) // parentNavMap: {} // Parent node in the navigation tree // } _screenMetadata, _debugMode ; function _init() { // _screen["#hash"] return the screen instance _screen = {}; _screenMetadata = {}; // _jsUrlScreens["/path/to/file.js"] indicates if a js-URL has been used by some screen _jsUrlScreens = {}; _includes = {}; _welcomeCreated = false; // Navigation _navMap = {}; _prevNav = []; _prevNavRaw = []; _gotoCancelled = false; _prevNavigationHash = ""; // dependencies _dependencyCount = 0; _lastLoadedDependencies = []; _paths = []; // By default debug is disabled _debugMode = false; // If environment is local enable debug if ( iris.isLocalhost() ) { _debug(true); } iris.on("iris-reset", function () { $(window).off("hashchange"); document.location.hash = "#"; _init(); }); } function _welcome(p_jsUrl) { if ( _welcomeCreated ) { throw "welcome screen already exists"; } _welcomeCreated = true; _screenMetadata['#'] = { js : p_jsUrl, container : $(document.body), navMap : {} }; _navMap['#'] = {}; if ( iris.hasOwnProperty("path") ) { _loadPaths(iris.path); } if ( _paths.length > 0 ) { _load(_paths, _pathsLoaded); } else { _pathsLoaded(); } } function _loadPaths (paths) { if ( typeof paths === "string" ) { if ( !_includes.hasOwnProperty(paths) ) { _paths.push(paths); } } else { for ( var p in paths ) { _loadPaths(paths[p]); } } } // Ensure include is defined function _setInclude (include, path, type) { if ( $.type(path) !== "string" || path === "" ) { iris.log("[error] path[" + path + "]", include); throw "Invalid path on " + type +" registration"; } _includes[path] = include; } function _pathsLoaded () { // check hashchange event support if(!("onhashchange" in window)) { throw "hashchange event unsupported"; } else { // force to do an initial navigation according to the actual hash // when finished the hashChange listener is added _startHashChange(); $(window).on("hashchange", _startHashChange); } } // // Scripts load // function _load (paths, callback) { iris.log("[load-js]", paths); _loadJsCallback = callback; var path, script; for (var i = 0; i < paths.length; i++) { if ( !_includes.hasOwnProperty(paths[i]) ) { _dependencyCount++; // If the path doesn't start with http or https, it's concatenated to the iris base uri path = /^https?:\/\//.test(paths[i]) ? paths[i] : String(iris.baseUri() + paths[i]); if ( !iris.cache() ) { path += "?_=" + new Date().getTime(); } else if( iris.cacheVersion() ) { path += "?_=" + iris.cacheVersion(); } if ( /\.html$/.test(paths[i]) ) { iris.ajax({ url: path, dataType: "html", async: true, componentPath: paths[i] }) .done(_templateLoaded); } else { script = document.createElement("script"); script.type = "text/javascript"; script.src = path; script.charset = "UTF-8"; if (iris.browser().msie && parseInt(iris.browser().version, 10) < 9) { script.onreadystatechange = onReadyStateChange; } else { script.onload = _checkLoadFinish; } _head.appendChild(script); } } } } function onReadyStateChange () { if ( this.readyState === "loaded" ) { _checkLoadFinish(); } } function _templateLoaded (data, textStatus, jqXHR) { _includes[this.componentPath] = data.replace(HTML_COMMENT_REGEX, ''); // Internet Explorer fails when a template component has a comment _checkLoadFinish(); } function _checkLoadFinish () { if ( --_dependencyCount === 0 ) { _loadJsCallback(); } } // // Navigation // function _goto(p_hashUri) { document.location.hash = p_hashUri; // Trigger hashchange event, then execute _startHashChange() } function _getHashRegex (hash) { return new RegExp('^' + hash.replace(PATH_PARAM_REGEX, '([^/]+)') + '(?:' + SCREEN_PARAM_REGEX + ')*/*' ); } function _getScreenPathParams (screenHash, hash, hashRegex) { // Get screen path params var params = {}; var paramsValues = hash.match(hashRegex); var i; if ( paramsValues ) { paramsValues = paramsValues.slice(1); // the first item is the whole match if ( paramsValues.length > 0 ) { var paramsNames = screenHash.match(PATH_PARAM_REGEX); for ( i = 0; i < paramsValues.length; i++ ) { params[ paramsNames[i].substr(1) ] = paramsValues[i]; // Remove first ':' } } } // Get matrix params var mpRegex = new RegExp('^' + screenHash + '(' + SCREEN_PARAM_REGEX + ')+'); var screenHashRaw = hash.match(mpRegex); if ( screenHashRaw && screenHashRaw.length > 0 ) { var matrixParams = screenHashRaw[0].match(new RegExp(SCREEN_PARAM_REGEX, 'g')); var idx; for ( i = 0; i < matrixParams.length; i++ ) { idx = matrixParams[i].indexOf('='); params[ matrixParams[i].substr(1, idx - 1) ] = matrixParams[i].substr(idx + 1); } } return params; } function _getRawHashRegex (hash) { return hash.replace(PATH_PARAM_REGEX, '(?:[^/]+)') + '(?:' + SCREEN_PARAM_REGEX + ')*(?:/?)'; } function _startHashChange(e) { // when document.location.href is [http://localhost:8080/#], the document.location.hash is [] (empty string) // to avoid the use of empty strings and prevent mistakes, we replace it by #. (# == welcome-screen) var currHash = document.location.hash || "#", fullHash = currHash, hashRegex, fullRawHashRegex, currNav = _navMap, screenFound, deep = 0, i, fullScreenHash = '', // e.g.: #/user/:id/friends fullScreenHashRaw = '', // e.g.: #/user/1234/friends;filter=all historyNavRaw = [], historyNav = [], firstNodeToSleep, hashRaw, screenHash, // e.g.: user/:id screenChilds; // used to add the screenHashes of the current deep to search // If a screen cannot sleep, finish navigation if ( _gotoCancelled ) { _gotoCancelled = false; iris.notify(iris.AFTER_NAVIGATION); return false; } // Notify that a valid navigation is started iris.notify(iris.BEFORE_NAVIGATION); while ( currHash ) { // // Find the screen according to the current level. // Sort screenChilds (alphabetically and descending) to get the correct screen in case of contained screens, e.g. // // screenChilds = ['screen/other', 'screen'] // 'screen/other' contains 'screen' (The array is sorted alphabetically and descending) // // currHash = 'screen/other' // - search child['screen/other'] in currHash['screen/other'] : screenFound! // // currHash = 'screen' // - search child['screen/other'] in currHash['screen'] : Not screenFound, screen/other contains screen but is skipped // - search child['screen'] in currHash['screen'] : screenFound! screenFound = false; screenChilds = []; for ( screenHash in currNav ) { screenChilds.push(screenHash); } screenChilds.sort(); screenChilds.reverse(); for ( i = 0; i < screenChilds.length; i++ ) { screenHash = screenChilds[i]; hashRegex = _getHashRegex(screenHash); if ( hashRegex.test(currHash) ) { screenFound = true; // fullScreenHash will be like #/user/:id/friends // If it's the first screen dont add '/' if ( fullScreenHash ) { fullScreenHash += '/' + screenHash; } else { fullScreenHash = screenHash; } // fullRawHashRegex will match hashes like #/user/1234/friends;filter=all if ( fullRawHashRegex ) { fullRawHashRegex += '/' + _getRawHashRegex(screenHash); } else { fullRawHashRegex = '^' + _getRawHashRegex(screenHash); } fullScreenHashRaw = fullHash.match(fullRawHashRegex)[0].replace(/\/?$/, ''); // remove last '/' break; } } if ( screenFound ) { var screenInPrevNav = _prevNav[deep] && _prevNav[deep] === fullScreenHash; // Prepare to the next iteration historyNav.push(fullScreenHash); historyNavRaw.push(fullScreenHashRaw); hashRaw = currHash; currHash = currHash.replace(hashRegex, ''); currNav = currNav[screenHash]; if ( !currHash && _prevNav.length > deep ) { firstNodeToSleep = screenInPrevNav ? deep + 1 : deep; // Can sleep? for ( i = _prevNav.length-1; i >= firstNodeToSleep; i-- ) { if ( _prevNav[i] !== "#" && _screen[_prevNav[i]].canSleep() === false ) { _gotoCancelled = true; document.location.href = _prevNavigationHash; return false; } } // Hide previous screens while ( _prevNav.length > firstNodeToSleep ) { var pathToSleep = _prevNav.pop(); _prevNavRaw.pop(); if ( pathToSleep !== "#" ) { var screenToSleep = _screen[pathToSleep]; screenToSleep._sleep(); screenToSleep.hide(); } } } // Wake up the screen at this deep (if not exists, create before) var hashNewParams = _prevNavRaw[deep] && _prevNavRaw[deep] !== fullScreenHashRaw; var params = _getScreenPathParams(screenHash, hashRaw, hashRegex); var screenInstance; if ( screenInPrevNav ) { // The screen is in the previous navigation. If the screen receives new parameters, wake up screenInstance = _screen[fullScreenHash]; if ( hashNewParams ) { screenInstance.params = params; screenInstance._awake(params); } } else { // The screen is not in the previous navigation // Instantiate the screen if it wasn't created previously if ( !_screen.hasOwnProperty(fullScreenHash) ) { if ( !_screenMetadata.hasOwnProperty(fullScreenHash) ) { throw '"' + fullScreenHash + '" not found'; } // Instantiate the new screen screenInstance = new Screen(fullScreenHash); screenInstance.params = params; screenInstance.create(); _screen[fullScreenHash] = screenInstance; } else { // Get the screen instance screenInstance = _screen[fullScreenHash]; } // Wake up screenInstance.show(); screenInstance._awake(params); } } else { // Invalid hash, screen not found iris.notify(iris.SCREEN_NOT_FOUND, fullHash); iris.log("[warning] '" + fullHash + "' must be registered using self.screens()"); return; } deep++; } // Prepare to the next iteration _prevNavigationHash = fullHash; _prevNav = historyNav; _prevNavRaw = historyNavRaw; iris.log("Navigation finished"); iris.notify(iris.AFTER_NAVIGATION); } function _parseLangTags(p_html) { var html = p_html; var matches = html.match(/@@[0-9A-Za-z_\.]+@@/g); if(matches) { var f, F = matches.length; for(f = 0; f < F; f++) { html = html.replace(matches[f], iris.translate(matches[f].substring(2, matches[f].length - 2))); } } return html; } // // UI // function _registerTmpl(path, html) { _includes[path] = html.replace(HTML_COMMENT_REGEX, ''); // Internet Explorer fails when a template component has a comment } function _registerUI(ui, path) { _setInclude(ui, path, "ui"); } function _jqToHash(p_$obj) { var hash = {}; var attrs = p_$obj.get(0).attributes; var label; for(var f = 0, F = attrs.length; f < F; f++) { label = attrs[f].name; if(label.indexOf("data-") === 0) { label = label.substr(5); } hash[label] = attrs[f].value; } return hash; } // // SCREEN // function _registerScreen(screen, path) { _setInclude(screen, path, "screen"); } function _destroyScreenByPath(p_screenPath) { if ( _screen.hasOwnProperty(p_screenPath) ) { if ( p_screenPath === "#" ) { throw "Welcome screen cannot be deleted"; } var hash = document.location.hash || '#'; // if url=http://example.com/#, the document.location.hash="" empty string // check if current screen is welcome screen (hash !== "#") // check if the current hash belongs to the path to delete if ( hash !== "#" && (p_screenPath.indexOf(hash) === 0 || hash.indexOf(p_screenPath) === 0) ) { throw "Cannot delete the current screen or its parents"; } _destroyScreen(p_screenPath); } else { iris.log('[warning] "' + p_screenPath + '" was not instantiated, nothing to destroy'); } } function _destroyScreen (path) { var screen = _screen[path]; // the screen can be register using self.screens() but no instantiated using navigation if ( screen !== undefined ) { // destroy child screens if ( screen.screenChilds !== undefined ) { for (var i = 0; i < screen.screenChilds.length; i++ ) { _destroyScreen(screen.screenChilds[i]); } } screen._destroy(); screen.get().remove(); // Remove instance delete _screen[path]; // Remove the screen completely (no reusable) var screenMeta = _screenMetadata[path]; delete screenMeta.parentNavMap[ screenMeta.hashFragment ]; // remove from parent's navMap delete _jsUrlScreens[screenMeta.js]; delete _screenMetadata[path]; } } var Component = function(id, $container, fileJs, type) { iris.Settable.call(this); this.type = type; this.id = id; this.uis = []; // child UIs this.uisMap = {}; // UIs sorted by id this.el = {}; // Map contains data-id elements this.con = $container; // JQ container this.fileJs = fileJs; // Path to the script this.fileTmpl = null; this.template = null; this.sleeping = null; _includes[fileJs](this); }; iris.inherits(Component, iris.Settable); var pComponent = Component.prototype; pComponent.APPEND = "append"; pComponent.REPLACE = "replace"; pComponent.PREPEND = "prepend"; pComponent._sleep = function() { for(var f = 0, F = this.uis.length; f < F; f++) { this.uis[f]._sleep(); } this.sleeping = true; this.sleep(); }; pComponent._awake = function(p_params) { this.sleeping = false; this.awake(p_params); for(var f = 0, F = this.uis.length; f < F; f++) { if (this.uis[f].sleeping !== false) { this.uis[f]._awake(); } } }; pComponent._destroy = function() { if(!this.sleeping) { this._sleep(); } // propage destroys for(var f = 0, F = this.uis.length; f < F; f++) { this.uis[f]._destroy(); } // remove component events for ( var eventName in this.events ) { iris.destroyEvents(eventName, this.events[eventName]); } this.destroy(); this.uis = null; this.events = null; }; pComponent._tmpl = function(p_htmlUrl, p_mode) { var f, childrens, tmpl; if (this.template !== null) { throw "self.tmpl() has already been called in '" + this.fileJs + "'"; } this.fileTmpl = p_htmlUrl; tmpl = $( _parseLangTags(_includes[p_htmlUrl]) ); this.template = tmpl; if(tmpl.size() > 1) { throw "'" + p_htmlUrl + "' must have only one root node"; } switch(p_mode) { case this.APPEND: this.con.append(tmpl); break; case this.REPLACE: this.con.replaceWith(tmpl); this.con = {selector: this.con.selector}; break; case this.PREPEND: this.con.prepend(tmpl); break; default: throw "Unknown template mode '" + p_mode + "'"; } // Process elements with data-* attributes this.el = {}; this.inflateTargets = {}; // The tmpl root node this._data_attrs(tmpl); // And all tmpl child nodes childrens = tmpl.get(0).getElementsByTagName("*"); for (f = childrens.length; f--;) { this._data_attrs( $(childrens[f]) ); } }; pComponent._data_attrs = function ($el) { var f, key, attr, attrs = $el.get(0).attributes, inflate, inflateFormats = {}, inflatesByKeys = {}, target, targetParams, format, formatParams, formatMatches ; for (f = attrs.length; f--;) { attr = attrs[f]; if ( attr.name.indexOf("data-") === 0 ) { key = attr.name.substr(5); } else { continue; } // data-id if ( key === "id" ) { this.el[ attr.value ] = $el; continue; } // data-*-format if ( key.indexOf("-format", key.length - 7) !== -1 ) { format = attr.value; formatParams = undefined; if ( format && FORMAT_REG_EXP.test(format) ) { formatMatches = format.match(FORMAT_REG_EXP); format = formatMatches[1]; formatParams = formatMatches[2]; // TODO manage multiple parameter using: formatParams[2].splice(","); } // inflateFormats key = "jq-xxxx-format" -> "xxxx" inflateFormats[ key.substr(3, key.length - 10) ] = { key: format, params: formatParams }; continue; } if ( key.indexOf("jq-") === 0 ) { key = key.substr(3); if ( /^(text|html|val|toggle)$/.test(key) ) { target = key; } else if ( /^(prop-|attr-)/.test(key) ) { target = key.substr(0, 4); targetParams = key.substr(5); } else { // Other data-* attribute continue; } } if ( !this.inflateTargets.hasOwnProperty(attr.value) ) { this.inflateTargets[ attr.value ] = []; } inflate = { target: target, targetParams: targetParams, el: $el }; this.inflateTargets[ attr.value ].push( inflate ); inflatesByKeys[key] = inflate; } // After of iterate the element data attributes, set the formatting to each target for ( key in inflateFormats ) { if ( inflatesByKeys.hasOwnProperty(key) ) { inflatesByKeys[key].format = inflateFormats[key].key; inflatesByKeys[key].formatParams = inflateFormats[key].params; } } }; pComponent.inflate = function(data) { var dataKey, f, F, targets, inflate, format, unformattedValue, value; for ( dataKey in this.inflateTargets ) { unformattedValue = iris.val(data, dataKey); if ( unformattedValue !== undefined ) { targets = this.inflateTargets[dataKey]; for ( f = 0, F = targets.length; f < F; f++ ) { inflate = targets[f]; switch ( inflate.format ) { case "date": value = iris.date(unformattedValue, inflate.formatParams); break; case "currency": value = iris.currency(unformattedValue); break; case "number": value = iris.number(unformattedValue); break; default: value = unformattedValue; } switch ( inflate.target ) { case "text": inflate.el.text(value); break; case "html": inflate.el.html(value); break; case "val": inflate.el.val(value); break; case "toggle": inflate.el.toggle(value); break; case "prop": inflate.el.prop(inflate.targetParams, value); break; case "attr": inflate.el.attr(inflate.targetParams, value); } } } } }; // Check if the template is set (https://github.com/intelygenz/iris/issues/19) pComponent._checkTmpl = function() { if(this.template === null) { throw "Set a template using self.tmpl() in '" + this.fileJs + "'"; } }; pComponent.show = function() { this._checkTmpl(); this.template.show(); }; pComponent.hide = function() { this._checkTmpl(); this.template.hide(); }; pComponent.get = function(p_id) { this._checkTmpl(); if(p_id) { if(!this.el.hasOwnProperty(p_id)) { var id = "[data-id=" + p_id + "]", filter = this.template.filter(id), $element = null; if(filter.length > 0) { $element = filter; } else { var find = this.template.find(id); if(find.size() > 0) { $element = find; } } if($element === null) { throw "[data-id=" + p_id + "] not screenFound in '" + this.fileTmpl + "' used by '" + this.fileJs + "'"; } else if($element.size() > 1) { throw "[data-id=" + p_id + "] must be unique in '" + this.fileTmpl + "' used by '" + this.fileJs + "'"; } this.el[p_id] = $element; } return this.el[p_id]; } return this.template; }; pComponent._ui = function(p_id, p_jsUrl, p_uiSettings, p_templateMode) { if ( p_jsUrl === undefined ) { // Get UI var ui = this.uisMap[p_id]; if ( ui === undefined ) { ui = []; } return ui; } else { // Create UI return this._createUi(p_id, p_jsUrl, p_uiSettings, p_templateMode); } }; pComponent._createUi = function(p_id, p_jsUrl, p_uiSettings, p_templateMode) { var $container = this.get(p_id); if($container !== undefined && $container.size() === 1) { var uiInstance = new UI($container, $container.data("id"), p_jsUrl, p_uiSettings, p_templateMode, this); if (uiInstance._tmplMode === undefined || uiInstance._tmplMode === uiInstance.REPLACE) { this.el[p_id] = undefined; } this.uis.push(uiInstance); // Add uiInstance to the UIs map if ( uiInstance._tmplMode === uiInstance.REPLACE ) { this.uisMap[p_id] = uiInstance; } else { if ( !this.uisMap.hasOwnProperty(p_id) ) { this.uisMap[p_id] = []; } this.uisMap[p_id].push(uiInstance); } return uiInstance; } else { throw "The container does not exist or has been replaced."; } }; pComponent.destroyUI = function(p_ui) { if ( p_ui === undefined ) { // Self destroy this.parentUI.destroyUI(this); } else { var idx; // Remove p_ui from the UIs array idx = $.inArray(p_ui, this.uis); if ( idx !== -1 ) { this.uis.splice(idx, 1); } // Remove p_ui from the UIs map if ( p_ui._tmplMode === p_ui.REPLACE ) { this.uisMap[p_ui.id] = null; delete this.uisMap[p_ui.id]; } else { var uis = this.uisMap[p_ui.id]; idx = $.inArray(p_ui, uis); if ( idx !== -1 ) { uis.splice(idx, 1); } } // Destroy p_ui p_ui._destroy(); p_ui.get().remove(); } }; pComponent.destroyUIs = function(id) { var uis = this.uisMap[id]; if ( $.isArray(uis) ) { var f, F; for ( f=uis.length-1; f >= 0; f-- ) { this.destroyUI(uis[f]); } } else if ( uis && uis._tmplMode === this.REPLACE ) { // uis is a single UI this.destroyUI(uis); } }; pComponent.container = function() { return this.con; }; // // To override functions // pComponent.create = function() {}; pComponent.awake = function() {}; pComponent.sleep = function() {}; pComponent.destroy = function() {}; // // UI // var UI = function($container, id, fileJs, settings, tmplMode, parentUI) { Component.call(this, id, $container, fileJs, 'ui'); var jqToHash = _jqToHash($container); this.parentUI = parentUI; this._tmplMode = tmplMode || "replace"; $.extend(this.cfg, jqToHash, settings || {}); this.create(); this._awake(); }; iris.inherits(UI, Component); var pUI = UI.prototype; pUI.tmplMode = function(p_mode) { this._tmplMode = p_mode; }; pUI.tmpl = function(p_htmlUrl) { this._tmpl(p_htmlUrl, this._tmplMode); }; pUI.ui = function(p_id, p_jsUrl, p_uiSettings, p_templateMode) { return this._ui(p_id, p_jsUrl, p_uiSettings, p_templateMode); }; // // SCREEN // var Screen = function(path) { var screenMeta = _screenMetadata[path]; Component.call(this, path, screenMeta.container, screenMeta.js, 'screen'); this.params = {}; this.screenConId = null; this.navMap = null; this.hash = null; }; iris.inherits(Screen, Component); var pScreen = Screen.prototype; pScreen.param = function(p_key) { return this.params[p_key]; }; pScreen.ui = function(p_id, p_jsUrl, p_uiSettings, p_templateMode) { if ( p_id === this.screenConId ) { throw "'" + p_id + "' has already been registered as a screen container"; } return this._ui(p_id, p_jsUrl, p_uiSettings, p_templateMode); }; pScreen.tmpl = function(p_htmlUrl) { this._tmpl(p_htmlUrl, this.APPEND); }; pScreen.screens = function(p_containerId, p_screens) { this.screenConId = p_containerId; if (this.hasOwnProperty("screenChilds")) { throw "Multiple calls to self.screens() are not allowed: " + this.id; } else if (this.template === null){ throw "self.tmpl() must be called before self.screens(): " + this.id; } else { var $cont = this.get(p_containerId); this.screenChilds = []; var screenMeta = _screenMetadata[this.id]; this.navMap = ( this.id === '#' ) ? _navMap['#'] : screenMeta.parentNavMap[screenMeta.hashFragment]; var newScreen, newScreenHashFragment, newScreenHash, newScreenJs; for ( var i = 0; i < p_screens.length; i++ ) { newScreen = p_screens[i]; newScreenHashFragment = newScreen[0]; // newScreen[0] == hash fragment newScreenHash = this.id + "/" + newScreenHashFragment; // full hash like #/parent/screen/:id (screen[0] == hash fragment) newScreenJs = newScreen[1]; // screen js path if ( _jsUrlScreens.hasOwnProperty(newScreenJs) ) { throw 'Cannot associate "' + newScreenJs + '" to "' + newScreenHash + '", it was previously used in "' + _jsUrlScreens[newScreenJs] + '"'; } if ( _screenMetadata.hasOwnProperty(newScreenHash) ) { throw 'The hash "' + newScreenHash + '" was associated to "' + _screenMetadata[newScreenHash].js + '" previously, cannot associate to "' + newScreenJs + '" again'; } _jsUrlScreens[newScreenJs] = newScreenHash; // To prevent future instances of this screen _screenMetadata[newScreenHash] = { js : newScreenJs, parentNavMap : this.navMap, container : $cont, hashFragment : newScreenHashFragment }; this.screenChilds[i] = newScreenHash; this.navMap[newScreenHashFragment] = {}; iris.log("Register screen hash[" + newScreenHash + "] js[" + newScreenJs + "]"); } } }; // To override pScreen.canSleep = function() { return true; }; function _registerRes (resourceOrPath, path) { if ( typeof resourceOrPath === "string" ) { // resourceOrPath == path if ( !_includes.hasOwnProperty(resourceOrPath) ) { throw "add service[" + resourceOrPath + "] to iris.path"; } return _includes[resourceOrPath]; } else { // resourceOrPath == resource var serv = new iris.Resource(); serv.cfg = {}; serv.settings({ type: "json", path: "" }); resourceOrPath(serv); _setInclude(serv, path, "resource"); } } // // Debug mode // function _debug (enabled) { var $doc = $(window.document); var style = document.getElementById('iris-debug-css'); if ( enabled && !style ) { $('').appendTo(_head); $doc.on('keydown', _debugModeOnKeyDown); } else if ( !enabled && style ) { $doc.off('keydown', _debugModeOnKeyDown); $(style).remove(); } } function _debugModeOnKeyDown (e) { // Control + Shift + Alt + D if ( e.shiftKey && e.ctrlKey && e.altKey && e.keyCode !== 16 && e.keyCode === 68 ) { _debugMode = !_debugMode; var key, screen; for ( key in _screen ) { screen = _screen[key]; _applyDebugMode(screen); _applyDebugToUIs(screen.uis); } } } // Recursive function _applyDebugToUIs (uis) { for ( var f = 0, F = uis.length; f < F; f++ ) { _applyDebugMode( uis[f] ); _applyDebugToUIs( uis[f].uis ); } } function _applyDebugMode (component) { component.template.toggleClass('iris-debug iris-debug-' + component.type, _debugMode); if ( _debugMode ) { // Add debug info label. Styles are in line to override inheritance var color, idType; if ( component.type === 'screen' ) { color = 'red'; idType = 'Hash'; } else { color = 'blue'; idType = 'Data-id'; } var styleInfo = {'font-family': 'sans-serif', 'font-size': '14px', 'color': 'white', 'padding': '4px', 'white-space': 'nowrap', 'background-color': color }; var tooltip = 'Type: ' + component.type + '\n' + idType + ': ' + component.id + '\nPresenter: ' + component.fileJs + '\nTemplate: ' + component.fileTmpl; component.debugElement = $( '' + '' + component.id + ' [' + component.fileJs + ']' + '').css(styleInfo).prependTo(component.template); } else { // Remove debug info label if exists if ( component.debugElement ) { component.debugElement.remove(); } } } iris.screen = _registerScreen; iris.destroyScreen = _destroyScreenByPath; iris.welcome = _welcome; iris.navigate = _goto; iris.ui = _registerUI; iris.tmpl = _registerTmpl; iris.resource = _registerRes; iris.include = _load; iris.debug = _debug; // // Classes // iris.Component = Component; iris.UI = UI; iris.Screen = Screen; _init(); })(jQuery); (function() { var Resource = function() {}; Resource.prototype = new iris.Settable(); Resource.prototype.ajax = function(p_method, p_path, p_params, f_success, f_error) { return iris.ajax({ "url": this.setting("path") + p_path, "type": p_method, "data": p_params, "cache": false, "dataType": this.setting("type"), "async": true, "success": f_success, "error": function (p_request, p_textStatus, p_errorThrown) { iris.notify(iris.RESOURCE_ERROR, {request: p_request, status: p_textStatus, error: p_errorThrown}); if ( f_error !== undefined ) { f_error( p_request, p_textStatus, p_errorThrown ); } } }); }; Resource.prototype.get = function(p_path, f_success, f_error) { return this.ajax("GET", p_path, null, f_success, f_error); }; Resource.prototype.del = function(p_path, f_success, f_error) { return this.ajax("DELETE", p_path, null, f_success, f_error); }; Resource.prototype.put = function(p_path, p_params, f_success, f_error) { return this.ajax("PUT", p_path, p_params, f_success, f_error); }; Resource.prototype.post = function(p_path, p_params, f_success, f_error) { return this.ajax("POST", p_path, p_params, f_success, f_error); }; iris.Resource = Resource; })();