diff --git a/build/nodegame-window.js b/build/nodegame-window.js index 3624410..874f941 100644 --- a/build/nodegame-window.js +++ b/build/nodegame-window.js @@ -1,6 +1,6 @@ /** * # GameWindow - * Copyright(c) 2021 Stefano Balietti + * Copyright(c) 2023 Stefano Balietti * MIT Licensed * * API to interface nodeGame with the browser window @@ -1346,6 +1346,7 @@ */ GameWindow.prototype.preCacheTest = function(cb, uri) { var iframe, iframeName; + uri = uri || '/pages/testpage.htm'; if ('string' !== typeof uri) { throw new TypeError('GameWindow.precacheTest: uri must string ' + @@ -1372,8 +1373,15 @@ catch(e) { W.cacheSupported = false; } + // It's possible that it was already removed if two calls + // to preCacheTest are made one after the other. + try { + document.body.removeChild(iframe); + } + catch(e) { + node.warn('W.preCacheTest: iframe already removed'); + } - document.body.removeChild(iframe); if (cb) cb(); }); }; @@ -1599,9 +1607,8 @@ GameWindow.prototype.loadFrame = function(uri, func, opts) { var that; var loadCache; - var storeCacheNow, storeCacheLater; + var shouldTestCache, storeCacheNow, storeCacheLater; var scrollUp; - var autoParse, autoParsePrefix, autoParseMod; var iframe, iframeName, iframeDocument, iframeWindow; var frameDocumentElement, frameReady; var lastURI; @@ -1658,6 +1665,7 @@ } else if (opts.cache.loadMode === 'cache') { loadCache = true; + shouldTestCache = true; } else { throw new Error('GameWindow.loadFrame: unkown cache ' + @@ -1669,48 +1677,24 @@ storeCacheNow = false; storeCacheLater = false; } - else if (opts.cache.storeMode === 'onLoad') { - storeCacheNow = true; - storeCacheLater = false; - } - else if (opts.cache.storeMode === 'onClose') { - storeCacheNow = false; - storeCacheLater = true; - } else { - throw new Error('GameWindow.loadFrame: unkown cache ' + - 'store mode: ' + opts.cache.storeMode); - } - } - } - - // Parsing options. + + if (opts.cache.storeMode === 'onLoad') { + storeCacheNow = true; + storeCacheLater = false; + } + else if (opts.cache.storeMode === 'onClose') { + storeCacheNow = false; + storeCacheLater = true; + } + else { + throw new Error('GameWindow.loadFrame: unkown cache ' + + 'store mode: ' + opts.cache.storeMode); + } - if ('undefined' !== typeof opts.autoParse) { - if ('object' !== typeof opts.autoParse) { - throw new TypeError('GameWindow.loadFrame: opts.autoParse ' + - 'must be object or undefined. Found: ' + - opts.autoParse); - } - if ('undefined' !== typeof opts.autoParsePrefix) { - if ('string' !== typeof opts.autoParsePrefix) { - throw new TypeError('GameWindow.loadFrame: opts.' + - 'autoParsePrefix must be string ' + - 'or undefined. Found: ' + - opts.autoParsePrefix); + shouldTestCache = true; } - autoParsePrefix = opts.autoParsePrefix; } - if ('undefined' !== typeof opts.autoParseMod) { - if ('string' !== typeof opts.autoParseMod) { - throw new TypeError('GameWindow.loadFrame: opts.' + - 'autoParseMod must be string ' + - 'or undefined. Found: ' + - opts.autoParseMod); - } - autoParseMod = opts.autoParseMod; - } - autoParse = opts.autoParse; } // Scroll Up. @@ -1720,7 +1704,8 @@ // Store unprocessed uri parameter. this.unprocessedUri = uri; - if (this.cacheSupported === null) { + shouldTestCache = true; + if (shouldTestCache && this.cacheSupported === null) { this.preCacheTest(function() { that.loadFrame(uri, func, opts); }); @@ -1789,13 +1774,9 @@ handleFrameLoad(that, uri, iframe, iframeName, loadCache, storeCacheNow, function() { - // Executes callback, autoParses, + // Executes callback, css, replace, html, // and updates GameWindow state. - that.updateLoadFrameState(func, - autoParse, - autoParseMod, - autoParsePrefix, - scrollUp); + that.updateLoadFrameState(func, scrollUp); }); }); } @@ -1811,13 +1792,9 @@ handleFrameLoad(this, uri, iframe, iframeName, loadCache, storeCacheNow, function() { - // Executes callback + // Executes callback, css, replace, html, // and updates GameWindow state. - that.updateLoadFrameState(func, - autoParse, - autoParseMod, - autoParsePrefix, - scrollUp); + that.updateLoadFrameState(func, scrollUp); }); } } @@ -1860,14 +1837,10 @@ * - decrements the counter of loading iframes * - executes a given callback function * - auto parses the elements specified (if any) + * - updates UI, e.g., scroll-up or adds CSS * - set the window state as loaded (eventually) * * @param {function} func Optional. A callback function - * @param {object} autoParse Optional. An object containing elements - * to replace in the HTML DOM. - * @param {string} autoParseMod Optional. Modifier for search and replace - * @param {string} autoParsePrefix Optional. Custom prefix to add to the - * keys of the elements in autoParse object * @param {boolean} scrollUp Optional. If TRUE, scrolls the page to the, * top (if window.scrollTo is defined). Default: FALSE. * @@ -1877,20 +1850,23 @@ * @emit FRAME_LOADED * @emit LOADED */ - GameWindow.prototype.updateLoadFrameState = function(func, autoParse, - autoParseMod, - autoParsePrefix, - scrollUp) { + GameWindow.prototype.updateLoadFrameState = function(func, scrollUp) { - var loaded, stageLevel; + var css, html, replace, loaded, stageLevel; loaded = updateAreLoading(this, -1); if (loaded) this.setStateLevel('LOADED'); if (func) func.call(node.game); - if (autoParse) { - this.searchReplace(autoParse, autoParseMod, autoParsePrefix); - } if (scrollUp && window.scrollTo) window.scrollTo(0,0); + css = node.game.getProperty('css'); + if (css) W.cssRule(css); + + html = node.game.getProperty('html'); + if (html) W.write(html); + + replace = node.game.getProperty('replace'); + if (replace) W.searchReplace(replace, 'g', ''); + // ng event emitter is not used. node.events.ee.game.emit('FRAME_LOADED'); node.events.ee.stage.emit('FRAME_LOADED'); @@ -2159,6 +2135,40 @@ }; + /** + * ### GameWindow.cssRule + * + * Add a css rule to the page + * + * @param {string} rule The css rule + * @param {boolean} clear Optional. TRUE to clear all previous rules + * added with this method to the page + * + * @return {Element} The HTML style element where the rules were added + * + * @see handleFrameLoad + */ + GameWindow.prototype.cssRule = function(rule, clear) { + var root; + if ('string' !== typeof rule) { + throw new TypeError(G + 'cssRule: style property must be ' + + 'string. Found: ' + rule); + } + if (!this.styleElement) { + root = W.getFrameDocument() || window.document; + this.styleElement = W.append('style', root.head, { + type: 'text/css', + id: 'ng_style' + }); + } + else if (clear) { + this.styleElement.innerHTML = ''; + } + this.styleElement.innerHTML += rule; + return this.styleElement; + }; + + // ## Helper functions /** @@ -4070,7 +4080,7 @@ /** * # extra - * Copyright(c) 2022 Stefano Balietti + * Copyright(c) 2023 Stefano Balietti * MIT Licensed * * GameWindow extras @@ -4084,15 +4094,20 @@ var GameWindow = node.GameWindow; var DOM = J.require('DOM'); + var G = 'GameWindow.'; + + // ### BASIC. + /** * ### GameWindow.getScreen * * Returns the "screen" of the game * - * i.e. the innermost element inside which to display content + * i.e., the innermost element inside which to display content * * In the following order the screen can be: * + * - the element with id "container" (presumably inside the iframe) * - the body element of the iframe * - the document element of the iframe * - the body element of the document @@ -4102,45 +4117,43 @@ */ GameWindow.prototype.getScreen = function() { var el; - el = this.getFrameDocument(); - if (el) el = el.body || el; - else el = document.body || document.lastElementChild; + el = this.gid('container'); + if (!el) { + el = this.getFrameDocument(); + if (el) el = el.body || el; + else el = document.body || document.lastElementChild; + } return el; }; /** - * ### GameWindow.cssRule + * ### GameWindow.uniqueId|generateUniqueId * - * Add a css rule to the page + * Generates a unique id * - * @param {string} rule The css rule - * @param {boolean} clear Optional. TRUE to clear all previous rules - * added with this method to the page + * Overrides JSUS.DOM.generateUniqueId. * - * @return {Element} The HTML style element where the rules were added + * @param {string} prefix Optional. The prefix to use * - * @see handleFrameLoad + * @return {string} The generated id + * + * @experimental + * TODO: it is not always working fine. */ - GameWindow.prototype.cssRule = function(rule, clear) { - var root; - if ('string' !== typeof rule) { - throw new TypeError('Game.execStep: style property must be ' + - 'string. Found: ' + rule); - } - if (!this.styleElement) { - root = W.getFrameDocument() || window.document; - this.styleElement = W.append('style', root.head, { - type: 'text/css', - id: 'ng_style' - }); - } - else if (clear) { - this.styleElement.innerHTML = ''; + GameWindow.prototype.uniqueId = + GameWindow.prototype.generateUniqueId = function(prefix) { + var id, found; + id = '' + (prefix || J.randomInt(0, 1000)); + found = this.gid(id); + while (found) { + id = '' + prefix + '_' + J.randomInt(0, 1000); + found = this.gid(id); } - this.styleElement.innerHTML += rule; - return this.styleElement; + return id; }; + // ### WRITE TO SCREEN. + /** * ### GameWindow.write * @@ -4154,17 +4167,13 @@ * * @return {string|object} The content written * + * @see JSUS.write * @see GameWindow.writeln + * @see getDefaultRoot */ GameWindow.prototype.write = function(text, root) { - if ('string' === typeof root) root = this.getElementById(root); - else if (!root) root = this.getScreen(); - - if (!root) { - throw new - Error('GameWindow.write: could not determine where to write'); - } - return DOM.write(root, text); + root = getDefaultRoot(root, 'write'); + return DOM.write2(root, text); }; /** @@ -4180,99 +4189,253 @@ * * @return {string|object} The content written * + * @see JSUS.writeln * @see GameWindow.write + * @see getDefaultRoot */ GameWindow.prototype.writeln = function(text, root, br) { - if ('string' === typeof root) root = this.getElementById(root); - else if (!root) root = this.getScreen(); + root = getDefaultRoot(root, 'writeln'); + return DOM.writeln2(root, text, br); + }; - if (!root) { - throw new Error('GameWindow.writeln: ' + - 'could not determine where to write'); - } - return DOM.writeln(root, text, br); + /** + * ### DOM.sprintf + * + * Builds up a decorated HTML text element + * + * Performs string substitution from an args object where the first + * character of the key bears the following semantic: + * + * - '@': variable substitution with escaping + * - '!': variable substitution without variable escaping + * - '%': wraps a portion of string into a _span_ element to which is + * possible to associate a css class or id. Alternatively, + * it also possible to add in-line style. E.g.: + * + * ```javascript + * sprintf('%sImportant!%s An error has occurred: %pre@err%pre', { + * '%pre': { + * style: 'font-size: 12px; font-family: courier;' + * }, + * '%s': { + * id: 'myId', + * 'class': 'myClass', + * }, + * '@err': 'file not found', + * }, document.body); + * ``` + * + * Special span elements are %strong and %em, which add + * respectively a _strong_ and _em_ tag instead of the default + * _span_ tag. They cannot be styled. + * + * @param {string} string A text to transform + * @param {object} args Optional. An object containing string + * transformations + * @param {Element} root Optional. An HTML element to which append the + * string. Defaults, a new _span_ element + * + * @return {Element} The root element. + */ + GameWindow.prototype.sprintf = function(string, args, root) { + if (!root) root = getDefaultRoot(root, 'sprintf'); + return DOM.sprintf(string, args, root); }; /** - * ### GameWindow.generateUniqueId + * ### GameWindo.add|append * - * Generates a unique id - * - * Overrides JSUS.DOM.generateUniqueId. + * Creates and append an element with specified attributes to a root * - * @param {string} prefix Optional. The prefix to use + * @param {string} name The name of the HTML tag + * @param {HTMLElement} root The root element to which the new element + * will be appended + * @param {object|string} options Optional. Object containing + * attributes for the element and rules about how to insert it relative + * to root. Available options: insertAfter, insertBefore (default: + * child of root). If string, it is the id of the element. Examples: * - * @return {string} The generated id * - * @experimental - * TODO: it is not always working fine. + * @see getDefaultRoot */ - GameWindow.prototype.generateUniqueId = function(prefix) { - var id, found; - - id = '' + (prefix || J.randomInt(0, 1000)); - found = this.getElementById(id); - - while (found) { - id = '' + prefix + '_' + J.randomInt(0, 1000); - found = this.getElementById(id); - } - return id; + GameWindow.prototype.add = + GameWindow.prototype.append = function(el, root, opts) { + if (!root) root = getDefaultRoot(root, 'add'); + return DOM.add(el, root, opts); }; /** - * ### GameWindow.toggleInputs + * ### GameWindow.searchReplace * - * Enables / disables the input forms + * Replaces the innerHTML of the element/s with matching id or class name * - * If an id is provided, only input elements that are children - * of the element with the specified id are toggled. + * It iterates through each element and passes it to + * `GameWindow.setInnerHTML`. * - * If id is not given, it toggles the input elements on the whole page, - * including the frame document, if found. + * If elements is array, each item in the array must be of the type: * - * If a state parameter is given, all the input forms will be either - * disabled or enabled (and not toggled). + * ```javascript * - * @param {string} id Optional. The id of the element container - * of the forms. Default: the whole page, including the frame document - * @param {boolean} disabled Optional. Forces all the inputs to be either - * disabled or enabled (not toggled) + * { search: 'key', replace: 'value' } * - * @return {boolean} FALSE, if the method could not be executed + * // or * - * @see GameWindow.getFrameDocument - * @see toggleInputs + * { search: 'key', replace: 'value', mod: 'id' } + * ``` + * + * If elements is object, it must be of the type: + * + * ```javascript + * + * { + * search1: value1, search2: value 2 // etc. + * } + * ``` + * + * It accepts a variable number of input parameters. The first is always + * _elements_. If there are 2 input parameters, the second is _prefix_, + * while if there are 3 input parameters, the second is _mod_ and the third + * is _prefix_. + * + * @param {object|array} Elements to search and replace + * @param {string} mod Optional. Modifier passed to GameWindow.setInnerHTML + * @param {string} prefix Optional. Prefix added to the search string. + * Default: 'ng_replace_', null or '' equals no prefix. + * + * @see GameWindow.setInnerHTML */ - GameWindow.prototype.toggleInputs = function(id, disabled) { - var container; - if (!document.getElementsByTagName) { - node.err( - 'GameWindow.toggleInputs: getElementsByTagName not found'); - return false; + GameWindow.prototype.searchReplace = function() { + var elements, mod, prefix; + var name, len, i, el, rep; + + if (arguments.length === 2) { + mod = 'g'; + prefix = arguments[1]; } - if (id && 'string' === typeof id) { - throw new Error('GameWindow.toggleInputs: id must be string or ' + - 'undefined. Found: ' + id); + else if (arguments.length > 2) { + mod = arguments[1]; + prefix = arguments[2]; } - if (id) { - container = this.getElementById(id); - if (!container) { - throw new Error('GameWindow.toggleInputs: no elements found ' + - 'with id ' + id); + + if ('undefined' === typeof prefix) { + prefix = 'ng_replace_'; + } + else if (null === prefix) { + prefix = ''; + } + else if ('string' !== typeof prefix) { + throw new TypeError(G + 'searchReplace: prefix must be string, ' + + 'null or undefined. Found: ' + prefix); + } + + elements = arguments[0]; + if (J.isArray(elements)) { + i = -1, len = elements.length; + for ( ; ++i < len ; ) { + el = elements[i].search; + if ('string' !== typeof el && 'number' !== typeof el) { + continue; + } + rep = elements[i].replace; + if ('string' !== typeof rep && 'number' !== typeof rep) { + continue; + } + + this.setInnerHTML(prefix + el, + elements[i].replace, + elements[i].mod || mod); + } + + } + else if ('object' === typeof elements) { + for (name in elements) { + if (elements.hasOwnProperty(name)) { + el = elements[name]; + if ('string' !== typeof el && 'number' !== typeof el) { + node.warn(G + 'searchReplace: replace for key ' + name + + ' is invalid. Found: ' + el); + continue; + } + this.setInnerHTML(prefix + name, el, mod); + } } - toggleInputs(disabled, container); } else { - // The whole page. - toggleInputs(disabled); - container = this.getFrameDocument(); - // If there is a frame, apply it there too. - if (container) toggleInputs(disabled, container); + throw new TypeError(G + 'setInnerHTML: elements must be ' + + 'object or arrray. Found: ' + elements); } - return true; + }; + /** + * ### GameWindow.html|setInnerHTML + * + * Replaces the innerHTML of the element with matching id or class name + * + * @param {string|number} search Element id or className + * @param {string|number} replace The new value of the property innerHTML + * @param {string} mod Optional. A modifier defining how to use the + * search parameter. Values: + * + * - 'id': replaces at most one element with the same id (default) + * - 'className': replaces all elements with same class name + * - 'g': replaces globally, both by id and className + */ + GameWindow.prototype.setInnerHTML = + GameWindow.prototype.html = function(search, replace, mod) { + var el, i, len; + + // Only process strings or numbers. + if ('string' !== typeof search && 'number' !== typeof search) { + throw new TypeError(G + 'setInnerHTML: search must be ' + + 'string or number. Found: ' + search + + " (replace = " + replace + ")"); + } + + // Only process strings or numbers. + if ('string' !== typeof replace && 'number' !== typeof replace) { + throw new TypeError(G + 'setInnerHTML: replace must be ' + + 'string or number. Found: ' + replace + + " (search = " + search + ")"); + } + + if ('undefined' === typeof mod) { + mod = 'id'; + } + else if ('string' === typeof mod) { + if (mod !== 'g' && mod !== 'id' && mod !== 'className') { + throw new Error(G + 'setInnerHTML: invalid ' + + 'mod value: ' + mod + + " (search = " + search + ")"); + } + } + else { + throw new TypeError(G + 'setInnerHTML: mod must be ' + + 'string or undefined. Found: ' + mod + + " (search = " + search + ")"); + } + + if (mod === 'id' || mod === 'g') { + // Look by id. + el = W.getElementById(search); + if (el && el.className !== search) el.innerHTML = replace; + } + + if (mod === 'className' || mod === 'g') { + // Look by class name. + el = W.getElementsByClassName(search); + len = el.length; + if (len) { + i = -1; + for ( ; ++i < len ; ) { + el[i].innerHTML = replace; + } + } + } + }; + + // ### ADD STUFF: Event button, loading dots. + /** * ### GameWindow.getLoadingDots * @@ -4285,7 +4448,7 @@ * * @param {number} len Optional. The maximum length of the loading dots. * Default: 5 - * @param {string} id Optional The id of the span + * @param {string} id Optional. The id of the span * * @return {object} An object containing two properties: the span element * and a method stop, that clears the interval @@ -4293,8 +4456,8 @@ GameWindow.prototype.getLoadingDots = function(len, id) { var spanDots, counter, intervalId; if (len & len < 0) { - throw new Error('GameWindow.getLoadingDots: len cannot be < 0. ' + - 'Found: ' + len); + throw new Error(G + 'getLoadingDots: len cannot be < 0. Found: ' + + len); } spanDots = document.createElement('span'); spanDots.id = id || 'span_dots'; @@ -4367,7 +4530,7 @@ GameWindow.prototype.getEventButton = function(event, attributes) { var b; if ('string' !== typeof event) { - throw new TypeError('GameWindow.getEventButton: event must ' + + throw new TypeError(G + 'getEventButton: event must ' + 'be string. Found: ' + event); } if ('string' === typeof attributes) { @@ -4405,163 +4568,58 @@ return root.appendChild(eb); }; + // ### SHOWING, HIDING, TOGGLING. + /** - * ### GameWindow.searchReplace - * - * Replaces the innerHTML of the element/s with matching id or class name - * - * It iterates through each element and passes it to - * `GameWindow.setInnerHTML`. - * - * If elements is array, each item in the array must be of the type: - * - * ```javascript - * - * { search: 'key', replace: 'value' } - * - * // or - * - * { search: 'key', replace: 'value', mod: 'id' } - * ``` - * - * If elements is object, it must be of the type: - * - * ```javascript + * ### GameWindow.toggleInputs * - * { - * search1: value1, search2: value 2 // etc. - * } - * ``` + * Enables / disables the input forms * - * It accepts a variable number of input parameters. The first is always - * _elements_. If there are 2 input parameters, the second is _prefix_, - * while if there are 3 input parameters, the second is _mod_ and the third - * is _prefix_. + * If an id is provided, only input elements that are children + * of the element with the specified id are toggled. * - * @param {object|array} Elements to search and replace - * @param {string} mod Optional. Modifier passed to GameWindow.setInnerHTML - * @param {string} prefix Optional. Prefix added to the search string. - * Default: 'ng_replace_', null or '' equals no prefix. + * If id is not given, it toggles the input elements on the whole page, + * including the frame document, if found. * - * @see GameWindow.setInnerHTML - */ - GameWindow.prototype.searchReplace = function() { - var elements, mod, prefix; - var name, len, i; - - if (arguments.length === 2) { - mod = 'g'; - prefix = arguments[1]; - } - else if (arguments.length > 2) { - mod = arguments[1]; - prefix = arguments[2]; - } - - if ('undefined' === typeof prefix) { - prefix = 'ng_replace_'; - } - else if (null === prefix) { - prefix = ''; - } - else if ('string' !== typeof prefix) { - throw new TypeError('GameWindow.searchReplace: prefix ' + - 'must be string, null or undefined. Found: ' + - prefix); - } - - elements = arguments[0]; - if (J.isArray(elements)) { - i = -1, len = elements.length; - for ( ; ++i < len ; ) { - this.setInnerHTML(prefix + elements[i].search, - elements[i].replace, - elements[i].mod || mod); - } - - } - else if ('object' !== typeof elements) { - for (name in elements) { - if (elements.hasOwnProperty(name)) { - this.setInnerHTML(prefix + name, elements[name], mod); - } - } - } - else { - throw new TypeError('GameWindow.setInnerHTML: elements must be ' + - 'object or arrray. Found: ' + elements); - } - - }; - - GameWindow.prototype.setInnerHTML = function(search, replace, mod) { - this.html(search, replace, mod); - }; - - /** - * ### GameWindow.html + * If a state parameter is given, all the input forms will be either + * disabled or enabled (and not toggled). * - * Replaces the innerHTML of the element with matching id or class name + * @param {string} id Optional. The id of the element container + * of the forms. Default: the whole page, including the frame document + * @param {boolean} disabled Optional. Forces all the inputs to be either + * disabled or enabled (not toggled) * - * @param {string|number} search Element id or className - * @param {string|number} replace The new value of the property innerHTML - * @param {string} mod Optional. A modifier defining how to use the - * search parameter. Values: + * @return {boolean} FALSE, if the method could not be executed * - * - 'id': replaces at most one element with the same id (default) - * - 'className': replaces all elements with same class name - * - 'g': replaces globally, both by id and className + * @see GameWindow.getFrameDocument + * @see toggleInputs */ - GameWindow.prototype.html = function(search, replace, mod) { - var el, i, len; - - // Only process strings or numbers. - if ('string' !== typeof search && 'number' !== typeof search) { - throw new TypeError('GameWindow.setInnerHTML: search must be ' + - 'string or number. Found: ' + search + - " (replace = " + replace + ")"); - } - - // Only process strings or numbers. - if ('string' !== typeof replace && 'number' !== typeof replace) { - throw new TypeError('GameWindow.setInnerHTML: replace must be ' + - 'string or number. Found: ' + replace + - " (search = " + search + ")"); + GameWindow.prototype.toggleInputs = function(id, disabled) { + var container; + if (!document.getElementsByTagName) { + node.err(G + 'toggleInputs: getElementsByTagName not found'); + return false; } - - if ('undefined' === typeof mod) { - mod = 'id'; + if (id && 'string' === typeof id) { + throw new Error(G + 'toggleInputs: id must be string or ' + + 'undefined. Found: ' + id); } - else if ('string' === typeof mod) { - if (mod !== 'g' && mod !== 'id' && mod !== 'className') { - throw new Error('GameWindow.setInnerHTML: invalid ' + - 'mod value: ' + mod + - " (search = " + search + ")"); + if (id) { + container = this.gid(id); + if (!container) { + throw new Error(G + 'toggleInputs: no elements found with id ' + + id); } + toggleInputs(disabled, container); } else { - throw new TypeError('GameWindow.setInnerHTML: mod must be ' + - 'string or undefined. Found: ' + mod + - " (search = " + search + ")"); - } - - if (mod === 'id' || mod === 'g') { - // Look by id. - el = W.getElementById(search); - if (el && el.className !== search) el.innerHTML = replace; - } - - if (mod === 'className' || mod === 'g') { - // Look by class name. - el = W.getElementsByClassName(search); - len = el.length; - if (len) { - i = -1; - for ( ; ++i < len ; ) { - el[i].innerHTML = replace; - } - } + // The whole page. + toggleInputs(disabled); + container = this.getFrameDocument(); + // If there is a frame, apply it there too. + if (container) toggleInputs(disabled, container); } + return true; }; /** @@ -4580,7 +4638,7 @@ */ GameWindow.prototype.hide = function(idOrObj) { var el; - el = getElement(idOrObj, 'GameWindow.hide'); + el = getElement(idOrObj, 'hide'); if (el) { el.style.display = 'none'; W.adjustFrameHeight(0, 0); @@ -4608,10 +4666,10 @@ var el; display = display || ''; if ('string' !== typeof display) { - throw new TypeError('GameWindow.show: display must be ' + + throw new TypeError(G + 'show: display must be ' + 'string or undefined. Found: ' + display); } - el = getElement(idOrObj, 'GameWindow.show'); + el = getElement(idOrObj, 'show'); if (el) { el.style.display = display; W.adjustFrameHeight(0, 0); @@ -4639,10 +4697,10 @@ var el; display = display || ''; if ('string' !== typeof display) { - throw new TypeError('GameWindow.toggle: display must ' + + throw new TypeError(G + 'toggle: display must ' + 'be string or undefined. Found: ' + display); } - el = getElement(idOrObj, 'GameWindow.toggle'); + el = getElement(idOrObj, 'toggle'); if (el) { if (el.style.display === 'none') el.style.display = display; else el.style.display = 'none'; @@ -4656,6 +4714,13 @@ /** * ### toggleInputs * + * Enable/disable inputs: 'button', 'select', 'textarea', 'input' + * + * @param {boolean} state Optional. True/false to enable/disable, undefined + * to toggle. + * @param {HTMLElement} container Optional. The element inside which + * toggling takes place. Default: `document`. + * * @api private */ function toggleInputs(state, container) { @@ -4686,26 +4751,48 @@ * * Gets the element or returns it * - * @param {string|HTMLElement} The id or the HTML element itself + * @param {string|HTMLElement} idOrObj The id or the HTML element itself + * @param {string|undefined} throwAs Optional. The name of the calling + * method used in the string of the error, or undefined to avoid + * throwing altogether. * - * @return {HTMLElement} The HTML Element + * @return {HTMLElement|undefined} The HTML Element or undefined if none + * is found and throwAs is falsy * - * @see GameWindow.getElementById + * @see GameWindow.gid * @api private */ - function getElement(idOrObj, prefix) { - var el; - if ('string' === typeof idOrObj) { - el = W.getElementById(idOrObj); - } - else if (J.isElement(idOrObj)) { - el = idOrObj; - } - else { - throw new TypeError(prefix + ': idOrObj must be string ' + - ' or HTML Element. Found: ' + idOrObj); + function getElement(idOrObj, throwAs) { + if ('string' === typeof idOrObj) return W.gid(idOrObj); + if (J.isElement(idOrObj)) return idOrObj; + if (throwAs) { + throw new TypeError(G + throwAs + ': idOrObj must be string or ' + + 'HTML Element. Found: ' + idOrObj); } - return el; + } + + /** + * ### getDefaultRoot + * + * Tries to find a default root and returns it + * + * @param {string|HTMLElement} root Optional. The id of or the HTML + * element itself to be used as root. + * @param {string|undefined} throwAs Optional. The name of the calling + * method used in the string of the error, or undefined to avoid + * throwing altogether. + * + * @return {HTMLElement|undefined} The root HTML Element, or undefined + * if none is found and `throwAs` is falsy + * + * @see GameWindow.gid + * @api private + */ + function getDefaultRoot(root, throwAs) { + if (!root) root = W.getScreen(); + else root = getElement(root); + if (root) return root; + if (throwAs) throw new Error(G + throwAs + ': could not find root'); } })( diff --git a/build/nodegame-window.min.js b/build/nodegame-window.min.js index 2f06753..f50273a 100644 --- a/build/nodegame-window.min.js +++ b/build/nodegame-window.min.js @@ -1,6 +1,6 @@ /** * # GameWindow - * Copyright(c) 2021 Stefano Balietti + * Copyright(c) 2023 Stefano Balietti * MIT Licensed * * API to interface nodeGame with the browser window @@ -15,4 +15,4 @@ * * Depends on JSUS and nodegame-client. */ -(function(e,t){"use strict";function a(e,t){function r(){e.removeEventListener("load",r,!1),n.removeEventListener("load",r,!1),t&&setTimeout(function(){t()},120)}var n;n=e.contentWindow,e.addEventListener("load",r,!1),n.addEventListener("load",r,!1)}function f(e,t){function r(i){var s;s=J.getIFrameDocument(e);if(i.type==="load"||s.readyState==="complete")e.detachEvent("onreadystatechange",r),n.detachEvent("onload",r),t&&setTimeout(function(){t()},120)}var n;n=e.contentWindow,e.attachEvent("onreadystatechange",r),n.attachEvent("onload",r)}function l(e,t){W.isIE?f(e,t):a(e,t)}function c(){this.setStateLevel("UNINITIALIZED");if("undefined"==typeof e)throw new Error("GameWindow: no window found. Are you in a browser?");if("undefined"==typeof t)throw new Error("GameWindow: nodeGame not found");t.silly("node-window: loading..."),this.frameName=null,this.frameElement=null,this.frameWindow=null,this.frameDocument=null,this.frameRoot=null,this.headerElement=null,this.headerName=null,this.headerRoot=null,this.headerPosition=null,this.defaultHeaderPosition="top",this.conf={},this.uriChannel=null,this.areLoading=0,this.cacheSupported=null,this.directFrameDocumentAccess=null,this.cache={},this.currentURIs={},this.unprocessedUri=null,this.globalLibs=[],this.frameLibs={},this.uriPrefix=null,this.stateLevel=null,this.waitScreen=null,this.listenersAdded=null,this.screenState=t.constants.screenLevels.ACTIVE,this.styleElement=null,this.isIE=!!document.createElement("span").attachEvent,this.headerOffset=0,this.willResizeFrame=!1,this.addDefaultSetups(),this.addDefaultListeners(),setTimeout(function(){(function(e){e.length>=1&&(e[0].style.display="none")})(document.getElementsByTagName("noscript"))},1e3),this.init(c.defaults),t.silly("node-window: created.")}function h(e,t,n,r,i,s,o){var u,a;n=W.getElementById(r),u=W.getIFrameDocument(n).documentElement,i&&(u.innerHTML=e.cache[t].contents),r===e.frameName&&(e.frameWindow=n.contentWindow,e.frameDocument=e.getIFrameDocument(n),e.conf.rightClickDisabled&&J.disableRightClick(e.frameDocument),e.conf.noEscape&&(e.frameDocument.onkeydown=document.onkeydown)),e.styleElement=null,p(n),a=function(){v(n,e.globalLibs.concat(e.frameLibs.hasOwnProperty(t)?e.frameLibs[t]:[])),s&&(e.cache[t].contents=u.innerHTML),o(),b()},i?d(n,a):a()}function p(e){var t,n,r,i;n=W.getIFrameDocument(e),r=W.getElementsByClassName(n,"injectedlib","script");for(t=0;t=n.length&&r&&r()})}(o,a),e.frames[f].location.replace(o)},c.prototype.clearCache=function(){this.cache={}},c.prototype.getElementById=c.prototype.gid=function(e){var t,n;return n=this.getFrameDocument(),t=null,n&&n.getElementById&&(t=n.getElementById(e)),t||(t=document.getElementById(e)),t},c.prototype.getElementsByTagName=function(e){var t;return t=this.getFrameDocument(),t?t.getElementsByTagName(e):document.getElementsByTagName(e)},c.prototype.getElementsByClassName=function(e,t){var n;return n=this.getFrameDocument()||document,J.getElementsByClassName(n,e,t)},c.prototype.loadFrame=function(e,n,r){var i,s,o,u,a,f,p,d,v,g,b,w,E,S,x;if("string"!=typeof e)throw new TypeError("GameWindow.loadFrame: uri must be string. Found: "+e);if(n&&"function"!=typeof n)throw new TypeError("GameWindow.loadFrame: func must be function or undefined. Found: "+n);if(r&&"object"!=typeof r)throw new TypeError("GameWindow.loadFrame: opts must be object or undefined. Found: "+r);r=r||{},v=this.getFrame(),g=this.frameName;if(!v)throw new Error("GameWindow.loadFrame: no frame found");if(!g)throw new Error("GameWindow.loadFrame: frame has no name");this.setStateLevel("LOADING"),i=this,w=v.contentWindow,b=W.getIFrameDocument(v),S=b.readyState,S=S==="complete",s=c.defaults.cacheDefaults.loadCache,o=c.defaults.cacheDefaults.storeCacheNow,u=c.defaults.cacheDefaults.storeCacheLater;if(r.cache){if(r.cache.loadMode)if(r.cache.loadMode==="reload")s=!1;else{if(r.cache.loadMode!=="cache")throw new Error("GameWindow.loadFrame: unkown cache load mode: "+r.cache.loadMode);s=!0}if(r.cache.storeMode)if(r.cache.storeMode==="off")o=!1,u=!1;else if(r.cache.storeMode==="onLoad")o=!0,u=!1;else{if(r.cache.storeMode!=="onClose")throw new Error("GameWindow.loadFrame: unkown cache store mode: "+r.cache.storeMode);o=!1,u=!0}}if("undefined"!=typeof r.autoParse){if("object"!=typeof r.autoParse)throw new TypeError("GameWindow.loadFrame: opts.autoParse must be object or undefined. Found: "+r.autoParse);if("undefined"!=typeof r.autoParsePrefix){if("string"!=typeof r.autoParsePrefix)throw new TypeError("GameWindow.loadFrame: opts.autoParsePrefix must be string or undefined. Found: "+r.autoParsePrefix);p=r.autoParsePrefix}if("undefined"!=typeof r.autoParseMod){if("string"!=typeof r.autoParseMod)throw new TypeError("GameWindow.loadFrame: opts.autoParseMod must be string or undefined. Found: "+r.autoParseMod);d=r.autoParseMod}f=r.autoParse}a="undefined"==typeof r.scrollUp?!0:r.scrollUp,this.unprocessedUri=e;if(this.cacheSupported===null){this.preCacheTest(function(){i.loadFrame(e,n,r)});return}e=this.processUri(e),this.cacheSupported===!1?(o=!1,u=!1,s=!1):(x=this.currentURIs[g],this.cache.hasOwnProperty(x)&&this.cache[x].cacheOnClose&&(E=b.documentElement,this.cache[x].contents=E.innerHTML),this.cache.hasOwnProperty(e)||(this.cache[e]={contents:null,cacheOnClose:!1}),this.cache[e].cacheOnClose=u,this.cache[e].contents===null&&(s=!1)),this.currentURIs[g]=e,m(this,1),v.style.visibility="hidden",(!s||!S)&&l(v,function(){i.directFrameDocumentAccess===null&&y(i),h(i,e,v,g,s,o,function(){i.updateLoadFrameState(n,f,d,p,a)})}),s?S&&h(this,e,v,g,s,o,function(){i.updateLoadFrameState(n,f,d,p,a)}):w.location.replace(e),w.node=t},c.prototype.processUri=function(e){return e.charAt(0)!=="/"&&e.substr(0,7)!=="http://"&&(this.uriPrefix&&(e=this.uriPrefix+e),this.uriChannel&&(e=this.uriChannel+e)),e},c.prototype.updateLoadFrameState=function(n,r,i,s,u){var a,f;a=m(this,-1),a&&this.setStateLevel("LOADED"),n&&n.call(t.game),r&&this.searchReplace(r,i,s),u&&e.scrollTo&&e.scrollTo(0,0),t.events.ee.game.emit("FRAME_LOADED"),t.events.ee.stage.emit("FRAME_LOADED"),t.events.ee.step.emit("FRAME_LOADED"),a?(f=t.game.getStageLevel(),f===o&&t.emit("LOADED")):t.silly("game-window: "+this.areLoading+" frames "+"still loading.")},c.prototype.clearPageBody=function(){this.reset(),document.body.innerHTML=""},c.prototype.clearPage=function(){this.reset();try{document.documentElement.innerHTML=""}catch(e){this.removeChildrenFromNode(document.documentElement)}},c.prototype.setUriPrefix=function(e){if(e!==null&&"string"!=typeof e)throw new TypeError("GameWindow.setUriPrefix: uriPrefix must be string or null. Found: "+e);this.conf.uriPrefix=this.uriPrefix=e},c.prototype.setUriChannel=function(e){if("string"==typeof e)e.charAt(0)!=="/"&&(e="/"+e),e.charAt(e.length-1)!=="/"&&(e+="/");else if(e!==null)throw new TypeError("GameWindow.uriChannel: uriChannel must be string or null. Found: "+e);this.uriChannel=e},c.prototype.adjustFrameHeight=function(){var t,n;return n=function(t){var n,r,i;W.adjustHeaderOffset(),n=W.getFrame();if(!n||!n.contentWindow)return;if(!n.contentWindow.document.body){W.adjustFrameHeight(t,120);return}W.conf.adjustFrameHeight===!1?r="100vh":(r=e.innerHeight||e.clientHeight,i=n.contentWindow.document.body.offsetHeight,i+=60,W.headerPosition==="top"&&(i+=W.headerOffset),rDo not refresh the page!
Maximum Waiting Time: ",countdownResuming:e.countdownResumingText||"Resuming soon...",formatCountdown:function(e){var t;return t="",e=J.parseMilliseconds(e),e[2]&&(t+=e[2]+" min "),e[3]&&(t+=e[3]+" sec"),t||0}},this.lockedInputs=[],this.enable()}e.WaitScreen=l,l.version="0.10.0",l.description="Shows a waiting screen";var n,r;n=["button","select","textarea","input"],r=n.length,l.prototype.enable=function(){if(this.enabled)return;node.events.ee.game.on("REALLY_DONE",s),node.events.ee.game.on("STEPPING",o),node.events.ee.game.on("PLAYING",u),node.events.ee.game.on("PAUSED",a),node.events.ee.game.on("RESUMED",f),this.enabled=!0},l.prototype.disable=function(){if(!this.enabled)return;node.events.ee.game.off("REALLY_DONE",s),node.events.ee.game.off("STEPPING",o),node.events.ee.game.off("PLAYING",u),node.events.ee.game.off("PAUSED",a),node.events.ee.game.off("RESUMED",f),this.enabled=!1},l.prototype.lock=function(e,t){var n,r;r=this.defaultTexts,"undefined"==typeof e&&(e=r.locked),"undefined"==typeof document.getElementsByTagName&&node.warn("WaitScreen.lock: cannot lock inputs"),i(document),n=W.getFrameDocument(),n&&i(n),this.waitingDiv||(this.root||(this.root=W.getFrameRoot()||document.body),this.waitingDiv=W.add("div",this.root,this.id),this.contentDiv=W.add("div",this.waitingDiv,"ng_waitscreen-content-div")),this.waitingDiv.style.display==="none"&&(this.waitingDiv.style.display=""),this.contentDiv.innerHTML=e,this.displayCountdown&&t?(this.countdownDiv||(this.countdownDiv=W.add("div",this.waitingDiv,"ng_waitscreen-countdown-div"),this.countdownDiv.innerHTML=r.countdown,this.countdownSpan=W.add("span",this.countdownDiv,"ng_waitscreen-countdown-span")),this.countdown=t,this.countdownSpan.innerHTML=r.formatCountdown(t),this.countdownDiv.style.display="",this.countdownInterval=setInterval(function(){var e;e=W.waitScreen;if(!W.isScreenLocked()){clearInterval(e.countdownInterval);return}e.countdown-=1e3,e.countdown<0?(clearInterval(e.countdownInterval),e.countdownDiv.style.display="none",e.contentDiv.innerHTML=r.countdownResuming):e.countdownSpan.innerHTML=r.formatCountdown(e.countdown)},1e3)):this.countdownDiv&&(this.countdownDiv.style.display="none")},l.prototype.unlock=function(){var e,t;this.waitingDiv&&this.waitingDiv.style.display===""&&(this.waitingDiv.style.display="none"),this.countdownInterval&&clearInterval(this.countdownInterval);try{t=this.lockedInputs.length;for(e=-1;++e2&&(t=arguments[1],n=arguments[2]);if("undefined"==typeof n)n="ng_replace_";else if(null===n)n="";else if("string"!=typeof n)throw new TypeError("GameWindow.searchReplace: prefix must be string, null or undefined. Found: "+n);e=arguments[0];if(J.isArray(e)){s=-1,i=e.length;for(;++sn.dt)return 1}else{if(e.dtn.dt)return-1}if(e.dt===n.dt){if("undefined"==typeof e.dd)return-1;if("undefined"==typeof n.dd)return 1;if(e.ddn.dd)return 1;if(e.nddbidn.nddbid)return-1}return 0},this.DL=e.list||document.createElement(this.FIRST_LEVEL),this.DL.id=e.id||this.id,e.className&&(this.DL.className=e.className),this.options.title&&this.DL.appendChild(document.createTextNode(e.title)),this.htmlRenderer=new r(e.render)},s.prototype._add=function(e){if(!e)return;this.insert(e),this.auto_update&&this.parse()},s.prototype.addDT=function(e,t){if("undefined"==typeof e)return;this.last_dt++,t="undefined"!=typeof t?t:this.last_dt,this.last_dd=0;var n=new o({dt:t,content:e});return this._add(n)},s.prototype.addDD=function(e,t,n){if("undefined"==typeof e)return;t="undefined"!=typeof t?t:this.last_dt,n="undefined"!=typeof n?n:this.last_dd++;var r=new o({dt:t,dd:n,content:e});return this._add(r)},s.prototype.parse=function(){this.sort();var e=null,t=null,n=function(){var n=document.createElement(this.SECOND_LEVEL);return this.DL.appendChild(n),t=null,e=n,n},r=function(){var e=document.createElement(this.THIRD_LEVEL);return this.DL.appendChild(e),e};if(this.DL){while(this.DL.hasChildNodes())this.DL.removeChild(this.DL.firstChild);this.options.title&&this.DL.appendChild(document.createTextNode(this.options.title))}for(var i=0;ithis.pointers[e])this.pointers[e]=t;return this.pointers[e]},u.prototype.addMultiple=function(e,t,n,r){var i,s,o,u;f("addMultiple",e,n,r);if(t&&"string"!=typeof t||t&&"undefined"==typeof this.pointers[t])throw new TypeError("Table.addMultiple: dim must be a valid dimension (x or y) or undefined.");t=t||"x",n=this.getCurrPointer("x",n),r=this.getNextPointer("y",r),n="undefined"!=typeof n?n:this.pointers.x===null?0:this.pointers.x,r="undefined"!=typeof r?r:this.pointers.y===null?0:this.pointers.y,J.isArray(e)||(e=[e]),i=-1,s=e.length;for(;++ih+1){d=this.db[u].y-(h+1);for(a=0;a=1&&(e[0].style.display="none")})(document.getElementsByTagName("noscript"))},1e3),this.init(c.defaults),t.silly("node-window: created.")}function h(e,t,n,r,i,s,o){var u,a;n=W.getElementById(r),u=W.getIFrameDocument(n).documentElement,i&&(u.innerHTML=e.cache[t].contents),r===e.frameName&&(e.frameWindow=n.contentWindow,e.frameDocument=e.getIFrameDocument(n),e.conf.rightClickDisabled&&J.disableRightClick(e.frameDocument),e.conf.noEscape&&(e.frameDocument.onkeydown=document.onkeydown)),e.styleElement=null,p(n),a=function(){v(n,e.globalLibs.concat(e.frameLibs.hasOwnProperty(t)?e.frameLibs[t]:[])),s&&(e.cache[t].contents=u.innerHTML),o(),b()},i?d(n,a):a()}function p(e){var t,n,r,i;n=W.getIFrameDocument(e),r=W.getElementsByClassName(n,"injectedlib","script");for(t=0;t=n.length&&r&&r()})}(o,a),e.frames[f].location.replace(o)},c.prototype.clearCache=function(){this.cache={}},c.prototype.getElementById=c.prototype.gid=function(e){var t,n;return n=this.getFrameDocument(),t=null,n&&n.getElementById&&(t=n.getElementById(e)),t||(t=document.getElementById(e)),t},c.prototype.getElementsByTagName=function(e){var t;return t=this.getFrameDocument(),t?t.getElementsByTagName(e):document.getElementsByTagName(e)},c.prototype.getElementsByClassName=function(e,t){var n;return n=this.getFrameDocument()||document,J.getElementsByClassName(n,e,t)},c.prototype.loadFrame=function(e,n,r){var i,s,o,u,a,f,p,d,v,g,b,w,E;if("string"!=typeof e)throw new TypeError("GameWindow.loadFrame: uri must be string. Found: "+e);if(n&&"function"!=typeof n)throw new TypeError("GameWindow.loadFrame: func must be function or undefined. Found: "+n);if(r&&"object"!=typeof r)throw new TypeError("GameWindow.loadFrame: opts must be object or undefined. Found: "+r);r=r||{},p=this.getFrame(),d=this.frameName;if(!p)throw new Error("GameWindow.loadFrame: no frame found");if(!d)throw new Error("GameWindow.loadFrame: frame has no name");this.setStateLevel("LOADING"),i=this,g=p.contentWindow,v=W.getIFrameDocument(p),w=v.readyState,w=w==="complete",s=c.defaults.cacheDefaults.loadCache,u=c.defaults.cacheDefaults.storeCacheNow,a=c.defaults.cacheDefaults.storeCacheLater;if(r.cache){if(r.cache.loadMode)if(r.cache.loadMode==="reload")s=!1;else{if(r.cache.loadMode!=="cache")throw new Error("GameWindow.loadFrame: unkown cache load mode: "+r.cache.loadMode);s=!0,o=!0}if(r.cache.storeMode)if(r.cache.storeMode==="off")u=!1,a=!1;else{if(r.cache.storeMode==="onLoad")u=!0,a=!1;else{if(r.cache.storeMode!=="onClose")throw new Error("GameWindow.loadFrame: unkown cache store mode: "+r.cache.storeMode);u=!1,a=!0}o=!0}}f="undefined"==typeof r.scrollUp?!0:r.scrollUp,this.unprocessedUri=e,o=!0;if(o&&this.cacheSupported===null){this.preCacheTest(function(){i.loadFrame(e,n,r)});return}e=this.processUri(e),this.cacheSupported===!1?(u=!1,a=!1,s=!1):(E=this.currentURIs[d],this.cache.hasOwnProperty(E)&&this.cache[E].cacheOnClose&&(b=v.documentElement,this.cache[E].contents=b.innerHTML),this.cache.hasOwnProperty(e)||(this.cache[e]={contents:null,cacheOnClose:!1}),this.cache[e].cacheOnClose=a,this.cache[e].contents===null&&(s=!1)),this.currentURIs[d]=e,m(this,1),p.style.visibility="hidden",(!s||!w)&&l(p,function(){i.directFrameDocumentAccess===null&&y(i),h(i,e,p,d,s,u,function(){i.updateLoadFrameState(n,f)})}),s?w&&h(this,e,p,d,s,u,function(){i.updateLoadFrameState(n,f)}):g.location.replace(e),g.node=t},c.prototype.processUri=function(e){return e.charAt(0)!=="/"&&e.substr(0,7)!=="http://"&&(this.uriPrefix&&(e=this.uriPrefix+e),this.uriChannel&&(e=this.uriChannel+e)),e},c.prototype.updateLoadFrameState=function(n,r){var i,s,u,a,f;a=m(this,-1),a&&this.setStateLevel("LOADED"),n&&n.call(t.game),r&&e.scrollTo&&e.scrollTo(0,0),i=t.game.getProperty("css"),i&&W.cssRule(i),s=t.game.getProperty("html"),s&&W.write(s),u=t.game.getProperty("replace"),u&&W.searchReplace(u,"g",""),t.events.ee.game.emit("FRAME_LOADED"),t.events.ee.stage.emit("FRAME_LOADED"),t.events.ee.step.emit("FRAME_LOADED"),a?(f=t.game.getStageLevel(),f===o&&t.emit("LOADED")):t.silly("game-window: "+this.areLoading+" frames "+"still loading.")},c.prototype.clearPageBody=function(){this.reset(),document.body.innerHTML=""},c.prototype.clearPage=function(){this.reset();try{document.documentElement.innerHTML=""}catch(e){this.removeChildrenFromNode(document.documentElement)}},c.prototype.setUriPrefix=function(e){if(e!==null&&"string"!=typeof e)throw new TypeError("GameWindow.setUriPrefix: uriPrefix must be string or null. Found: "+e);this.conf.uriPrefix=this.uriPrefix=e},c.prototype.setUriChannel=function(e){if("string"==typeof e)e.charAt(0)!=="/"&&(e="/"+e),e.charAt(e.length-1)!=="/"&&(e+="/");else if(e!==null)throw new TypeError("GameWindow.uriChannel: uriChannel must be string or null. Found: "+e);this.uriChannel=e},c.prototype.adjustFrameHeight=function(){var t,n;return n=function(t){var n,r,i;W.adjustHeaderOffset(),n=W.getFrame();if(!n||!n.contentWindow)return;if(!n.contentWindow.document.body){W.adjustFrameHeight(t,120);return}W.conf.adjustFrameHeight===!1?r="100vh":(r=e.innerHeight||e.clientHeight,i=n.contentWindow.document.body.offsetHeight,i+=60,W.headerPosition==="top"&&(i+=W.headerOffset),rDo not refresh the page!
Maximum Waiting Time: ",countdownResuming:e.countdownResumingText||"Resuming soon...",formatCountdown:function(e){var t;return t="",e=J.parseMilliseconds(e),e[2]&&(t+=e[2]+" min "),e[3]&&(t+=e[3]+" sec"),t||0}},this.lockedInputs=[],this.enable()}e.WaitScreen=l,l.version="0.10.0",l.description="Shows a waiting screen";var n,r;n=["button","select","textarea","input"],r=n.length,l.prototype.enable=function(){if(this.enabled)return;node.events.ee.game.on("REALLY_DONE",s),node.events.ee.game.on("STEPPING",o),node.events.ee.game.on("PLAYING",u),node.events.ee.game.on("PAUSED",a),node.events.ee.game.on("RESUMED",f),this.enabled=!0},l.prototype.disable=function(){if(!this.enabled)return;node.events.ee.game.off("REALLY_DONE",s),node.events.ee.game.off("STEPPING",o),node.events.ee.game.off("PLAYING",u),node.events.ee.game.off("PAUSED",a),node.events.ee.game.off("RESUMED",f),this.enabled=!1},l.prototype.lock=function(e,t){var n,r;r=this.defaultTexts,"undefined"==typeof e&&(e=r.locked),"undefined"==typeof document.getElementsByTagName&&node.warn("WaitScreen.lock: cannot lock inputs"),i(document),n=W.getFrameDocument(),n&&i(n),this.waitingDiv||(this.root||(this.root=W.getFrameRoot()||document.body),this.waitingDiv=W.add("div",this.root,this.id),this.contentDiv=W.add("div",this.waitingDiv,"ng_waitscreen-content-div")),this.waitingDiv.style.display==="none"&&(this.waitingDiv.style.display=""),this.contentDiv.innerHTML=e,this.displayCountdown&&t?(this.countdownDiv||(this.countdownDiv=W.add("div",this.waitingDiv,"ng_waitscreen-countdown-div"),this.countdownDiv.innerHTML=r.countdown,this.countdownSpan=W.add("span",this.countdownDiv,"ng_waitscreen-countdown-span")),this.countdown=t,this.countdownSpan.innerHTML=r.formatCountdown(t),this.countdownDiv.style.display="",this.countdownInterval=setInterval(function(){var e;e=W.waitScreen;if(!W.isScreenLocked()){clearInterval(e.countdownInterval);return}e.countdown-=1e3,e.countdown<0?(clearInterval(e.countdownInterval),e.countdownDiv.style.display="none",e.contentDiv.innerHTML=r.countdownResuming):e.countdownSpan.innerHTML=r.formatCountdown(e.countdown)},1e3)):this.countdownDiv&&(this.countdownDiv.style.display="none")},l.prototype.unlock=function(){var e,t;this.waitingDiv&&this.waitingDiv.style.display===""&&(this.waitingDiv.style.display="none"),this.countdownInterval&&clearInterval(this.countdownInterval);try{t=this.lockedInputs.length;for(e=-1;++e2&&(n=arguments[1],r=arguments[2]);if("undefined"==typeof r)r="ng_replace_";else if(null===r)r="";else if("string"!=typeof r)throw new TypeError(i+"searchReplace: prefix must be string, "+"null or undefined. Found: "+r);e=arguments[0];if(J.isArray(e)){u=-1,o=e.length;for(;++un.dt)return 1}else{if(e.dtn.dt)return-1}if(e.dt===n.dt){if("undefined"==typeof e.dd)return-1;if("undefined"==typeof n.dd)return 1;if(e.ddn.dd)return 1;if(e.nddbidn.nddbid)return-1}return 0},this.DL=e.list||document.createElement(this.FIRST_LEVEL),this.DL.id=e.id||this.id,e.className&&(this.DL.className=e.className),this.options.title&&this.DL.appendChild(document.createTextNode(e.title)),this.htmlRenderer=new r(e.render)},s.prototype._add=function(e){if(!e)return;this.insert(e),this.auto_update&&this.parse()},s.prototype.addDT=function(e,t){if("undefined"==typeof e)return;this.last_dt++,t="undefined"!=typeof t?t:this.last_dt,this.last_dd=0;var n=new o({dt:t,content:e});return this._add(n)},s.prototype.addDD=function(e,t,n){if("undefined"==typeof e)return;t="undefined"!=typeof t?t:this.last_dt,n="undefined"!=typeof n?n:this.last_dd++;var r=new o({dt:t,dd:n,content:e});return this._add(r)},s.prototype.parse=function(){this.sort();var e=null,t=null,n=function(){var n=document.createElement(this.SECOND_LEVEL);return this.DL.appendChild(n),t=null,e=n,n},r=function(){var e=document.createElement(this.THIRD_LEVEL);return this.DL.appendChild(e),e};if(this.DL){while(this.DL.hasChildNodes())this.DL.removeChild(this.DL.firstChild);this.options.title&&this.DL.appendChild(document.createTextNode(this.options.title))}for(var i=0;ithis.pointers[e])this.pointers[e]=t;return this.pointers[e]},u.prototype.addMultiple=function(e,t,n,r){var i,s,o,u;f("addMultiple",e,n,r);if(t&&"string"!=typeof t||t&&"undefined"==typeof this.pointers[t])throw new TypeError("Table.addMultiple: dim must be a valid dimension (x or y) or undefined.");t=t||"x",n=this.getCurrPointer("x",n),r=this.getNextPointer("y",r),n="undefined"!=typeof n?n:this.pointers.x===null?0:this.pointers.x,r="undefined"!=typeof r?r:this.pointers.y===null?0:this.pointers.y,J.isArray(e)||(e=[e]),i=-1,s=e.length;for(;++ih+1){d=this.db[u].y-(h+1);for(a=0;a + * Copyright(c) 2023 Stefano Balietti * MIT Licensed * * API to interface nodeGame with the browser window @@ -1346,6 +1346,7 @@ */ GameWindow.prototype.preCacheTest = function(cb, uri) { var iframe, iframeName; + uri = uri || '/pages/testpage.htm'; if ('string' !== typeof uri) { throw new TypeError('GameWindow.precacheTest: uri must string ' + @@ -1372,8 +1373,15 @@ catch(e) { W.cacheSupported = false; } + // It's possible that it was already removed if two calls + // to preCacheTest are made one after the other. + try { + document.body.removeChild(iframe); + } + catch(e) { + node.warn('W.preCacheTest: iframe already removed'); + } - document.body.removeChild(iframe); if (cb) cb(); }); }; @@ -1599,9 +1607,8 @@ GameWindow.prototype.loadFrame = function(uri, func, opts) { var that; var loadCache; - var storeCacheNow, storeCacheLater; + var shouldTestCache, storeCacheNow, storeCacheLater; var scrollUp; - var autoParse, autoParsePrefix, autoParseMod; var iframe, iframeName, iframeDocument, iframeWindow; var frameDocumentElement, frameReady; var lastURI; @@ -1658,6 +1665,7 @@ } else if (opts.cache.loadMode === 'cache') { loadCache = true; + shouldTestCache = true; } else { throw new Error('GameWindow.loadFrame: unkown cache ' + @@ -1669,48 +1677,24 @@ storeCacheNow = false; storeCacheLater = false; } - else if (opts.cache.storeMode === 'onLoad') { - storeCacheNow = true; - storeCacheLater = false; - } - else if (opts.cache.storeMode === 'onClose') { - storeCacheNow = false; - storeCacheLater = true; - } else { - throw new Error('GameWindow.loadFrame: unkown cache ' + - 'store mode: ' + opts.cache.storeMode); - } - } - } - // Parsing options. + if (opts.cache.storeMode === 'onLoad') { + storeCacheNow = true; + storeCacheLater = false; + } + else if (opts.cache.storeMode === 'onClose') { + storeCacheNow = false; + storeCacheLater = true; + } + else { + throw new Error('GameWindow.loadFrame: unkown cache ' + + 'store mode: ' + opts.cache.storeMode); + } - if ('undefined' !== typeof opts.autoParse) { - if ('object' !== typeof opts.autoParse) { - throw new TypeError('GameWindow.loadFrame: opts.autoParse ' + - 'must be object or undefined. Found: ' + - opts.autoParse); - } - if ('undefined' !== typeof opts.autoParsePrefix) { - if ('string' !== typeof opts.autoParsePrefix) { - throw new TypeError('GameWindow.loadFrame: opts.' + - 'autoParsePrefix must be string ' + - 'or undefined. Found: ' + - opts.autoParsePrefix); - } - autoParsePrefix = opts.autoParsePrefix; - } - if ('undefined' !== typeof opts.autoParseMod) { - if ('string' !== typeof opts.autoParseMod) { - throw new TypeError('GameWindow.loadFrame: opts.' + - 'autoParseMod must be string ' + - 'or undefined. Found: ' + - opts.autoParseMod); + shouldTestCache = true; } - autoParseMod = opts.autoParseMod; } - autoParse = opts.autoParse; } // Scroll Up. @@ -1720,7 +1704,8 @@ // Store unprocessed uri parameter. this.unprocessedUri = uri; - if (this.cacheSupported === null) { + shouldTestCache = true; + if (shouldTestCache && this.cacheSupported === null) { this.preCacheTest(function() { that.loadFrame(uri, func, opts); }); @@ -1789,13 +1774,9 @@ handleFrameLoad(that, uri, iframe, iframeName, loadCache, storeCacheNow, function() { - // Executes callback, autoParses, + // Executes callback, css, replace, html, // and updates GameWindow state. - that.updateLoadFrameState(func, - autoParse, - autoParseMod, - autoParsePrefix, - scrollUp); + that.updateLoadFrameState(func, scrollUp); }); }); } @@ -1811,13 +1792,9 @@ handleFrameLoad(this, uri, iframe, iframeName, loadCache, storeCacheNow, function() { - // Executes callback + // Executes callback, css, replace, html, // and updates GameWindow state. - that.updateLoadFrameState(func, - autoParse, - autoParseMod, - autoParsePrefix, - scrollUp); + that.updateLoadFrameState(func, scrollUp); }); } } @@ -1860,14 +1837,10 @@ * - decrements the counter of loading iframes * - executes a given callback function * - auto parses the elements specified (if any) + * - updates UI, e.g., scroll-up or adds CSS * - set the window state as loaded (eventually) * * @param {function} func Optional. A callback function - * @param {object} autoParse Optional. An object containing elements - * to replace in the HTML DOM. - * @param {string} autoParseMod Optional. Modifier for search and replace - * @param {string} autoParsePrefix Optional. Custom prefix to add to the - * keys of the elements in autoParse object * @param {boolean} scrollUp Optional. If TRUE, scrolls the page to the, * top (if window.scrollTo is defined). Default: FALSE. * @@ -1877,20 +1850,23 @@ * @emit FRAME_LOADED * @emit LOADED */ - GameWindow.prototype.updateLoadFrameState = function(func, autoParse, - autoParseMod, - autoParsePrefix, - scrollUp) { + GameWindow.prototype.updateLoadFrameState = function(func, scrollUp) { - var loaded, stageLevel; + var css, html, replace, loaded, stageLevel; loaded = updateAreLoading(this, -1); if (loaded) this.setStateLevel('LOADED'); if (func) func.call(node.game); - if (autoParse) { - this.searchReplace(autoParse, autoParseMod, autoParsePrefix); - } if (scrollUp && window.scrollTo) window.scrollTo(0,0); + css = node.game.getProperty('css'); + if (css) W.cssRule(css); + + html = node.game.getProperty('html'); + if (html) W.write(html); + + replace = node.game.getProperty('replace'); + if (replace) W.searchReplace(replace, 'g', ''); + // ng event emitter is not used. node.events.ee.game.emit('FRAME_LOADED'); node.events.ee.stage.emit('FRAME_LOADED'); @@ -2159,6 +2135,40 @@ }; + /** + * ### GameWindow.cssRule + * + * Add a css rule to the page + * + * @param {string} rule The css rule + * @param {boolean} clear Optional. TRUE to clear all previous rules + * added with this method to the page + * + * @return {Element} The HTML style element where the rules were added + * + * @see handleFrameLoad + */ + GameWindow.prototype.cssRule = function(rule, clear) { + var root; + if ('string' !== typeof rule) { + throw new TypeError(G + 'cssRule: style property must be ' + + 'string. Found: ' + rule); + } + if (!this.styleElement) { + root = W.getFrameDocument() || window.document; + this.styleElement = W.append('style', root.head, { + type: 'text/css', + id: 'ng_style' + }); + } + else if (clear) { + this.styleElement.innerHTML = ''; + } + this.styleElement.innerHTML += rule; + return this.styleElement; + }; + + // ## Helper functions /** diff --git a/lib/modules/extra.js b/lib/modules/extra.js index f0558ff..78310ef 100644 --- a/lib/modules/extra.js +++ b/lib/modules/extra.js @@ -1,6 +1,6 @@ /** * # extra - * Copyright(c) 2022 Stefano Balietti + * Copyright(c) 2023 Stefano Balietti * MIT Licensed * * GameWindow extras @@ -14,15 +14,20 @@ var GameWindow = node.GameWindow; var DOM = J.require('DOM'); + var G = 'GameWindow.'; + + // ### BASIC. + /** * ### GameWindow.getScreen * * Returns the "screen" of the game * - * i.e. the innermost element inside which to display content + * i.e., the innermost element inside which to display content * * In the following order the screen can be: * + * - the element with id "container" (presumably inside the iframe) * - the body element of the iframe * - the document element of the iframe * - the body element of the document @@ -32,45 +37,43 @@ */ GameWindow.prototype.getScreen = function() { var el; - el = this.getFrameDocument(); - if (el) el = el.body || el; - else el = document.body || document.lastElementChild; + el = this.gid('container'); + if (!el) { + el = this.getFrameDocument(); + if (el) el = el.body || el; + else el = document.body || document.lastElementChild; + } return el; }; /** - * ### GameWindow.cssRule + * ### GameWindow.uniqueId|generateUniqueId * - * Add a css rule to the page + * Generates a unique id * - * @param {string} rule The css rule - * @param {boolean} clear Optional. TRUE to clear all previous rules - * added with this method to the page + * Overrides JSUS.DOM.generateUniqueId. + * + * @param {string} prefix Optional. The prefix to use * - * @return {Element} The HTML style element where the rules were added + * @return {string} The generated id * - * @see handleFrameLoad + * @experimental + * TODO: it is not always working fine. */ - GameWindow.prototype.cssRule = function(rule, clear) { - var root; - if ('string' !== typeof rule) { - throw new TypeError('Game.execStep: style property must be ' + - 'string. Found: ' + rule); - } - if (!this.styleElement) { - root = W.getFrameDocument() || window.document; - this.styleElement = W.append('style', root.head, { - type: 'text/css', - id: 'ng_style' - }); - } - else if (clear) { - this.styleElement.innerHTML = ''; - } - this.styleElement.innerHTML += rule; - return this.styleElement; + GameWindow.prototype.uniqueId = + GameWindow.prototype.generateUniqueId = function(prefix) { + var id, found; + id = '' + (prefix || J.randomInt(0, 1000)); + found = this.gid(id); + while (found) { + id = '' + prefix + '_' + J.randomInt(0, 1000); + found = this.gid(id); + } + return id; }; + // ### WRITE TO SCREEN. + /** * ### GameWindow.write * @@ -84,17 +87,13 @@ * * @return {string|object} The content written * + * @see JSUS.write * @see GameWindow.writeln + * @see getDefaultRoot */ GameWindow.prototype.write = function(text, root) { - if ('string' === typeof root) root = this.getElementById(root); - else if (!root) root = this.getScreen(); - - if (!root) { - throw new - Error('GameWindow.write: could not determine where to write'); - } - return DOM.write(root, text); + root = getDefaultRoot(root, 'write'); + return DOM.write2(root, text); }; /** @@ -110,99 +109,253 @@ * * @return {string|object} The content written * + * @see JSUS.writeln * @see GameWindow.write + * @see getDefaultRoot */ GameWindow.prototype.writeln = function(text, root, br) { - if ('string' === typeof root) root = this.getElementById(root); - else if (!root) root = this.getScreen(); + root = getDefaultRoot(root, 'writeln'); + return DOM.writeln2(root, text, br); + }; - if (!root) { - throw new Error('GameWindow.writeln: ' + - 'could not determine where to write'); - } - return DOM.writeln(root, text, br); + /** + * ### DOM.sprintf + * + * Builds up a decorated HTML text element + * + * Performs string substitution from an args object where the first + * character of the key bears the following semantic: + * + * - '@': variable substitution with escaping + * - '!': variable substitution without variable escaping + * - '%': wraps a portion of string into a _span_ element to which is + * possible to associate a css class or id. Alternatively, + * it also possible to add in-line style. E.g.: + * + * ```javascript + * sprintf('%sImportant!%s An error has occurred: %pre@err%pre', { + * '%pre': { + * style: 'font-size: 12px; font-family: courier;' + * }, + * '%s': { + * id: 'myId', + * 'class': 'myClass', + * }, + * '@err': 'file not found', + * }, document.body); + * ``` + * + * Special span elements are %strong and %em, which add + * respectively a _strong_ and _em_ tag instead of the default + * _span_ tag. They cannot be styled. + * + * @param {string} string A text to transform + * @param {object} args Optional. An object containing string + * transformations + * @param {Element} root Optional. An HTML element to which append the + * string. Defaults, a new _span_ element + * + * @return {Element} The root element. + */ + GameWindow.prototype.sprintf = function(string, args, root) { + if (!root) root = getDefaultRoot(root, 'sprintf'); + return DOM.sprintf(string, args, root); }; /** - * ### GameWindow.generateUniqueId + * ### GameWindo.add|append * - * Generates a unique id + * Creates and append an element with specified attributes to a root * - * Overrides JSUS.DOM.generateUniqueId. + * @param {string} name The name of the HTML tag + * @param {HTMLElement} root The root element to which the new element + * will be appended + * @param {object|string} options Optional. Object containing + * attributes for the element and rules about how to insert it relative + * to root. Available options: insertAfter, insertBefore (default: + * child of root). If string, it is the id of the element. Examples: * - * @param {string} prefix Optional. The prefix to use * - * @return {string} The generated id - * - * @experimental - * TODO: it is not always working fine. + * @see getDefaultRoot */ - GameWindow.prototype.generateUniqueId = function(prefix) { - var id, found; - - id = '' + (prefix || J.randomInt(0, 1000)); - found = this.getElementById(id); - - while (found) { - id = '' + prefix + '_' + J.randomInt(0, 1000); - found = this.getElementById(id); - } - return id; + GameWindow.prototype.add = + GameWindow.prototype.append = function(el, root, opts) { + if (!root) root = getDefaultRoot(root, 'add'); + return DOM.add(el, root, opts); }; /** - * ### GameWindow.toggleInputs + * ### GameWindow.searchReplace * - * Enables / disables the input forms + * Replaces the innerHTML of the element/s with matching id or class name * - * If an id is provided, only input elements that are children - * of the element with the specified id are toggled. + * It iterates through each element and passes it to + * `GameWindow.setInnerHTML`. * - * If id is not given, it toggles the input elements on the whole page, - * including the frame document, if found. + * If elements is array, each item in the array must be of the type: * - * If a state parameter is given, all the input forms will be either - * disabled or enabled (and not toggled). + * ```javascript * - * @param {string} id Optional. The id of the element container - * of the forms. Default: the whole page, including the frame document - * @param {boolean} disabled Optional. Forces all the inputs to be either - * disabled or enabled (not toggled) + * { search: 'key', replace: 'value' } * - * @return {boolean} FALSE, if the method could not be executed + * // or * - * @see GameWindow.getFrameDocument - * @see toggleInputs + * { search: 'key', replace: 'value', mod: 'id' } + * ``` + * + * If elements is object, it must be of the type: + * + * ```javascript + * + * { + * search1: value1, search2: value 2 // etc. + * } + * ``` + * + * It accepts a variable number of input parameters. The first is always + * _elements_. If there are 2 input parameters, the second is _prefix_, + * while if there are 3 input parameters, the second is _mod_ and the third + * is _prefix_. + * + * @param {object|array} Elements to search and replace + * @param {string} mod Optional. Modifier passed to GameWindow.setInnerHTML + * @param {string} prefix Optional. Prefix added to the search string. + * Default: 'ng_replace_', null or '' equals no prefix. + * + * @see GameWindow.setInnerHTML */ - GameWindow.prototype.toggleInputs = function(id, disabled) { - var container; - if (!document.getElementsByTagName) { - node.err( - 'GameWindow.toggleInputs: getElementsByTagName not found'); - return false; + GameWindow.prototype.searchReplace = function() { + var elements, mod, prefix; + var name, len, i, el, rep; + + if (arguments.length === 2) { + mod = 'g'; + prefix = arguments[1]; } - if (id && 'string' === typeof id) { - throw new Error('GameWindow.toggleInputs: id must be string or ' + - 'undefined. Found: ' + id); + else if (arguments.length > 2) { + mod = arguments[1]; + prefix = arguments[2]; } - if (id) { - container = this.getElementById(id); - if (!container) { - throw new Error('GameWindow.toggleInputs: no elements found ' + - 'with id ' + id); + + if ('undefined' === typeof prefix) { + prefix = 'ng_replace_'; + } + else if (null === prefix) { + prefix = ''; + } + else if ('string' !== typeof prefix) { + throw new TypeError(G + 'searchReplace: prefix must be string, ' + + 'null or undefined. Found: ' + prefix); + } + + elements = arguments[0]; + if (J.isArray(elements)) { + i = -1, len = elements.length; + for ( ; ++i < len ; ) { + el = elements[i].search; + if ('string' !== typeof el && 'number' !== typeof el) { + continue; + } + rep = elements[i].replace; + if ('string' !== typeof rep && 'number' !== typeof rep) { + continue; + } + + this.setInnerHTML(prefix + el, + elements[i].replace, + elements[i].mod || mod); + } + + } + else if ('object' === typeof elements) { + for (name in elements) { + if (elements.hasOwnProperty(name)) { + el = elements[name]; + if ('string' !== typeof el && 'number' !== typeof el) { + node.warn(G + 'searchReplace: replace for key ' + name + + ' is invalid. Found: ' + el); + continue; + } + this.setInnerHTML(prefix + name, el, mod); + } } - toggleInputs(disabled, container); } else { - // The whole page. - toggleInputs(disabled); - container = this.getFrameDocument(); - // If there is a frame, apply it there too. - if (container) toggleInputs(disabled, container); + throw new TypeError(G + 'setInnerHTML: elements must be ' + + 'object or arrray. Found: ' + elements); + } + + }; + + /** + * ### GameWindow.html|setInnerHTML + * + * Replaces the innerHTML of the element with matching id or class name + * + * @param {string|number} search Element id or className + * @param {string|number} replace The new value of the property innerHTML + * @param {string} mod Optional. A modifier defining how to use the + * search parameter. Values: + * + * - 'id': replaces at most one element with the same id (default) + * - 'className': replaces all elements with same class name + * - 'g': replaces globally, both by id and className + */ + GameWindow.prototype.setInnerHTML = + GameWindow.prototype.html = function(search, replace, mod) { + var el, i, len; + + // Only process strings or numbers. + if ('string' !== typeof search && 'number' !== typeof search) { + throw new TypeError(G + 'setInnerHTML: search must be ' + + 'string or number. Found: ' + search + + " (replace = " + replace + ")"); + } + + // Only process strings or numbers. + if ('string' !== typeof replace && 'number' !== typeof replace) { + throw new TypeError(G + 'setInnerHTML: replace must be ' + + 'string or number. Found: ' + replace + + " (search = " + search + ")"); + } + + if ('undefined' === typeof mod) { + mod = 'id'; + } + else if ('string' === typeof mod) { + if (mod !== 'g' && mod !== 'id' && mod !== 'className') { + throw new Error(G + 'setInnerHTML: invalid ' + + 'mod value: ' + mod + + " (search = " + search + ")"); + } + } + else { + throw new TypeError(G + 'setInnerHTML: mod must be ' + + 'string or undefined. Found: ' + mod + + " (search = " + search + ")"); + } + + if (mod === 'id' || mod === 'g') { + // Look by id. + el = W.getElementById(search); + if (el && el.className !== search) el.innerHTML = replace; + } + + if (mod === 'className' || mod === 'g') { + // Look by class name. + el = W.getElementsByClassName(search); + len = el.length; + if (len) { + i = -1; + for ( ; ++i < len ; ) { + el[i].innerHTML = replace; + } + } } - return true; }; + // ### ADD STUFF: Event button, loading dots. + /** * ### GameWindow.getLoadingDots * @@ -215,7 +368,7 @@ * * @param {number} len Optional. The maximum length of the loading dots. * Default: 5 - * @param {string} id Optional The id of the span + * @param {string} id Optional. The id of the span * * @return {object} An object containing two properties: the span element * and a method stop, that clears the interval @@ -223,8 +376,8 @@ GameWindow.prototype.getLoadingDots = function(len, id) { var spanDots, counter, intervalId; if (len & len < 0) { - throw new Error('GameWindow.getLoadingDots: len cannot be < 0. ' + - 'Found: ' + len); + throw new Error(G + 'getLoadingDots: len cannot be < 0. Found: ' + + len); } spanDots = document.createElement('span'); spanDots.id = id || 'span_dots'; @@ -297,7 +450,7 @@ GameWindow.prototype.getEventButton = function(event, attributes) { var b; if ('string' !== typeof event) { - throw new TypeError('GameWindow.getEventButton: event must ' + + throw new TypeError(G + 'getEventButton: event must ' + 'be string. Found: ' + event); } if ('string' === typeof attributes) { @@ -335,163 +488,58 @@ return root.appendChild(eb); }; + // ### SHOWING, HIDING, TOGGLING. + /** - * ### GameWindow.searchReplace - * - * Replaces the innerHTML of the element/s with matching id or class name - * - * It iterates through each element and passes it to - * `GameWindow.setInnerHTML`. - * - * If elements is array, each item in the array must be of the type: - * - * ```javascript - * - * { search: 'key', replace: 'value' } - * - * // or - * - * { search: 'key', replace: 'value', mod: 'id' } - * ``` - * - * If elements is object, it must be of the type: - * - * ```javascript + * ### GameWindow.toggleInputs * - * { - * search1: value1, search2: value 2 // etc. - * } - * ``` + * Enables / disables the input forms * - * It accepts a variable number of input parameters. The first is always - * _elements_. If there are 2 input parameters, the second is _prefix_, - * while if there are 3 input parameters, the second is _mod_ and the third - * is _prefix_. + * If an id is provided, only input elements that are children + * of the element with the specified id are toggled. * - * @param {object|array} Elements to search and replace - * @param {string} mod Optional. Modifier passed to GameWindow.setInnerHTML - * @param {string} prefix Optional. Prefix added to the search string. - * Default: 'ng_replace_', null or '' equals no prefix. + * If id is not given, it toggles the input elements on the whole page, + * including the frame document, if found. * - * @see GameWindow.setInnerHTML - */ - GameWindow.prototype.searchReplace = function() { - var elements, mod, prefix; - var name, len, i; - - if (arguments.length === 2) { - mod = 'g'; - prefix = arguments[1]; - } - else if (arguments.length > 2) { - mod = arguments[1]; - prefix = arguments[2]; - } - - if ('undefined' === typeof prefix) { - prefix = 'ng_replace_'; - } - else if (null === prefix) { - prefix = ''; - } - else if ('string' !== typeof prefix) { - throw new TypeError('GameWindow.searchReplace: prefix ' + - 'must be string, null or undefined. Found: ' + - prefix); - } - - elements = arguments[0]; - if (J.isArray(elements)) { - i = -1, len = elements.length; - for ( ; ++i < len ; ) { - this.setInnerHTML(prefix + elements[i].search, - elements[i].replace, - elements[i].mod || mod); - } - - } - else if ('object' !== typeof elements) { - for (name in elements) { - if (elements.hasOwnProperty(name)) { - this.setInnerHTML(prefix + name, elements[name], mod); - } - } - } - else { - throw new TypeError('GameWindow.setInnerHTML: elements must be ' + - 'object or arrray. Found: ' + elements); - } - - }; - - GameWindow.prototype.setInnerHTML = function(search, replace, mod) { - this.html(search, replace, mod); - }; - - /** - * ### GameWindow.html + * If a state parameter is given, all the input forms will be either + * disabled or enabled (and not toggled). * - * Replaces the innerHTML of the element with matching id or class name + * @param {string} id Optional. The id of the element container + * of the forms. Default: the whole page, including the frame document + * @param {boolean} disabled Optional. Forces all the inputs to be either + * disabled or enabled (not toggled) * - * @param {string|number} search Element id or className - * @param {string|number} replace The new value of the property innerHTML - * @param {string} mod Optional. A modifier defining how to use the - * search parameter. Values: + * @return {boolean} FALSE, if the method could not be executed * - * - 'id': replaces at most one element with the same id (default) - * - 'className': replaces all elements with same class name - * - 'g': replaces globally, both by id and className + * @see GameWindow.getFrameDocument + * @see toggleInputs */ - GameWindow.prototype.html = function(search, replace, mod) { - var el, i, len; - - // Only process strings or numbers. - if ('string' !== typeof search && 'number' !== typeof search) { - throw new TypeError('GameWindow.setInnerHTML: search must be ' + - 'string or number. Found: ' + search + - " (replace = " + replace + ")"); - } - - // Only process strings or numbers. - if ('string' !== typeof replace && 'number' !== typeof replace) { - throw new TypeError('GameWindow.setInnerHTML: replace must be ' + - 'string or number. Found: ' + replace + - " (search = " + search + ")"); + GameWindow.prototype.toggleInputs = function(id, disabled) { + var container; + if (!document.getElementsByTagName) { + node.err(G + 'toggleInputs: getElementsByTagName not found'); + return false; } - - if ('undefined' === typeof mod) { - mod = 'id'; + if (id && 'string' === typeof id) { + throw new Error(G + 'toggleInputs: id must be string or ' + + 'undefined. Found: ' + id); } - else if ('string' === typeof mod) { - if (mod !== 'g' && mod !== 'id' && mod !== 'className') { - throw new Error('GameWindow.setInnerHTML: invalid ' + - 'mod value: ' + mod + - " (search = " + search + ")"); + if (id) { + container = this.gid(id); + if (!container) { + throw new Error(G + 'toggleInputs: no elements found with id ' + + id); } + toggleInputs(disabled, container); } else { - throw new TypeError('GameWindow.setInnerHTML: mod must be ' + - 'string or undefined. Found: ' + mod + - " (search = " + search + ")"); - } - - if (mod === 'id' || mod === 'g') { - // Look by id. - el = W.getElementById(search); - if (el && el.className !== search) el.innerHTML = replace; - } - - if (mod === 'className' || mod === 'g') { - // Look by class name. - el = W.getElementsByClassName(search); - len = el.length; - if (len) { - i = -1; - for ( ; ++i < len ; ) { - el[i].innerHTML = replace; - } - } + // The whole page. + toggleInputs(disabled); + container = this.getFrameDocument(); + // If there is a frame, apply it there too. + if (container) toggleInputs(disabled, container); } + return true; }; /** @@ -510,7 +558,7 @@ */ GameWindow.prototype.hide = function(idOrObj) { var el; - el = getElement(idOrObj, 'GameWindow.hide'); + el = getElement(idOrObj, 'hide'); if (el) { el.style.display = 'none'; W.adjustFrameHeight(0, 0); @@ -538,10 +586,10 @@ var el; display = display || ''; if ('string' !== typeof display) { - throw new TypeError('GameWindow.show: display must be ' + + throw new TypeError(G + 'show: display must be ' + 'string or undefined. Found: ' + display); } - el = getElement(idOrObj, 'GameWindow.show'); + el = getElement(idOrObj, 'show'); if (el) { el.style.display = display; W.adjustFrameHeight(0, 0); @@ -569,10 +617,10 @@ var el; display = display || ''; if ('string' !== typeof display) { - throw new TypeError('GameWindow.toggle: display must ' + + throw new TypeError(G + 'toggle: display must ' + 'be string or undefined. Found: ' + display); } - el = getElement(idOrObj, 'GameWindow.toggle'); + el = getElement(idOrObj, 'toggle'); if (el) { if (el.style.display === 'none') el.style.display = display; else el.style.display = 'none'; @@ -581,11 +629,145 @@ return el; }; + /** + * ## GameWindow.shake + * + * Shakes an element briefly and adds the invalid class + * + * Adds a listener to remove the invalid class on change. + * + * @param {string|HTMLElement} idOrObj The id of or the HTML element itself + * + * @return {HTMLElement} The shaken element, if found + * + */ + GameWindow.prototype.shake = function(idOrObj) { + var el, listener; + el = getElement(idOrObj); + if (el) { + el.classList.add('is-invalid'); + el.classList.add('shake'); // Add shake class + setTimeout(function() { + el.classList.remove('shake'); + }, 500); // Match animation duration + + // Add an event listener that removes the invalid class if + // the element is interacted with. + listener = function() { + el.classList.remove('is-invalid'); + el.removeEventListener('change', listener); + }; + + el.addEventListener('change', listener); + } + return el; + }; + + /** + * ## GameWindow.fadeIn + * + * Fades in an element + * + * @param {string|HTMLElement} idOrObj The id of or the HTML element itself + * @param {object} opts Configuration options + * + * @return {HTMLElement|null} The faded-in element if found, null otherwise + * + * @see _fade + */ + GameWindow.prototype.fadeIn = function(idOrObj, opts) { + return _fade(false, idOrObj, opts); + }; + + /** + * ## GameWindow.fadeOut + * + * Fades out an element + * + * @param {string|HTMLElement} idOrObj The id of or the HTML element itself + * @param {object} opts Configuration options + * + * @return {HTMLElement|null} The faded-out element if found, null otherwise + * + * @see _fade + */ + GameWindow.prototype.fadeOut = function(idOrObj, opts) { + return _fade(true, idOrObj, opts); + }; + + + /** + * ### GameWindow.isRTL + * + * Returns TRUE is the page is written right to left + * + * Results are cached. + * + * @param {boolean} force If truthy, it resets the cache; + * if HTMLElement, it checks within that element. + * + * @returns {boolean} TRUE if the page is RTL + */ + GameWindow.prototype.isRTL = GameWindow.prototype.isRtl = (function(cache) { + return function(force) { + var d; + if ('undefined' === typeof cache || force) { + d = J.isElement(force) ? force : document.documentElement; + cache = d.dir === 'rtl' || + ('function' === typeof getComputedStyle && + getComputedStyle(d).direction === 'rtl'); + } + return cache; + }; + })(); + + + // ## Helper Functions + /** + * Fades an element in or out and adjust the frame height + * + * @param {boolean} out True if it is a fadeOut event, False otherwise + * @param {string|HTMLElement} idOrObj The id of or the HTML element itself + * @param {object} opts Configuration options: + * - `display`: The display property for the faded element + * - `adjustFrameHeight`: If not FALSE, W.adjustFrameHeight is called + * + * @return {HTMLElement|null} The faded* element if found, null otherwise + */ + function _fade(out, idOrObj, opts) { + var el, classRem, classAdd, defDisplay; + el = getElement(idOrObj); + if (!el) return null; + if (out) { + classRem = 'fadein'; + classAdd = 'fadeout'; + defDisplay = 'none'; + } + else { + classRem = 'fadeout'; + classAdd = 'fadein'; + defDisplay = ''; + } + opts = opts || {}; + el.classList.remove(classRem); + el.classList.add(classAdd); + el.style.display = opts.display || defDisplay; + if (opts.adjustFrameHeight !== false) W.adjustFrameHeight(); + return el; + } + /** * ### toggleInputs * + * Enable/disable inputs: 'button', 'select', 'textarea', 'input' + * + * @param {boolean} state Optional. True/false to enable/disable, undefined + * to toggle. + * @param {HTMLElement} container Optional. The element inside which + * toggling takes place. Default: `document`. + * * @api private */ function toggleInputs(state, container) { @@ -616,26 +798,48 @@ * * Gets the element or returns it * - * @param {string|HTMLElement} The id or the HTML element itself + * @param {string|HTMLElement} idOrObj The id or the HTML element itself + * @param {string|undefined} throwAs Optional. The name of the calling + * method used in the string of the error, or undefined to avoid + * throwing altogether. * - * @return {HTMLElement} The HTML Element + * @return {HTMLElement|undefined} The HTML Element or undefined if none + * is found and throwAs is falsy * - * @see GameWindow.getElementById + * @see GameWindow.gid * @api private */ - function getElement(idOrObj, prefix) { - var el; - if ('string' === typeof idOrObj) { - el = W.getElementById(idOrObj); + function getElement(idOrObj, throwAs) { + if ('string' === typeof idOrObj) return W.gid(idOrObj); + if (J.isElement(idOrObj)) return idOrObj; + if (throwAs) { + throw new TypeError(G + throwAs + ': idOrObj must be string or ' + + 'HTML Element. Found: ' + idOrObj); } - else if (J.isElement(idOrObj)) { - el = idOrObj; - } - else { - throw new TypeError(prefix + ': idOrObj must be string ' + - ' or HTML Element. Found: ' + idOrObj); - } - return el; + } + + /** + * ### getDefaultRoot + * + * Tries to find a default root and returns it + * + * @param {string|HTMLElement} root Optional. The id of or the HTML + * element itself to be used as root. + * @param {string|undefined} throwAs Optional. The name of the calling + * method used in the string of the error, or undefined to avoid + * throwing altogether. + * + * @return {HTMLElement|undefined} The root HTML Element, or undefined + * if none is found and `throwAs` is falsy + * + * @see GameWindow.gid + * @api private + */ + function getDefaultRoot(root, throwAs) { + if (!root) root = W.getScreen(); + else root = getElement(root); + if (root) return root; + if (throwAs) throw new Error(G + throwAs + ': could not find root'); } })( diff --git a/package.json b/package.json index a557c1a..0739a78 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "nodegame-window", "description": "Provides a handy API to interface nodeGame with the browser window.", - "version": "7.0.0", + "version": "8.0.0", "keywords": [ "nodegame", "window", "browser", "frame", "behavioral", "multiplayer", "games", "ui" ], "author": "Stefano Balietti ", "license": "MIT",