nodegame-widgets
Version:
Collections of useful and reusable javascript / HTML snippets for nodeGame
757 lines (674 loc) • 22.2 kB
JavaScript
/**
* # VisualTimer
* Copyright(c) 2021 Stefano Balietti <ste@nodegame.org>
* MIT Licensed
*
* Display a configurable timer for the game
*
* Timer can trigger events, only for countdown smaller than 1h.
*
* www.nodegame.org
*/
(function(node) {
"use strict";
node.widgets.register('VisualTimer', VisualTimer);
// ## Meta-data
VisualTimer.version = '0.9.3';
VisualTimer.description = 'Display a configurable timer for the game. ' +
'Can trigger events. Only for countdown smaller than 1h.';
VisualTimer.title = 'Time Left';
VisualTimer.className = 'visualtimer';
// ## Dependencies
VisualTimer.dependencies = {
GameTimer: {},
JSUS: {}
};
/**
* ## VisualTimer constructor
*
* `VisualTimer` displays and manages a `GameTimer`
*
* @param {object} options Optional. Configuration options
* The options it can take are:
*
* - any options that can be passed to a `GameTimer`
* - `waitBoxOptions`: an option object to be passed to `TimerBox`
* - `mainBoxOptions`: an option object to be passed to `TimerBox`
*
* @see TimerBox
* @see GameTimer
*/
function VisualTimer() {
/**
* ### VisualTimer.gameTimer
*
* The timer which counts down the game time
*
* @see node.timer.createTimer
*/
this.gameTimer = null;
/**
* ### VisualTimer.mainBox
*
* The `TimerBox` which displays the main timer
*
* @see TimerBox
*/
this.mainBox = null;
/**
* ### VisualTimer.waitBox
*
* The `TimerBox` which displays the wait timer
*
* @see TimerBox
*/
this.waitBox = null;
/**
* ### VisualTimer.activeBox
*
* The `TimerBox` in which to display the time
*
* This variable is always a reference to either `waitBox` or
* `mainBox`.
*
* @see TimerBox
*/
this.activeBox = null;
/**
* ### VisualTimer.isInitialized
*
* Indicates whether the instance has been initializded already
*/
this.isInitialized = false;
/**
* ### VisualTimer.options
*
* Currently stored options
*/
this.options = {};
/**
* ### VisualTimer.internalTimer
*
* TRUE, if the timer is created internally
*
* Internal timers are destroyed when widget is destroyed or cleared
*
* @see VisualTimer.gameTimer
* @see VisualTimer.clear
*/
this.internalTimer = null;
}
// ## VisualTimer methods
/**
* ### VisualTimer.init
*
* Initializes the instance. When called again, adds options to current ones
*
* The options it can take are:
*
* - any options that can be passed to a `GameTimer`
* - waitBoxOptions: an option object to be passed to `TimerBox`
* - mainBoxOptions: an option object to be passed to `TimerBox`
*
* @param {object} options Optional. Configuration options
*
* @see TimerBox
* @see GameTimer
*/
VisualTimer.prototype.init = function(options) {
var t, gameTimerOptions;
// We keep the check for object, because this widget is often
// called by users and the restart methods does not guarantee
// an object.
options = options || {};
if ('object' !== typeof options) {
throw new TypeError('VisualTimer.init: options must be ' +
'object or undefined. Found: ' + options);
}
// Important! Do not modify directly options, because it might
// modify a step-property. Will manual clone later.
gameTimerOptions = {};
// If gameTimer is not already set, check options, then
// try to use node.game.timer, if defined, otherwise crete a new timer.
if ('undefined' !== typeof options.gameTimer) {
if (this.gameTimer) {
throw new Error('GameTimer.init: options.gameTimer cannot ' +
'be set if a gameTimer is already existing: ' +
this.name);
}
if ('object' !== typeof options.gameTimer) {
throw new TypeError('VisualTimer.init: options.' +
'gameTimer must be object or ' +
'undefined. Found: ' + options.gameTimer);
}
this.gameTimer = options.gameTimer;
}
else {
if (!this.isInitialized) {
this.internalTimer = true;
this.gameTimer = node.timer.createTimer({
name: options.name || 'VisualTimer_' + J.randomInt(10000000)
});
}
}
if (options.hooks) {
if (!this.internalTimer) {
throw new Error('VisualTimer.init: cannot add hooks on ' +
'external gameTimer.');
}
if (!J.isArray(options.hooks)) {
gameTimerOptions.hooks = [ options.hooks ];
}
}
else {
gameTimerOptions.hooks = [];
}
// Only push this hook once.
if (!this.isInitialized) {
gameTimerOptions.hooks.push({
name: 'VisualTimer_' + this.wid,
hook: this.updateDisplay,
ctx: this
});
}
// Important! Manual clone must be done after hooks and gameTimer.
// Parse milliseconds option.
if ('undefined' !== typeof options.milliseconds) {
gameTimerOptions.milliseconds =
node.timer.parseInput('milliseconds', options.milliseconds);
}
// Parse update option.
if ('undefined' !== typeof options.update) {
gameTimerOptions.update =
node.timer.parseInput('update', options.update);
}
else {
gameTimerOptions.update = 1000;
}
// Parse timeup option.
if ('undefined' !== typeof options.timeup) {
gameTimerOptions.timeup = options.timeup;
}
// Init the gameTimer, regardless of the source (internal vs external).
this.gameTimer.init(gameTimerOptions);
t = this.gameTimer;
// TODO: not using session for now.
// node.session.register('visualtimer', {
// set: function(p) {
// // TODO
// },
// get: function() {
// return {
// startPaused: t.startPaused,
// status: t.status,
// timeLeft: t.timeLeft,
// timePassed: t.timePassed,
// update: t.update,
// updateRemaining: t.updateRemaining,
// updateStart: t. updateStart
// };
// }
// });
this.options = gameTimerOptions;
// Must be after this.options is assigned.
if ('undefined' === typeof this.options.stopOnDone) {
this.options.stopOnDone = true;
}
if ('undefined' === typeof this.options.startOnPlaying) {
this.options.startOnPlaying = true;
}
if (!this.options.mainBoxOptions) {
this.options.mainBoxOptions = {};
}
if (!this.options.waitBoxOptions) {
this.options.waitBoxOptions = {};
}
J.mixout(this.options.mainBoxOptions,
{classNameBody: options.className, hideTitle: true});
J.mixout(this.options.waitBoxOptions,
{title: 'Max. wait timer',
classNameTitle: 'waitTimerTitle',
classNameBody: 'waitTimerBody', hideBox: true});
if (!this.mainBox) {
this.mainBox = new TimerBox(this.options.mainBoxOptions);
}
else {
this.mainBox.init(this.options.mainBoxOptions);
}
if (!this.waitBox) {
this.waitBox = new TimerBox(this.options.waitBoxOptions);
}
else {
this.waitBox.init(this.options.waitBoxOptions);
}
this.activeBox = this.options.activeBox || this.mainBox;
this.isInitialized = true;
};
VisualTimer.prototype.append = function() {
this.bodyDiv.appendChild(this.mainBox.boxDiv);
this.bodyDiv.appendChild(this.waitBox.boxDiv);
this.activeBox = this.mainBox;
this.updateDisplay();
};
/**
* ### VisualTimer.clear
*
* Reverts state of `VisualTimer` to right after creation
*
* @param {object} options Configuration object
*
* @return {object} oldOptions The Old options
*
* @see node.timer.destroyTimer
* @see VisualTimer.init
*/
VisualTimer.prototype.clear = function(options) {
var oldOptions;
options = options || {};
oldOptions = this.options;
destroyTimer(this);
this.gameTimer = null;
this.activeBox = null;
this.isInitialized = false;
this.init(options);
return oldOptions;
};
/**
* ### VisualTimer.updateDisplay
*
* Changes `activeBox` to display current time of `gameTimer`
*
* @see TimerBox.bodyDiv
*/
VisualTimer.prototype.updateDisplay = function() {
var time, minutes, seconds;
if (!this.gameTimer.milliseconds || this.gameTimer.milliseconds === 0) {
this.activeBox.bodyDiv.innerHTML = '00:00';
return;
}
time = this.gameTimer.milliseconds - this.gameTimer.timePassed;
time = J.parseMilliseconds(time);
minutes = (time[2] < 10) ? '' + '0' + time[2] : time[2];
seconds = (time[3] < 10) ? '' + '0' + time[3] : time[3];
this.activeBox.bodyDiv.innerHTML = minutes + ':' + seconds;
};
/**
* ### VisualTimer.start
*
* Starts the timer
*
* @see VisualTimer.updateDisplay
* @see GameTimer.start
*/
VisualTimer.prototype.start = function() {
this.updateDisplay();
this.gameTimer.start();
};
/**
* ### VisualTimer.restart
*
* Restarts the timer with new options
*
* @param {object|number} options Configuration object or the number of
* milliseconds
*
* @see VisualTimer.init
* @see VisualTimer.start
* @see VisualTimer.stop
*/
VisualTimer.prototype.restart = function(options) {
this.stop();
if ('number' === typeof options) options = { milliseconds: options };
this.init(options);
this.start();
};
/**
* ### VisualTimer.stop
*
* Stops the timer display and stores the time left in `activeBox.timeLeft`
*
* @see GameTimer.isStopped
* @see GameTimer.stop
*/
VisualTimer.prototype.stop = function() {
if (!this.gameTimer.isStopped()) {
this.activeBox.timeLeft = this.gameTimer.timeLeft;
this.gameTimer.stop();
}
};
/**
* ### VisualTimer.switchActiveBoxTo
*
* Switches the display of the `gameTimer` into the `TimerBox` `box`
*
* Stores `gameTimer.timeLeft` into `activeBox` and then switches
* `activeBox` to reference `box`.
*
* @param {TimerBox} box TimerBox in which to display `gameTimer` time
*/
VisualTimer.prototype.switchActiveBoxTo = function(box) {
this.activeBox.timeLeft = this.gameTimer.timeLeft || 0;
this.activeBox = box;
this.updateDisplay();
};
/**
* ### VisualTimer.startWaiting
*
* Stops the timer and changes the appearance to a max. wait timer
*
* If options and/or options.milliseconds are undefined, the wait timer
* will start with the current time left on the `gameTimer`. The mainBox
* will be striked out, the waitBox set active and unhidden. All other
* options are forwarded directly to `VisualTimer.restart`.
*
* @param {object} options Configuration object
*
* @see VisualTimer.restart
*/
VisualTimer.prototype.startWaiting = function(options) {
if ('undefined' === typeof options) options = {};
if ('undefined' === typeof options.milliseconds) {
options.milliseconds = this.gameTimer.timeLeft;
}
if ('undefined' === typeof options.mainBoxOptions) {
options.mainBoxOptions = {};
}
if ('undefined' === typeof options.waitBoxOptions) {
options.waitBoxOptions = {};
}
options.mainBoxOptions.classNameBody = 'strike';
options.mainBoxOptions.timeLeft = this.gameTimer.timeLeft || 0;
options.activeBox = this.waitBox;
options.waitBoxOptions.hideBox = false;
this.restart(options);
};
/**
* ### VisualTimer.startTiming
*
* Starts the timer and changes appearance to a regular countdown
*
* The mainBox will be unstriked and set active, the waitBox will be
* hidden. All other options are forwarded directly to
* `VisualTimer.restart`.
*
* @param {object} options Configuration object
*
* @see VisualTimer.restart
*/
VisualTimer.prototype.startTiming = function(options) {
if ('undefined' === typeof options) {
options = {};
}
if ('undefined' === typeof options.mainBoxOptions) {
options.mainBoxOptions = {};
}
if ('undefined' === typeof options.waitBoxOptions) {
options.waitBoxOptions = {};
}
options.activeBox = this.mainBox;
options.waitBoxOptions.timeLeft = this.gameTimer.timeLeft || 0;
options.waitBoxOptions.hideBox = true;
options.mainBoxOptions.classNameBody = '';
this.restart(options);
};
/**
* ### VisualTimer.resume
*
* Resumes the `gameTimer`
*
* @see GameTimer.resume
*/
VisualTimer.prototype.resume = function() {
this.gameTimer.resume();
};
/**
* ### VisualTimer.setToZero
*
* Stops `gameTimer` and sets `activeBox` to display `00:00`
*
* @see GameTimer.resume
*/
VisualTimer.prototype.setToZero = function() {
this.stop();
this.activeBox.bodyDiv.innerHTML = '00:00';
this.activeBox.setClassNameBody('strike');
};
/**
* ### VisualTimer.isTimeup
*
* Returns TRUE if the timer expired
*
* This method is added for backward compatibility.
*
* @see GameTimer.isTimeup
*/
VisualTimer.prototype.isTimeup = function() {
return this.gameTimer.isTimeup();
};
/**
* ### VisualTimer.doTimeUp
*
* Stops the timer and calls the timeup
*
* @see GameTimer.doTimeup
*/
VisualTimer.prototype.doTimeUp = function() {
this.gameTimer.doTimeUp();
};
VisualTimer.prototype.listeners = function() {
var that = this;
// Add listeners only on internal timer.
if (!this.internalTimer) return;
node.on('PLAYING', function() {
var options;
if (that.options.startOnPlaying) {
options = that.gameTimer.getStepOptions();
if (options) {
// Visual update is here (1000 usually).
options.update = that.update;
// Make sure timeup is not used (game.timer does it).
options.timeup = undefined;
// Options other than `update`, `timeup`,
// `milliseconds`, `hooks`, `gameTimer` are ignored.
that.startTiming(options);
}
else {
// Set to zero if it was not started already.
if (!that.gameTimer.isRunning()) that.setToZero();
}
}
});
node.on('REALLY_DONE', function() {
if (that.options.stopOnDone) {
if (!that.gameTimer.isStopped()) {
// This was creating problems, so we just stop it.
// It could be an option, though.
// that.startWaiting();
that.stop();
}
}
});
// Handle destroy.
this.on('destroyed', function() {
destroyTimer(that);
that.bodyDiv.removeChild(that.mainBox.boxDiv);
that.bodyDiv.removeChild(that.waitBox.boxDiv);
});
};
/**
* # TimerBox
*
* Copyright(c) 2015 Stefano Balietti
* MIT Licensed
*
* Represents a box wherin to display a `VisualTimer`
*/
/**
* ## TimerBox constructor
*
* `TimerBox` represents a box wherein to display the timer
*
* @param {object} options Optional. Configuration options
* The options it can take are:
*
* - `hideTitle`
* - `hideBody`
* - `hideBox`
* - `title`
* - `classNameTitle`
* - `classNameBody`
* - `timeLeft`
*/
function TimerBox(options) {
/**
* ### TimerBox.boxDiv
*
* The Div which will contain the title and body Divs
*/
this.boxDiv = null;
/**
* ### TimerBox.titleDiv
*
* The Div which will contain the title
*/
this.titleDiv = null;
/**
* ### TimerBox.bodyDiv
*
* The Div which will contain the numbers
*/
this.bodyDiv = null;
/**
* ### TimerBox.timeLeft
*
* Used to store the last value before focus is taken away
*/
this.timeLeft = null;
this.boxDiv = W.get('div');
this.titleDiv = W.add('div', this.boxDiv);
this.bodyDiv = W.add('div', this.boxDiv);
this.init(options);
}
TimerBox.prototype.init = function(options) {
if (options) {
if (options.hideTitle) {
this.hideTitle();
}
else {
this.unhideTitle();
}
if (options.hideBody) {
this.hideBody();
}
else {
this.unhideBody();
}
if (options.hideBox) {
this.hideBox();
}
else {
this.unhideBox();
}
}
this.setTitle(options.title || '');
this.setClassNameTitle(options.classNameTitle || '');
this.setClassNameBody(options.classNameBody || '');
if (options.timeLeft) {
this.timeLeft = options.timeLeft;
}
};
// ## TimerBox methods
/**
* ### TimerBox.hideBox
*
* Hides entire `TimerBox`
*/
TimerBox.prototype.hideBox = function() {
this.boxDiv.style.display = 'none';
};
/**
* ### TimerBox.unhideBox
*
* Hides entire `TimerBox`
*/
TimerBox.prototype.unhideBox = function() {
this.boxDiv.style.display = '';
};
/**
* ### TimerBox.hideTitle
*
* Hides title of `TimerBox`
*/
TimerBox.prototype.hideTitle = function() {
this.titleDiv.style.display = 'none';
};
/**
* ### TimerBox.unhideTitle
*
* Unhides title of `TimerBox`
*/
TimerBox.prototype.unhideTitle = function() {
this.titleDiv.style.display = '';
};
/**
* ### TimerBox.hideBody
*
* Hides body of `TimerBox`
*/
TimerBox.prototype.hideBody = function() {
this.bodyDiv.style.display = 'none';
};
/**
* ### TimerBox.unhideBody
*
* Unhides Body of `TimerBox`
*/
TimerBox.prototype.unhideBody = function() {
this.bodyDiv.style.display = '';
};
/**
* ### TimerBox.setTitle
*
* Sets title of `TimerBox`
*/
TimerBox.prototype.setTitle = function(title) {
this.titleDiv.innerHTML = title;
};
/**
* ### TimerBox.setClassNameTitle
*
* Sets class name of title of `TimerBox`
*/
TimerBox.prototype.setClassNameTitle = function(className) {
this.titleDiv.className = className;
};
/**
* ### TimerBox.setClassNameBody
*
* Sets class name of body of `TimerBox`
*/
TimerBox.prototype.setClassNameBody = function(className) {
this.bodyDiv.className = className;
};
// Helper function.
function destroyTimer(that) {
if (that.internalTimer) {
if (!that.gameTimer.isDestroyed()) {
node.timer.destroyTimer(that.gameTimer);
}
that.internalTimer = null;
}
else {
that.gameTimer.removeHook('VisualTimer_' + that.wid);
}
}
// if (this.internalTimer) {
// if (!this.gameTimer.isDestroyed()) {
// node.timer.destroyTimer(this.gameTimer);
// }
// this.internalTimer = null;
// }
// else {
// this.gameTimer.removeHook(this.updateHookName);
// }
})(node);