diff --git a/CHANGELOG b/CHANGELOG index 1b899cb..df75ac5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,23 @@ # nodegame-window change log +## 7.0.0 +- Improved error messages for setInnerHTML +- Frame loading waits for the callback to be executed. +- WaitScreen texts are all paramterers (thanks jjensenius3). + + +## 6.2.0 +- InfoPanel scrolls into view when opened. +- W.createInfoPanel creates toggle button by default. +- InfoPanel.createToggleBtn alias for InfoPanel.createToggleButton. + +## 6.1.1 +- Fixed no doc in previous update. + +## 6.1.0 +- If images are on page, it calls W.adjustFrameHeight after all images are +loaded. + ## 6.0.2 - Fixed auto scroll-up default. - Fixed undefined var root in generateInfoPanel. diff --git a/build/nodegame-window.js b/build/nodegame-window.js index 82601ed..3624410 100644 --- a/build/nodegame-window.js +++ b/build/nodegame-window.js @@ -1,6 +1,6 @@ /** * # GameWindow - * Copyright(c) 2019 Stefano Balietti + * Copyright(c) 2021 Stefano Balietti * MIT Licensed * * API to interface nodeGame with the browser window @@ -22,7 +22,7 @@ var DOM; var constants, windowLevels, screenLevels; - var CB_EXECUTED, WIN_LOADING, lockedUpdate; + var CB_EXECUTED, WIN_LOADING; if (!J) throw new Error('GameWindow: JSUS not found'); DOM = J.require('DOM'); @@ -36,9 +36,6 @@ WIN_LOADING = windowLevels.LOADING; - // Allows just one update at the time to the counter of loading frames. - lockedUpdate = false; - GameWindow.prototype = DOM; GameWindow.prototype.constructor = GameWindow; @@ -62,10 +59,7 @@ var iframeWin; iframeWin = iframe.contentWindow; - function completed(event) { - var iframeDoc; - iframeDoc = J.getIFrameDocument(iframe); - + function completed() { // Detaching the function to avoid double execution. iframe.removeEventListener('load', completed, false); iframeWin.removeEventListener('load', completed, false); @@ -853,17 +847,22 @@ * * Appends a configurable div element at to "top" of the page * - * @param {Element} root Optional. The HTML element to which the info - * panel will be appended. Default: + * @param {object} opts Optional. Configuration options: * - * - above the main frame, or - * - below the header, or - * - inside _documents.body_. + * - root: The HTML element (or its id) under which the Info Panel + * will be appended. Default: above the main frame, or below the + * the header, or under document.body. + * - innerHTML: the content of the Info Panel. + * - force: It destroys current frame, if existing. + * - toggleBtn: If TRUE, it creates a button to toggle the Info Panel. + * Default: TRUE. + * - toggleBtnRoot: the HTML element (or its id) under which the button + * to toggle the Info Panel will be appended. Default: the header. + * - toggleBtnLabel: The text on the button to toggle the Info Panel. + * Default: 'Info'. * - * @param {string} frameName Optional. The name of the iframe. Default: - * 'ng_mainframe' - * @param {boolean} force Optional. Will create the frame even if an - * existing one is found. Default: FALSE + * @param {boolean} force Optional. Deprecated, use force flag in + * options. Default: FALSE * * @return {InfoPanel} A reference to the InfoPanel object * @@ -871,26 +870,35 @@ * * @emit INFOPANEL_GENERATED */ - GameWindow.prototype.generateInfoPanel = function(options, force) { - var infoPanelDiv, root; + GameWindow.prototype.generateInfoPanel = function(opts, force) { + var infoPanelDiv, root, btn; + opts = opts || {}; + + // Backward compatible. + if ('undefined' === typeof force) force = opts.force; + + if (force && this.infoPanel) { + this.infoPanel.destroy(); + this.infoPanel = null; + } if (this.infoPanel) { - if (!force) { - throw new Error('GameWindow.generateInfoPanel: info panel is ' + - 'already existing. Use force to regenerate.'); - } - else { - this.infoPanel.destroy(); - this.infoPanel = null; + if (this.infoPanel.toggleBtn) { + if ('undefined' === typeof opts.toggleBtn) { + opts.toggleBtn = false; + } } + node.warn('W.generateInfoPanel: Info Panel already existing.') + } + else { + this.infoPanel = new node.InfoPanel(opts); } - options = options || {}; - this.infoPanel = new node.InfoPanel(options); infoPanelDiv = this.infoPanel.infoPanelDiv; - root = options.root; + root = opts.root; if (root) { + if ('string' === typeof root) root = W.gid(root); if (!J.isElement(root)) { throw new Error('GameWindow.generateInfoPanel: root must be ' + 'undefined or HTMLElement. Found: ' + root); @@ -907,6 +915,36 @@ document.body.appendChild(infoPanelDiv); } + // Adds Toggle Button if not false. + if (opts.toggleBtn !== false) { + root = null; + if (!opts.toggleBtnRoot) { + if (this.headerElement) root = this.headerElement; + } + else { + if ('string' === typeof opts.toggleBtnRoot) { + root = W.gid(opts.toggleBtnRoot); + } + else { + root = opts.toggleBtnRoot; + } + if (!J.isElement(root)) { + throw new Error('GameWindow.generateInfoPanel: ' + + 'toggleBtnRoot did not resolve to a ' + + 'valid HTMLElement: ' + opts.toggleBtnRoot); + } + } + if (root) { + btn = W.infoPanel.createToggleBtn(opts.toggleBtnLabel); + root.appendChild(btn); + } + } + + // Set inner HTML if specified. + if (opts.innerHTML) { + W.infoPanel.infoPanelDiv.innerHTML = opts.innerHTML; + } + // Emit event. node.events.ng.emit('INFOPANEL_GENERATED', this.infoPanel); @@ -1728,6 +1766,13 @@ // Keep track of nested call to loadFrame. updateAreLoading(this, 1); + // Hide iframe content while loading. + // This way if the page is manipulated in the step callback, + // the user still sees it appearing all at once. + // The iframe visibility is reset by nodegame-client listener + // on LOADED (avoiding registering two listeners this way.) + iframe.style.visibility = 'hidden'; + // Add the onLoad event listener: if (!loadCache || !frameReady) { onLoad(iframe, function() { @@ -2186,9 +2231,8 @@ func(); - // Important. We need a timeout (2nd param), because some changes - // might take time to be reflected in the DOM. - W.adjustFrameHeight(0, 120); + adjustFrameHeightAfterLoad(); + }; if (loadCache) reloadScripts(iframe, afterScripts); @@ -2329,8 +2373,6 @@ * @param {GameWindow} that A reference to the GameWindow instance * @param {number} update The number to add to the counter * - * @see GameWindow.lockedUpdate - * * @api private */ function updateAreLoading(that, update) { @@ -2413,6 +2455,48 @@ } } + /** + * ### adjustFrameHeightAfterLoad + * + * Adjusts the frame height after frame is loaded + * + * It waits 120ms or until all images are loaded. + * + * @see handleFrameLoad + */ + function adjustFrameHeightAfterLoad() { + + // Kudos: + // https://stackoverflow.com/questions/11071314/ + // javascript-execute-after-all-images-have-loaded + + var doc, imgs, len, counter, increment; + doc = W.getFrameDocument(); + if (doc) { + imgs = doc.images; + len = imgs.length; + } + + // If there are no images, we wait a fixed 120 milliseconds. + if (!len) { + // Important. We need a timeout (2nd param), because some changes + // might take time to be reflected in the DOM. + W.adjustFrameHeight(0, 120); + return; + } + + // Else we wait until all images are loaded. + counter = 0; + increment = function() { + if (++counter === len) W.adjustFrameHeight(); + }; + + [].forEach.call(imgs, function(img) { + if (img.complete) increment(); + else img.addEventListener('load', increment, false); + }); + } + //Expose GameWindow prototype to the global object. node.GameWindow = GameWindow; @@ -2829,7 +2913,6 @@ countdown); } this.setScreenLevel('LOCKING'); - text = text || 'Screen locked. Please wait...'; this.waitScreen.lock(text, countdown); this.setScreenLevel('LOCKED'); }; @@ -2874,7 +2957,7 @@ /** * # listeners - * Copyright(c) 2015 Stefano Balietti + * Copyright(c) 2021 Stefano Balietti * MIT Licensed * * GameWindow listeners @@ -2885,23 +2968,6 @@ "use strict"; - var J = node.JSUS; - - 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.'); - } - return el; - } - var GameWindow = node.GameWindow; /** @@ -2926,37 +2992,6 @@ W.init(node.conf.window); }); -// node.on('HIDE', function(idOrObj) { -// var el; -// console.log('***GameWindow.on.HIDE is deprecated. Use ' + -// 'GameWindow.hide() instead.***'); -// el = getElement(idOrObj, 'GameWindow.on.HIDE'); -// if (el) el.style.display = 'none'; -// }); -// -// node.on('SHOW', function(idOrObj) { -// var el; -// console.log('***GameWindow.on.SHOW is deprecated. Use ' + -// 'GameWindow.show() instead.***'); -// el = getElement(idOrObj, 'GameWindow.on.SHOW'); -// if (el) el.style.display = ''; -// }); -// -// node.on('TOGGLE', function(idOrObj) { -// var el; -// console.log('***GameWindow.on.TOGGLE is deprecated. Use ' + -// 'GameWindow.toggle() instead.***'); -// el = getElement(idOrObj, 'GameWindow.on.TOGGLE'); -// if (el) { -// if (el.style.display === 'none') { -// el.style.display = ''; -// } -// else { -// el.style.display = 'none'; -// } -// } -// }); - // Disable all the input forms found within a given id element. node.on('INPUT_DISABLE', function(id) { W.toggleInputs(id, true); @@ -3000,7 +3035,7 @@ /** * # WaitScreen - * Copyright(c) 2018 Stefano Balietti + * Copyright(c) 2021 Stefano Balietti * MIT Licensed * * Overlays the screen, disables inputs, and displays a message/timer @@ -3016,8 +3051,8 @@ // ## Meta-data - WaitScreen.version = '0.9.0'; - WaitScreen.description = 'Shows a standard waiting screen'; + WaitScreen.version = '0.10.0'; + WaitScreen.description = 'Shows a waiting screen'; // ## Helper functions @@ -3096,7 +3131,9 @@ } function event_PAUSED(text) { - text = text || W.waitScreen.defaultTexts.paused; + // Ignores non-string parameters. + text = 'string' === typeof text ? + text : W.waitScreen.defaultTexts.paused; if (W.isScreenLocked()) { W.waitScreen.beforePauseInnerHTML = W.waitScreen.contentDiv.innerHTML; @@ -3124,10 +3161,10 @@ * * Instantiates a new WaitScreen object * - * @param {object} options Optional. Configuration options + * @param {object} opts Optional. Configuration options */ - function WaitScreen(options) { - options = options || {}; + function WaitScreen(opts) { + opts = opts || {}; /** * ### WaitScreen.id @@ -3136,7 +3173,7 @@ * * @see WaitScreen.waitingDiv */ - this.id = options.id || 'ng_waitScreen'; + this.id = opts.id || 'ng_waitScreen'; /** * ### WaitScreen.root @@ -3145,7 +3182,7 @@ * * @see WaitScreen.waitingDiv */ - this.root = options.root || null; + this.root = opts.root || null; /** * ### WaitScreen.waitingDiv @@ -3203,23 +3240,60 @@ * ### WaitScreen.countdown * * Countdown of max waiting time - * - * @see WaitScreen.countdown */ this.countdown = null; + /** + * ### WaitScreen.displayCountdown + * + * If FALSE, countdown is never displayed by lock + * + * @see WaitScreen.lock + */ + this.displayCountdown = + 'undefined' !== typeof opts.displayCountdown ? + !!opts.displayCountdown : true; + /** * ### WaitScreen.text * * Default texts for default events */ this.defaultTexts = { - waiting: options.waitingText || + + // Default text for locked screen. + locked: opts.lockedText || + 'Screen locked. Please wait...', + + // When player is DONE and waiting for others. + waiting: opts.waitingText || 'Waiting for other players to be done...', - stepping: options.steppingText || + + // When entering a new step after DONE (displayed quickly usually). + stepping: opts.steppingText || 'Initializing game step, will be ready soon...', - paused: options.pausedText || - 'Game is paused. Please wait.' + + // Game paused. + paused: opts.pausedText || + 'Game is paused. Please wait.', + + // Countdown text displayed under waiting text. + countdown: opts.countdownResumingText || + '
Do not refresh the page!
Maximum Waiting Time: ', + + // Displayed after resuming from waiting. + countdownResuming: opts.countdownResumingText || + 'Resuming soon...', + + // Formats the countdown in minutes and seconds. + formatCountdown: function(time) { + var out; + out = ''; + time = J.parseMilliseconds(time); + if (time[2]) out += time[2] + ' min '; + if (time[3]) out += time[3] + ' sec'; + return out || 0; + } }; /** @@ -3286,9 +3360,11 @@ * @see WaitScren.updateText */ WaitScreen.prototype.lock = function(text, countdown) { - var frameDoc; + var frameDoc, t; + t = this.defaultTexts; + if ('undefined' === typeof text) text = t.locked; if ('undefined' === typeof document.getElementsByTagName) { - node.warn('WaitScreen.lock: cannot lock inputs.'); + node.warn('WaitScreen.lock: cannot lock inputs'); } // Disables all input forms in the page. lockUnlockedInputs(document); @@ -3310,20 +3386,20 @@ } this.contentDiv.innerHTML = text; - if (countdown) { + if (this.displayCountdown && countdown) { + if (!this.countdownDiv) { this.countdownDiv = W.add('div', this.waitingDiv, 'ng_waitscreen-countdown-div'); - this.countdownDiv.innerHTML = '
Do not refresh the page!' + - '
Maximum Waiting Time: '; + this.countdownDiv.innerHTML = t.countdown; this.countdownSpan = W.add('span', this.countdownDiv, 'ng_waitscreen-countdown-span'); } this.countdown = countdown; - this.countdownSpan.innerHTML = formatCountdown(countdown); + this.countdownSpan.innerHTML = t.formatCountdown(countdown); this.countdownDiv.style.display = ''; this.countdownInterval = setInterval(function() { @@ -3338,10 +3414,10 @@ if (w.countdown < 0) { clearInterval(w.countdownInterval); w.countdownDiv.style.display = 'none'; - w.contentDiv.innerHTML = 'Resuming soon...'; + w.contentDiv.innerHTML = t.countdownResuming; } else { - w.countdownSpan.innerHTML = formatCountdown(w.countdown); + w.countdownSpan.innerHTML = t.formatCountdown(w.countdown); } }, 1000); } @@ -3423,19 +3499,6 @@ this.disable(); }; - - // ## Helper functions. - - function formatCountdown(time) { - var out; - out = ''; - time = J.parseMilliseconds(time); - if (time[2]) out += time[2] + ' min '; - if (time[3]) out += time[3] + ' sec'; - return out || 0; - } - - })( ('undefined' !== typeof node) ? node : module.parent.exports.node, ('undefined' !== typeof window) ? window : module.parent.exports.window @@ -3443,7 +3506,7 @@ /** * # InfoPanel - * Copyright(c) 2017 Stefano Balietti + * Copyright(c) 2021 Stefano Balietti * MIT Licensed * * Adds a configurable extra panel at the top of the screen @@ -3504,6 +3567,15 @@ */ this._buttons = []; + /** + * ### InfoPanel._buttons + * + * Reference to the last created toggle button + * + * @see InfoPanel.createToggleButton + */ + this.toggleBtn = null; + /** * ### InfoPanel.className * @@ -3701,6 +3773,10 @@ this.isVisible = true; // Must be at the end. W.adjustHeaderOffset(true); + // Scroll into view. + if ('function' === typeof this.infoPanelDiv.scrollIntoView) { + this.infoPanelDiv.scrollIntoView({ behavior: 'smooth' }); + } }; /** @@ -3727,32 +3803,38 @@ * Creates an HTML button with a listener to toggle the InfoPanel * * Adds the button to the internal collection `_buttons`. All buttons - * are destroyed if the Info Panel is destroyed. + * are destroyed if the Info Panel is destroyed. Stores a reference to the + * last created button under `toggleBtn`. + * + * @param {string} label Optional. A text to be displayed on the button. + * Default: 'Info'. * * @return {HTMLElement} button A button that toggles info panel * * @see InfoPanel._buttons + * @see InfoPanel.toggleBtn * @see InfoPanel.toggle */ - InfoPanel.prototype.createToggleButton = function(buttonLabel) { + InfoPanel.prototype.createToggleBtn = + InfoPanel.prototype.createToggleButton = function(label) { var that, button; - buttonLabel = buttonLabel || 'Toggle Info Panel'; - if ('string' !== typeof buttonLabel || buttonLabel.trim() === '') { - throw new Error('InfoPanel.createToggleButton: buttonLabel ' + + label = label || 'Info'; + if ('string' !== typeof label || label.trim() === '') { + throw new Error('InfoPanel.createToggleButton: label ' + 'must be undefined or a non-empty string. Found: ' + - buttonLabel); + label); } button = document.createElement('button'); button.className = 'btn btn-lg btn-warning'; - button.innerHTML = buttonLabel ; + button.innerHTML = label ; that = this; - button.onclick = function() { - that.toggle(); - }; + button.onclick = function() { that.toggle(); }; + // Store references. this._buttons.push(button); + this.toggleBtn = button; return button; }; @@ -3760,7 +3842,7 @@ })( ('undefined' !== typeof node) ? node : module.parent.exports.node, ('undefined' !== typeof window) ? window : module.parent.exports.window -);; +); /** * # selector @@ -3988,7 +4070,7 @@ /** * # extra - * Copyright(c) 2019 Stefano Balietti + * Copyright(c) 2022 Stefano Balietti * MIT Licensed * * GameWindow extras @@ -4209,24 +4291,25 @@ * and a method stop, that clears the interval */ GameWindow.prototype.getLoadingDots = function(len, id) { - var spanDots, i, limit, intervalId; + var spanDots, counter, intervalId; if (len & len < 0) { throw new Error('GameWindow.getLoadingDots: len cannot be < 0. ' + 'Found: ' + len); } - len = len || 5; spanDots = document.createElement('span'); spanDots.id = id || 'span_dots'; - limit = ''; - for (i = 0; i < len; i++) { - limit = limit + '.'; - } // Refreshing the dots... + counter = 0; + len = len || 5; + // So the height does not change. + spanDots.innerHTML = ' '; intervalId = setInterval(function() { - if (spanDots.innerHTML !== limit) { + if (counter < len) { + counter++; spanDots.innerHTML = spanDots.innerHTML + '.'; } else { + counter = 0; spanDots.innerHTML = '.'; } }, 1000); @@ -4411,8 +4494,12 @@ }; + GameWindow.prototype.setInnerHTML = function(search, replace, mod) { + this.html(search, replace, mod); + }; + /** - * ### GameWindow.setInnerHTML + * ### GameWindow.html * * Replaces the innerHTML of the element with matching id or class name * @@ -4425,19 +4512,21 @@ * - 'className': replaces all elements with same class name * - 'g': replaces globally, both by id and className */ - GameWindow.prototype.setInnerHTML = function(search, replace, mod) { + 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); + '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); + 'string or number. Found: ' + replace + + " (search = " + search + ")"); } if ('undefined' === typeof mod) { @@ -4446,12 +4535,14 @@ else if ('string' === typeof mod) { if (mod !== 'g' && mod !== 'id' && mod !== 'className') { throw new Error('GameWindow.setInnerHTML: invalid ' + - 'mod value: ' + mod); + 'mod value: ' + mod + + " (search = " + search + ")"); } } else { throw new TypeError('GameWindow.setInnerHTML: mod must be ' + - 'string or undefined. Found: ' + mod); + 'string or undefined. Found: ' + mod + + " (search = " + search + ")"); } if (mod === 'id' || mod === 'g') { diff --git a/build/nodegame-window.min.js b/build/nodegame-window.min.js index b279b88..2f06753 100644 --- a/build/nodegame-window.min.js +++ b/build/nodegame-window.min.js @@ -1,6 +1,6 @@ /** * # GameWindow - * Copyright(c) 2019 Stefano Balietti + * Copyright(c) 2021 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 f(e,t){function r(i){var s;s=J.getIFrameDocument(e),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 l(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 c(e,t){W.isIE?l(e,t):f(e,t)}function h(){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(h.defaults),t.silly("node-window: created.")}function p(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,d(n),a=function(){m(n,e.globalLibs.concat(e.frameLibs.hasOwnProperty(t)?e.frameLibs[t]:[])),s&&(e.cache[t].contents=u.innerHTML),o(),W.adjustFrameHeight(0,120)},i?v(n,a):a()}function d(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)},h.prototype.clearCache=function(){this.cache={}},h.prototype.getElementById=h.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},h.prototype.getElementsByTagName=function(e){var t;return t=this.getFrameDocument(),t?t.getElementsByTagName(e):document.getElementsByTagName(e)},h.prototype.getElementsByClassName=function(e,t){var n;return n=this.getFrameDocument()||document,J.getElementsByClassName(n,e,t)},h.prototype.loadFrame=function(e,n,r){var i,s,o,u,a,f,l,d,v,m,y,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(),m=this.frameName;if(!v)throw new Error("GameWindow.loadFrame: no frame found");if(!m)throw new Error("GameWindow.loadFrame: frame has no name");this.setStateLevel("LOADING"),i=this,w=v.contentWindow,y=W.getIFrameDocument(v),S=y.readyState,S=S==="complete",s=h.defaults.cacheDefaults.loadCache,o=h.defaults.cacheDefaults.storeCacheNow,u=h.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);l=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[m],this.cache.hasOwnProperty(x)&&this.cache[x].cacheOnClose&&(E=y.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[m]=e,g(this,1),(!s||!S)&&c(v,function(){i.directFrameDocumentAccess===null&&b(i),p(i,e,v,m,s,o,function(){i.updateLoadFrameState(n,f,d,l,a)})}),s?S&&p(this,e,v,m,s,o,function(){i.updateLoadFrameState(n,f,d,l,a)}):w.location.replace(e),w.node=t},h.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},h.prototype.updateLoadFrameState=function(n,r,i,s,u){var a,f;a=g(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.")},h.prototype.clearPageBody=function(){this.reset(),document.body.innerHTML=""},h.prototype.clearPage=function(){this.reset();try{document.documentElement.innerHTML=""}catch(e){this.removeChildrenFromNode(document.documentElement)}},h.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},h.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},h.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: ",this.countdownSpan=W.add("span",this.countdownDiv,"ng_waitscreen-countdown-span")),this.countdown=t,this.countdownSpan.innerHTML=c(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="Resuming soon..."):e.countdownSpan.innerHTML=c(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,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 + * Copyright(c) 2021 Stefano Balietti * MIT Licensed * * API to interface nodeGame with the browser window @@ -22,7 +22,7 @@ var DOM; var constants, windowLevels, screenLevels; - var CB_EXECUTED, WIN_LOADING, lockedUpdate; + var CB_EXECUTED, WIN_LOADING; if (!J) throw new Error('GameWindow: JSUS not found'); DOM = J.require('DOM'); @@ -36,9 +36,6 @@ WIN_LOADING = windowLevels.LOADING; - // Allows just one update at the time to the counter of loading frames. - lockedUpdate = false; - GameWindow.prototype = DOM; GameWindow.prototype.constructor = GameWindow; @@ -62,10 +59,7 @@ var iframeWin; iframeWin = iframe.contentWindow; - function completed(event) { - var iframeDoc; - iframeDoc = J.getIFrameDocument(iframe); - + function completed() { // Detaching the function to avoid double execution. iframe.removeEventListener('load', completed, false); iframeWin.removeEventListener('load', completed, false); @@ -853,17 +847,22 @@ * * Appends a configurable div element at to "top" of the page * - * @param {Element} root Optional. The HTML element to which the info - * panel will be appended. Default: + * @param {object} opts Optional. Configuration options: * - * - above the main frame, or - * - below the header, or - * - inside _documents.body_. + * - root: The HTML element (or its id) under which the Info Panel + * will be appended. Default: above the main frame, or below the + * the header, or under document.body. + * - innerHTML: the content of the Info Panel. + * - force: It destroys current frame, if existing. + * - toggleBtn: If TRUE, it creates a button to toggle the Info Panel. + * Default: TRUE. + * - toggleBtnRoot: the HTML element (or its id) under which the button + * to toggle the Info Panel will be appended. Default: the header. + * - toggleBtnLabel: The text on the button to toggle the Info Panel. + * Default: 'Info'. * - * @param {string} frameName Optional. The name of the iframe. Default: - * 'ng_mainframe' - * @param {boolean} force Optional. Will create the frame even if an - * existing one is found. Default: FALSE + * @param {boolean} force Optional. Deprecated, use force flag in + * options. Default: FALSE * * @return {InfoPanel} A reference to the InfoPanel object * @@ -871,26 +870,35 @@ * * @emit INFOPANEL_GENERATED */ - GameWindow.prototype.generateInfoPanel = function(options, force) { - var infoPanelDiv, root; + GameWindow.prototype.generateInfoPanel = function(opts, force) { + var infoPanelDiv, root, btn; + opts = opts || {}; + + // Backward compatible. + if ('undefined' === typeof force) force = opts.force; + + if (force && this.infoPanel) { + this.infoPanel.destroy(); + this.infoPanel = null; + } if (this.infoPanel) { - if (!force) { - throw new Error('GameWindow.generateInfoPanel: info panel is ' + - 'already existing. Use force to regenerate.'); - } - else { - this.infoPanel.destroy(); - this.infoPanel = null; + if (this.infoPanel.toggleBtn) { + if ('undefined' === typeof opts.toggleBtn) { + opts.toggleBtn = false; + } } + node.warn('W.generateInfoPanel: Info Panel already existing.') + } + else { + this.infoPanel = new node.InfoPanel(opts); } - options = options || {}; - this.infoPanel = new node.InfoPanel(options); infoPanelDiv = this.infoPanel.infoPanelDiv; - root = options.root; + root = opts.root; if (root) { + if ('string' === typeof root) root = W.gid(root); if (!J.isElement(root)) { throw new Error('GameWindow.generateInfoPanel: root must be ' + 'undefined or HTMLElement. Found: ' + root); @@ -907,6 +915,36 @@ document.body.appendChild(infoPanelDiv); } + // Adds Toggle Button if not false. + if (opts.toggleBtn !== false) { + root = null; + if (!opts.toggleBtnRoot) { + if (this.headerElement) root = this.headerElement; + } + else { + if ('string' === typeof opts.toggleBtnRoot) { + root = W.gid(opts.toggleBtnRoot); + } + else { + root = opts.toggleBtnRoot; + } + if (!J.isElement(root)) { + throw new Error('GameWindow.generateInfoPanel: ' + + 'toggleBtnRoot did not resolve to a ' + + 'valid HTMLElement: ' + opts.toggleBtnRoot); + } + } + if (root) { + btn = W.infoPanel.createToggleBtn(opts.toggleBtnLabel); + root.appendChild(btn); + } + } + + // Set inner HTML if specified. + if (opts.innerHTML) { + W.infoPanel.infoPanelDiv.innerHTML = opts.innerHTML; + } + // Emit event. node.events.ng.emit('INFOPANEL_GENERATED', this.infoPanel); @@ -1728,6 +1766,13 @@ // Keep track of nested call to loadFrame. updateAreLoading(this, 1); + // Hide iframe content while loading. + // This way if the page is manipulated in the step callback, + // the user still sees it appearing all at once. + // The iframe visibility is reset by nodegame-client listener + // on LOADED (avoiding registering two listeners this way.) + iframe.style.visibility = 'hidden'; + // Add the onLoad event listener: if (!loadCache || !frameReady) { onLoad(iframe, function() { @@ -2328,8 +2373,6 @@ * @param {GameWindow} that A reference to the GameWindow instance * @param {number} update The number to add to the counter * - * @see GameWindow.lockedUpdate - * * @api private */ function updateAreLoading(that, update) { @@ -2429,8 +2472,10 @@ var doc, imgs, len, counter, increment; doc = W.getFrameDocument(); - imgs = doc.images; - len = imgs.length; + if (doc) { + imgs = doc.images; + len = imgs.length; + } // If there are no images, we wait a fixed 120 milliseconds. if (!len) { diff --git a/lib/InfoPanel.js b/lib/InfoPanel.js index de9505b..11c2c4c 100644 --- a/lib/InfoPanel.js +++ b/lib/InfoPanel.js @@ -1,6 +1,6 @@ /** * # InfoPanel - * Copyright(c) 2017 Stefano Balietti + * Copyright(c) 2021 Stefano Balietti * MIT Licensed * * Adds a configurable extra panel at the top of the screen @@ -61,6 +61,15 @@ */ this._buttons = []; + /** + * ### InfoPanel._buttons + * + * Reference to the last created toggle button + * + * @see InfoPanel.createToggleButton + */ + this.toggleBtn = null; + /** * ### InfoPanel.className * @@ -258,6 +267,10 @@ this.isVisible = true; // Must be at the end. W.adjustHeaderOffset(true); + // Scroll into view. + if ('function' === typeof this.infoPanelDiv.scrollIntoView) { + this.infoPanelDiv.scrollIntoView({ behavior: 'smooth' }); + } }; /** @@ -284,32 +297,38 @@ * Creates an HTML button with a listener to toggle the InfoPanel * * Adds the button to the internal collection `_buttons`. All buttons - * are destroyed if the Info Panel is destroyed. + * are destroyed if the Info Panel is destroyed. Stores a reference to the + * last created button under `toggleBtn`. + * + * @param {string} label Optional. A text to be displayed on the button. + * Default: 'Info'. * * @return {HTMLElement} button A button that toggles info panel * * @see InfoPanel._buttons + * @see InfoPanel.toggleBtn * @see InfoPanel.toggle */ - InfoPanel.prototype.createToggleButton = function(buttonLabel) { + InfoPanel.prototype.createToggleBtn = + InfoPanel.prototype.createToggleButton = function(label) { var that, button; - buttonLabel = buttonLabel || 'Toggle Info Panel'; - if ('string' !== typeof buttonLabel || buttonLabel.trim() === '') { - throw new Error('InfoPanel.createToggleButton: buttonLabel ' + + label = label || 'Info'; + if ('string' !== typeof label || label.trim() === '') { + throw new Error('InfoPanel.createToggleButton: label ' + 'must be undefined or a non-empty string. Found: ' + - buttonLabel); + label); } button = document.createElement('button'); button.className = 'btn btn-lg btn-warning'; - button.innerHTML = buttonLabel ; + button.innerHTML = label ; that = this; - button.onclick = function() { - that.toggle(); - }; + button.onclick = function() { that.toggle(); }; + // Store references. this._buttons.push(button); + this.toggleBtn = button; return button; }; @@ -317,4 +336,4 @@ })( ('undefined' !== typeof node) ? node : module.parent.exports.node, ('undefined' !== typeof window) ? window : module.parent.exports.window -);; +); diff --git a/lib/WaitScreen.js b/lib/WaitScreen.js index b544b39..951b57f 100644 --- a/lib/WaitScreen.js +++ b/lib/WaitScreen.js @@ -1,6 +1,6 @@ /** * # WaitScreen - * Copyright(c) 2018 Stefano Balietti + * Copyright(c) 2021 Stefano Balietti * MIT Licensed * * Overlays the screen, disables inputs, and displays a message/timer @@ -16,8 +16,8 @@ // ## Meta-data - WaitScreen.version = '0.9.0'; - WaitScreen.description = 'Shows a standard waiting screen'; + WaitScreen.version = '0.10.0'; + WaitScreen.description = 'Shows a waiting screen'; // ## Helper functions @@ -96,7 +96,9 @@ } function event_PAUSED(text) { - text = text || W.waitScreen.defaultTexts.paused; + // Ignores non-string parameters. + text = 'string' === typeof text ? + text : W.waitScreen.defaultTexts.paused; if (W.isScreenLocked()) { W.waitScreen.beforePauseInnerHTML = W.waitScreen.contentDiv.innerHTML; @@ -124,10 +126,10 @@ * * Instantiates a new WaitScreen object * - * @param {object} options Optional. Configuration options + * @param {object} opts Optional. Configuration options */ - function WaitScreen(options) { - options = options || {}; + function WaitScreen(opts) { + opts = opts || {}; /** * ### WaitScreen.id @@ -136,7 +138,7 @@ * * @see WaitScreen.waitingDiv */ - this.id = options.id || 'ng_waitScreen'; + this.id = opts.id || 'ng_waitScreen'; /** * ### WaitScreen.root @@ -145,7 +147,7 @@ * * @see WaitScreen.waitingDiv */ - this.root = options.root || null; + this.root = opts.root || null; /** * ### WaitScreen.waitingDiv @@ -203,23 +205,60 @@ * ### WaitScreen.countdown * * Countdown of max waiting time - * - * @see WaitScreen.countdown */ this.countdown = null; + /** + * ### WaitScreen.displayCountdown + * + * If FALSE, countdown is never displayed by lock + * + * @see WaitScreen.lock + */ + this.displayCountdown = + 'undefined' !== typeof opts.displayCountdown ? + !!opts.displayCountdown : true; + /** * ### WaitScreen.text * * Default texts for default events */ this.defaultTexts = { - waiting: options.waitingText || + + // Default text for locked screen. + locked: opts.lockedText || + 'Screen locked. Please wait...', + + // When player is DONE and waiting for others. + waiting: opts.waitingText || 'Waiting for other players to be done...', - stepping: options.steppingText || + + // When entering a new step after DONE (displayed quickly usually). + stepping: opts.steppingText || 'Initializing game step, will be ready soon...', - paused: options.pausedText || - 'Game is paused. Please wait.' + + // Game paused. + paused: opts.pausedText || + 'Game is paused. Please wait.', + + // Countdown text displayed under waiting text. + countdown: opts.countdownResumingText || + '
Do not refresh the page!
Maximum Waiting Time: ', + + // Displayed after resuming from waiting. + countdownResuming: opts.countdownResumingText || + 'Resuming soon...', + + // Formats the countdown in minutes and seconds. + formatCountdown: function(time) { + var out; + out = ''; + time = J.parseMilliseconds(time); + if (time[2]) out += time[2] + ' min '; + if (time[3]) out += time[3] + ' sec'; + return out || 0; + } }; /** @@ -286,9 +325,11 @@ * @see WaitScren.updateText */ WaitScreen.prototype.lock = function(text, countdown) { - var frameDoc; + var frameDoc, t; + t = this.defaultTexts; + if ('undefined' === typeof text) text = t.locked; if ('undefined' === typeof document.getElementsByTagName) { - node.warn('WaitScreen.lock: cannot lock inputs.'); + node.warn('WaitScreen.lock: cannot lock inputs'); } // Disables all input forms in the page. lockUnlockedInputs(document); @@ -310,20 +351,20 @@ } this.contentDiv.innerHTML = text; - if (countdown) { + if (this.displayCountdown && countdown) { + if (!this.countdownDiv) { this.countdownDiv = W.add('div', this.waitingDiv, 'ng_waitscreen-countdown-div'); - this.countdownDiv.innerHTML = '
Do not refresh the page!' + - '
Maximum Waiting Time: '; + this.countdownDiv.innerHTML = t.countdown; this.countdownSpan = W.add('span', this.countdownDiv, 'ng_waitscreen-countdown-span'); } this.countdown = countdown; - this.countdownSpan.innerHTML = formatCountdown(countdown); + this.countdownSpan.innerHTML = t.formatCountdown(countdown); this.countdownDiv.style.display = ''; this.countdownInterval = setInterval(function() { @@ -338,10 +379,10 @@ if (w.countdown < 0) { clearInterval(w.countdownInterval); w.countdownDiv.style.display = 'none'; - w.contentDiv.innerHTML = 'Resuming soon...'; + w.contentDiv.innerHTML = t.countdownResuming; } else { - w.countdownSpan.innerHTML = formatCountdown(w.countdown); + w.countdownSpan.innerHTML = t.formatCountdown(w.countdown); } }, 1000); } @@ -423,19 +464,6 @@ this.disable(); }; - - // ## Helper functions. - - function formatCountdown(time) { - var out; - out = ''; - time = J.parseMilliseconds(time); - if (time[2]) out += time[2] + ' min '; - if (time[3]) out += time[3] + ' sec'; - return out || 0; - } - - })( ('undefined' !== typeof node) ? node : module.parent.exports.node, ('undefined' !== typeof window) ? window : module.parent.exports.window diff --git a/lib/modules/extra.js b/lib/modules/extra.js index 9bd7538..f0558ff 100644 --- a/lib/modules/extra.js +++ b/lib/modules/extra.js @@ -1,6 +1,6 @@ /** * # extra - * Copyright(c) 2019 Stefano Balietti + * Copyright(c) 2022 Stefano Balietti * MIT Licensed * * GameWindow extras @@ -221,24 +221,25 @@ * and a method stop, that clears the interval */ GameWindow.prototype.getLoadingDots = function(len, id) { - var spanDots, i, limit, intervalId; + var spanDots, counter, intervalId; if (len & len < 0) { throw new Error('GameWindow.getLoadingDots: len cannot be < 0. ' + 'Found: ' + len); } - len = len || 5; spanDots = document.createElement('span'); spanDots.id = id || 'span_dots'; - limit = ''; - for (i = 0; i < len; i++) { - limit = limit + '.'; - } // Refreshing the dots... + counter = 0; + len = len || 5; + // So the height does not change. + spanDots.innerHTML = ' '; intervalId = setInterval(function() { - if (spanDots.innerHTML !== limit) { + if (counter < len) { + counter++; spanDots.innerHTML = spanDots.innerHTML + '.'; } else { + counter = 0; spanDots.innerHTML = '.'; } }, 1000); @@ -423,8 +424,12 @@ }; + GameWindow.prototype.setInnerHTML = function(search, replace, mod) { + this.html(search, replace, mod); + }; + /** - * ### GameWindow.setInnerHTML + * ### GameWindow.html * * Replaces the innerHTML of the element with matching id or class name * @@ -437,19 +442,21 @@ * - 'className': replaces all elements with same class name * - 'g': replaces globally, both by id and className */ - GameWindow.prototype.setInnerHTML = function(search, replace, mod) { + 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); + '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); + 'string or number. Found: ' + replace + + " (search = " + search + ")"); } if ('undefined' === typeof mod) { @@ -458,12 +465,14 @@ else if ('string' === typeof mod) { if (mod !== 'g' && mod !== 'id' && mod !== 'className') { throw new Error('GameWindow.setInnerHTML: invalid ' + - 'mod value: ' + mod); + 'mod value: ' + mod + + " (search = " + search + ")"); } } else { throw new TypeError('GameWindow.setInnerHTML: mod must be ' + - 'string or undefined. Found: ' + mod); + 'string or undefined. Found: ' + mod + + " (search = " + search + ")"); } if (mod === 'id' || mod === 'g') { diff --git a/lib/modules/lockScreen.js b/lib/modules/lockScreen.js index 9fec142..d6988db 100644 --- a/lib/modules/lockScreen.js +++ b/lib/modules/lockScreen.js @@ -45,7 +45,6 @@ countdown); } this.setScreenLevel('LOCKING'); - text = text || 'Screen locked. Please wait...'; this.waitScreen.lock(text, countdown); this.setScreenLevel('LOCKED'); }; diff --git a/listeners/listeners.js b/listeners/listeners.js index b701c09..f98a32b 100644 --- a/listeners/listeners.js +++ b/listeners/listeners.js @@ -1,6 +1,6 @@ /** * # listeners - * Copyright(c) 2015 Stefano Balietti + * Copyright(c) 2021 Stefano Balietti * MIT Licensed * * GameWindow listeners @@ -11,23 +11,6 @@ "use strict"; - var J = node.JSUS; - - 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.'); - } - return el; - } - var GameWindow = node.GameWindow; /** @@ -52,37 +35,6 @@ W.init(node.conf.window); }); -// node.on('HIDE', function(idOrObj) { -// var el; -// console.log('***GameWindow.on.HIDE is deprecated. Use ' + -// 'GameWindow.hide() instead.***'); -// el = getElement(idOrObj, 'GameWindow.on.HIDE'); -// if (el) el.style.display = 'none'; -// }); -// -// node.on('SHOW', function(idOrObj) { -// var el; -// console.log('***GameWindow.on.SHOW is deprecated. Use ' + -// 'GameWindow.show() instead.***'); -// el = getElement(idOrObj, 'GameWindow.on.SHOW'); -// if (el) el.style.display = ''; -// }); -// -// node.on('TOGGLE', function(idOrObj) { -// var el; -// console.log('***GameWindow.on.TOGGLE is deprecated. Use ' + -// 'GameWindow.toggle() instead.***'); -// el = getElement(idOrObj, 'GameWindow.on.TOGGLE'); -// if (el) { -// if (el.style.display === 'none') { -// el.style.display = ''; -// } -// else { -// el.style.display = 'none'; -// } -// } -// }); - // Disable all the input forms found within a given id element. node.on('INPUT_DISABLE', function(id) { W.toggleInputs(id, true); diff --git a/package.json b/package.json index d3cba18..a557c1a 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": "6.0.2", + "version": "7.0.0", "keywords": [ "nodegame", "window", "browser", "frame", "behavioral", "multiplayer", "games", "ui" ], "author": "Stefano Balietti ", "license": "MIT",