nodegame-widgets
Version:
Collections of useful and reusable javascript / HTML snippets for nodeGame
494 lines (422 loc) • 16.9 kB
JavaScript
/**
* # EndScreen
* Copyright(c) 2021 Stefano Balietti <ste@nodegame.org>
* MIT Licensed
*
* Creates an interface to display final earnings, exit code, etc.
*
* www.nodegame.org
*/
(function(node) {
"use strict";
// Register the widget in the widgets collection.
node.widgets.register('EndScreen', EndScreen);
// ## Add Meta-data
EndScreen.version = '0.7.2';
EndScreen.description = 'Game end screen. With end game message, ' +
'email form, and exit code.';
EndScreen.title = false;
EndScreen.className = 'endscreen';
EndScreen.texts = {
headerMessage: 'Thank you for participating!',
message: 'You have now completed this task and your data has ' +
'been saved. Please go back to the Amazon Mechanical Turk ' +
'web site and submit the HIT.',
totalWin: 'Your total win:',
exitCode: 'Your exit code:',
errTotalWin: 'Error: invalid total win.',
errExitCode: 'Error: invalid exit code.',
copyButton: 'Copy',
exitCopyMsg: 'Exit code copied to clipboard.',
exitCopyError: 'Failed to copy exit code. Please copy it manually.'
};
// ## Dependencies
// Checked when the widget is created.
EndScreen.dependencies = {
Feedback: {},
EmailForm: {}
};
/**
* ## EndScreen constructor
*
* Creates a new instance of EndScreen
*
* @param {object} options Configuration options
*
* @see EndScreen.init
*/
function EndScreen(options) {
/**
* ### EndScreen.showEmailForm
*
* If true, the email form is shown
*
* Default: true
*/
this.showEmailForm = true;
/**
* ### EndScreen.showFeedbackForm
*
* If true, the feedback form is shown
*
* Default: true
*/
this.showFeedbackForm = true;
/**
* ### EndScreen.showTotalWin
*
* If true, the total win is shown
*
* Default: true
*/
this.showTotalWin = true;
/**
* ### EndScreen.showExitCode
*
* If true, the exit code is shown
*
* Default: true
*/
this.showExitCode = true;
/**
* ### EndScreen.totalWinCurrency
*
* The currency displayed after totalWin
*
* Default: 'USD'
*
* // TODO: deprecate and rename to currency.
*/
this.totalWinCurrency = 'USD';
/**
* ### EndScreen.totalWinCb
*
* If defined, the return value is displayed inside the totalWin box
*
* Accepts two parameters: a data object (as sent from server), and
* the reference to the EndScreen.
*/
this.totalWinCb = null;
/**
* ### EndScreen.emailForm
*
* EmailForm widget element
*
* @see EmailForm
*/
this.emailForm = null;
/**
* ### EndScreen.feedback
*
* Feedback widget element
*
* @see Feedback
*/
this.feedback = null;
/**
* ### EndScreen.endScreenElement
*
* Endscreen HTML element
*
* Default: an HTML element,
* null initially, element added on append()
*/
this.endScreenHTML = null;
/**
* ### EndScreen.askServer
*
* If TRUE, after being appended it sends a 'WIN' message to server
*
* Default: FALSE
*/
this.askServer = options.askServer || false;
}
EndScreen.prototype.init = function(options) {
if (options.email === false) {
this.showEmailForm = false;
}
else if ('boolean' === typeof options.showEmailForm) {
this.showEmailForm = options.showEmailForm;
}
else if ('undefined' !== typeof options.showEmailForm) {
throw new TypeError('EndScreen.init: ' +
'options.showEmailForm ' +
'must be boolean or undefined. ' +
'Found: ' + options.showEmailForm);
}
if (options.feedback === false) {
this.showFeedbackForm = false;
}
else if ('boolean' === typeof options.showFeedbackForm) {
this.showFeedbackForm = options.showFeedbackForm;
}
else if ('undefined' !== typeof options.showFeedbackForm) {
throw new TypeError('EndScreen.init: ' +
'options.showFeedbackForm ' +
'must be boolean or undefined. ' +
'Found: ' + options.showFeedbackForm);
}
if (options.totalWin === false) {
this.showTotalWin = false;
}
else if ('boolean' === typeof options.showTotalWin) {
this.showTotalWin = options.showTotalWin;
}
else if ('undefined' !== typeof options.showTotalWin) {
throw new TypeError('EndScreen.init: ' +
'options.showTotalWin ' +
'must be boolean or undefined. ' +
'Found: ' + options.showTotalWin);
}
if (options.exitCode === false) {
options.showExitCode !== false
}
else if ('boolean' === typeof options.showExitCode) {
this.showExitCode = options.showExitCode;
}
else if ('undefined' !== typeof options.showExitCode) {
throw new TypeError('EndScreen.init: ' +
'options.showExitCode ' +
'must be boolean or undefined. ' +
'Found: ' + options.showExitCode);
}
if ('string' === typeof options.totalWinCurrency &&
options.totalWinCurrency.trim() !== '') {
this.totalWinCurrency = options.totalWinCurrency;
}
else if ('undefined' !== typeof options.totalWinCurrency) {
throw new TypeError('EndScreen.init: ' +
'options.totalWinCurrency must be undefined ' +
'or a non-empty string. Found: ' +
options.totalWinCurrency);
}
if (options.totalWinCb) {
if ('function' === typeof options.totalWinCb) {
this.totalWinCb = options.totalWinCb;
}
else {
throw new TypeError('EndScreen.init: ' +
'options.totalWinCb ' +
'must be function or undefined. ' +
'Found: ' + options.totalWinCb);
}
}
if (this.showEmailForm && !this.emailForm) {
// TODO: nested properties are overwitten fully. Update.
this.emailForm = node.widgets.get('EmailForm', J.mixin({
onsubmit: {
send: true,
emailOnly: true,
updateUI: true
},
storeRef: false,
texts: {
label: 'If you would like to be contacted for future ' +
'studies, please enter your email (optional):',
errString: 'Please enter a valid email and retry'
},
setMsg: true // Sends a set message for logic's db.
}, options.email));
}
if (this.showFeedbackForm) {
this.feedback = node.widgets.get('Feedback', J.mixin(
{ storeRef: false, minChars: 50, setMsg: true },
options.feedback));
}
};
// Implements the Widget.append method.
EndScreen.prototype.append = function() {
this.endScreenHTML = this.makeEndScreen();
this.bodyDiv.appendChild(this.endScreenHTML);
if (this.askServer) setTimeout(function() { node.say('WIN'); });
};
/**
* ### EndScreen.makeEndScreen
*
* Builds up the end screen (HTML + nested widgets)
*/
EndScreen.prototype.makeEndScreen = function() {
var endScreenElement;
var headerElement, messageElement;
var totalWinElement, totalWinParaElement, totalWinInputElement;
var exitCodeElement, exitCodeParaElement, exitCodeInputElement;
var exitCodeBtn, exitCodeGroup;
var that = this;
endScreenElement = document.createElement('div');
endScreenElement.className = 'endscreen';
headerElement = document.createElement('h1');
headerElement.innerHTML = this.getText('headerMessage');
endScreenElement.appendChild(headerElement);
messageElement = document.createElement('p');
messageElement.innerHTML = this.getText('message');
endScreenElement.appendChild(messageElement);
if (this.showTotalWin) {
totalWinElement = document.createElement('div');
totalWinParaElement = document.createElement('p');
totalWinParaElement.innerHTML = '<strong>' +
this.getText('totalWin') +
'</strong>';
totalWinInputElement = document.createElement('input');
totalWinInputElement.className = 'endscreen-total form-control';
totalWinInputElement.setAttribute('disabled', 'true');
totalWinParaElement.appendChild(totalWinInputElement);
totalWinElement.appendChild(totalWinParaElement);
endScreenElement.appendChild(totalWinElement);
this.totalWinInputElement = totalWinInputElement;
}
if (this.showExitCode) {
exitCodeElement = document.createElement('div');
exitCodeElement.className = 'input-group';
exitCodeParaElement = document.createElement('span');
exitCodeParaElement.innerHTML = '<strong>' +
this.getText('exitCode') + '</strong>';
exitCodeInputElement = document.createElement('input');
exitCodeInputElement.id = 'exit_code';
exitCodeInputElement.className = 'endscreen-exit-code ' +
'form-control';
exitCodeInputElement.setAttribute('disabled', 'true');
exitCodeGroup = document.createElement('span');
exitCodeGroup.className = 'input-group-btn';
exitCodeBtn = document.createElement('button');
exitCodeBtn.className = 'btn btn-default endscreen-copy-btn';
exitCodeBtn.innerHTML = this.getText('copyButton');
exitCodeBtn.type = 'button';
exitCodeBtn.onclick = function() {
that.copy(exitCodeInputElement.value);
};
exitCodeGroup.appendChild(exitCodeBtn);
endScreenElement.appendChild(exitCodeParaElement);
exitCodeElement.appendChild(exitCodeGroup);
exitCodeElement.appendChild(exitCodeInputElement);
endScreenElement.appendChild(exitCodeElement);
this.exitCodeInputElement = exitCodeInputElement;
}
if (this.showEmailForm) {
node.widgets.append(this.emailForm, endScreenElement, {
title: false,
panel: false
});
}
if (this.showFeedbackForm) {
node.widgets.append(this.feedback, endScreenElement, {
title: false,
panel: false
});
}
return endScreenElement;
};
// Implements the Widget.listeners method.
EndScreen.prototype.listeners = function() {
var that;
that = this;
node.on.data('WIN', function(message) {
that.updateDisplay(message.data);
});
};
EndScreen.prototype.copy = function(text) {
var inp = document.createElement('input');
try {
document.body.appendChild(inp);
inp.value = text;
inp.select();
document.execCommand('copy', false);
inp.remove();
alert(this.getText('exitCopyMsg'));
} catch (err) {
alert(this.getText('exitCopyError'));
}
};
/**
* ### EndScreen.updateDisplay
*
* Updates the display
*
* @param {object} data An object containing the info to update. Format:
* - total: The total won.
* - exit: An exit code.
*/
EndScreen.prototype.updateDisplay = function(data) {
var preWin, totalWin, totalRaw, exitCode;
var totalHTML, exitCodeHTML, ex, err;
if (this.totalWinCb) {
totalWin = this.totalWinCb(data, this);
}
else {
if ('undefined' === typeof data.total &&
'undefined' === typeof data.totalRaw) {
throw new Error('EndScreen.updateDisplay: data.total and ' +
'data.totalRaw cannot be both undefined.');
}
if ('undefined' !== typeof data.total) {
totalWin = J.isNumber(data.total);
if (totalWin === false) {
node.err('EndScreen.updateDisplay: invalid data.total: ' +
data.total);
totalWin = this.getText('errTotalWin');
err = true;
}
}
preWin = '';
if ('undefined' !== typeof data.basePay) {
preWin = data.basePay;
}
if ('undefined' !== typeof data.bonus &&
data.showBonus !== false) {
if (preWin !== '') preWin += ' + ';
preWin += data.bonus;
}
if (data.partials) {
if (!J.isArray(data.partials)) {
node.err('EndScreen error, invalid partials win: ' +
data.partials);
}
else {
// If there is a basePay we already have a preWin.
if (preWin !== '') preWin += ' + ';
preWin += data.partials.join(' + ');
}
}
if ('undefined' !== typeof data.totalRaw) {
if (preWin) preWin += ' = ';
else preWin = '';
preWin += data.totalRaw;
// Get Exchange Rate.
ex = 'undefined' !== typeof data.exchangeRate ?
data.exchangeRate : node.game.settings.EXCHANGE_RATE;
// If we have an exchange rate, check if we have a totalRaw.
if ('undefined' !== typeof ex) preWin += '*' + ex;
// Need to compute total manually.
if ('undefined' === typeof totalWin) {
totalRaw = J.isNumber(data.totalRaw, 0);
totalWin = parseFloat(ex*totalRaw).toFixed(2);
totalWin = J.isNumber(totalWin, 0);
if (totalWin === false) {
node.err('EndScreen.updateDisplay: invalid : ' +
'totalWin calculation from totalRaw.');
totalWin = this.getText('errTotalWin');
err = true;
}
}
}
if (!err) {
if (totalWin !== preWin & preWin !== '') {
totalWin = preWin + ' = ' + totalWin;
}
totalWin += ' ' + this.totalWinCurrency;
}
}
exitCode = data.exit;
if ('string' !== typeof exitCode) {
node.err('EndScreen error, invalid exit code: ' + exitCode);
exitCode = this.getText('errExitCode');
}
totalHTML = this.totalWinInputElement;
exitCodeHTML = this.exitCodeInputElement;
if (totalHTML && this.showTotalWin) {
totalHTML.value = totalWin;
}
if (exitCodeHTML && this.showExitCode) {
exitCodeHTML.value = exitCode;
}
};
})(node);