@oat-sa/tao-test-runner-qti
Version:
TAO Test Runner QTI implementation
373 lines (348 loc) • 15.4 kB
JavaScript
define(['jquery', 'lodash', 'i18n', 'ui/component', 'ui/hider', 'taoQtiTest/runner/plugins/controls/timer/component/countdown', 'handlebars', 'lib/handlebars/helpers', 'css!taoQtiTest/runner/plugins/controls/timer/component/css/timerbox.css'], function ($$1, _, __, component, hider, countdownFactory, Handlebars, Helpers0, timerbox_css) { 'use strict';
$$1 = $$1 && Object.prototype.hasOwnProperty.call($$1, 'default') ? $$1['default'] : $$1;
_ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
__ = __ && Object.prototype.hasOwnProperty.call(__, 'default') ? __['default'] : __;
component = component && Object.prototype.hasOwnProperty.call(component, 'default') ? component['default'] : component;
hider = hider && Object.prototype.hasOwnProperty.call(hider, 'default') ? hider['default'] : hider;
countdownFactory = countdownFactory && Object.prototype.hasOwnProperty.call(countdownFactory, 'default') ? countdownFactory['default'] : countdownFactory;
Handlebars = Handlebars && Object.prototype.hasOwnProperty.call(Handlebars, 'default') ? Handlebars['default'] : Handlebars;
Helpers0 = Helpers0 && Object.prototype.hasOwnProperty.call(Helpers0, 'default') ? Helpers0['default'] : Helpers0;
if (!Helpers0.__initialized) {
Helpers0(Handlebars);
Helpers0.__initialized = true;
}
var Template = Handlebars.template(function (Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers); data = data || {};
var buffer = "", stack1, helper, helperMissing=helpers.helperMissing, escapeExpression=this.escapeExpression, self=this, functionType="function";
function program1(depth0,data) {
var buffer = "", helper, options;
buffer += "\n <a href=\"#\" class=\"timer-toggler hidden\" title=\""
+ escapeExpression((helper = helpers.__ || (depth0 && depth0.__),options={hash:{},data:data},helper ? helper.call(depth0, "Hide timers", options) : helperMissing.call(depth0, "__", "Hide timers", options)))
+ "\" role=\"button\"><span class=\"icon-clock\"></span></a>\n ";
return buffer;
}
buffer += "<div class=\"timer-box\">\n ";
stack1 = helpers['if'].call(depth0, ((stack1 = (depth0 && depth0.zenMode)),stack1 == null || stack1 === false ? stack1 : stack1.enabled), {hash:{},inverse:self.noop,fn:self.program(1, program1, data),data:data});
if(stack1 || stack1 === 0) { buffer += stack1; }
buffer += "\n <div class=\"timer-wrapper\" aria-hidden=\"";
if (helper = helpers.ariaHidden) { stack1 = helper.call(depth0, {hash:{},data:data}); }
else { helper = (depth0 && depth0.ariaHidden); stack1 = typeof helper === functionType ? helper.call(depth0, {hash:{},data:data}) : helper; }
buffer += escapeExpression(stack1)
+ "\">\n\n </div>\n</div>\n";
return buffer;
});
function timerboxTpl(data, options, asString) {
var html = Template(data, options);
return (asString || true) ? html : $(html);
}
/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2018 (original work) Open Assessment Technologies SA ;
*/
/**
* Default config values, see below.
*/
var defaults = {
zenMode: {
enabled: true,
startHidden: false
}
};
/**
* Creates and initialize the timerbox component.
* Please not the component IS NOT rendered.
* You'll have to render it by yourself.
*
* @param {Object} config
* @param {Boolean} [config.zenMode = true] - zen mode adds a button to hide the countdowns to stay zen
* @param {Object[]} [config.timers] - the timers to start with
* @param {Boolean} [config.displayWarning = true] - let the countdown display their warning (contextual)
* @returns {timerbox} the component, initialized and rendered
*/
function timerboxFactory(config) {
var $zenModeToggler;
var $countdownContainer;
/**
* @typedef {Object} timerbox
*/
var timerbox = component({
/***
* Update the displayed timers.
* Compare the current and the given, it will add,remove and update timers.
*
* @param {Object[]} timers - the new timers
* @returns {Promise<Array>} resolves when all timers are up to date (with the result of all the update operations)
*
* @fires timerbox#update the update is done
*/
update: function update(timers) {
var self = this;
var updating = [];
//remove timers
var timerIdsToRemove = _.difference(_.keys(this.timers), _.keys(timers));
if (timerIdsToRemove.length) {
_.forEach(timerIdsToRemove, function (timerId) {
updating.push(self.removeTimer(timerId));
});
}
//add/update
_.forEach(timers, function (timer, id) {
if (typeof self.timers[id] === 'undefined') {
updating.push(self.addTimer(id, timer));
} else {
updating.push(self.updateTimer(id, timer));
}
});
return Promise.all(updating).then(function (results) {
//show the toggler only if there's timers
if (_.size(self.timers) > 0) {
hider.show($zenModeToggler);
} else {
hider.hide($zenModeToggler);
}
/**
* The timer box update is done
* @event timerbox#update
* @param {Object[]} timers - ALL update results (includes removals)
*/
self.trigger('update', results);
return results;
});
},
/**
* Get the current timers
* @return {Object[]} the timers
*/
getTimers: function getTimers() {
return this.timers;
},
/**
* Adds a new timer to the box
* @param {String} id - the timer unique identifier
* @param {Object} timer - the new timer
* @return {Promise<Object|Boolean>} resolves with the timer once added or false
*
* @fires timerbox#timerchange something changes in a timer
* @fires timerbox#timeradd a new timer is in the box
* @fires timerbox#timerstart a timer get started
* @fires timerbox#timerstop a timer get stopped
* @fires timerbox#timerend a timer get completed
* @fires timerbox#change spread from the countdown
*/
addTimer: function addTimer(id, timer) {
var self = this;
if (this.is('rendered') && typeof this.timers[id] === 'undefined') {
return new Promise(function (resolve) {
var countdown = countdownFactory($countdownContainer, _.defaults(timer, {
displayWarning: self.config.displayWarning
})).on('render', function () {
//keep track of the new timer
//and the countdown component
self.timers[id] = _.clone(timer);
self.timers[id].countdown = this;
/**
* The timers have changed (add, update, remove)
* @event timerbox#timerchange
* @param {String} action - add, update, remove
* @param {Object} timer
*/
self.trigger('timerchange', 'add', self.timers[id]);
/**
* A new timer is added
* @event timerbox#timeradd
* @param {Object} timer
*/
self.trigger('timeradd', self.timers[id]);
resolve(self.timers[id]);
}).on('start', function () {
/**
* A timer starts
* @event timerbox#timerstart
* @param {Object} timer
*/
self.trigger('timerstart', self.timers[id]);
}).on('stop', function () {
/**
* A timer stops
* @event timerbox#timerstop
* @param {Object} timer
*/
self.trigger('timerstop', self.timers[id]);
}).on('end', function () {
/**
* A timer ends
* @event timerbox#timerend
* @param {Object} timer
*/
self.trigger('timerend', self.timers[id]);
}).on('change', function (value) {
if (self.timers[id]) {
self.trigger('timertick', this.remainingTime, self.timers[id].scope, self.timers[id].unansweredQuestions); // propogate current timer data
//keep the current timer data in sync
self.timers[id].remainingTime = value;
}
});
countdown.spread(self, ['error', 'change', 'warn', 'warnscreenreader']);
});
}
return Promise.resolve(false);
},
/**
* Updates an existing timer
* @param {String} id - the timer unique identifier
* @param {Object} timer - the new timer
* @return {Promise<Object|Boolean>} resolves with the timer once updated or false
*
* @fires timerbox#timerchange something changes in a timer
* @fires timerbox#timerupdate an existing timer is updated
*/
updateTimer: function updateTimer(id, timer) {
if (this.is('rendered') && typeof this.timers[id] !== 'undefined') {
this.timers[id].remainingTime = timer.remainingTime;
this.timers[id].unansweredQuestions = timer.unansweredQuestions;
this.timers[id].extraTime = timer.extraTime;
if (_.isNumber(timer.remainingWithoutExtraTime)) {
this.timers[id].remainingWithoutExtraTime = timer.remainingWithoutExtraTime;
}
if (this.timers[id].countdown) {
this.timers[id].countdown.update(timer.remainingTime, timer.unansweredQuestions);
}
this.trigger('timerchange', 'update', this.timers[id]);
/**
* A timer has been updated with external values
* @event timerbox#timerupdate
* @param {Object} timer
*/
this.trigger('timerupdate', this.timers[id]);
return Promise.resolve(this.timers[id]);
}
return Promise.resolve(false);
},
/**
* Remove a timer
* @param {String} id - the timer unique identifier
* @return {Promise<Object|Boolean>} resolves with the timer once removed or false
*
* @fires timerbox#timerchange something changes in a timer
* @fires timerbox#timerremove a timer is removed
*/
removeTimer: function removeTimer(id) {
var self = this;
if (this.is('rendered') && typeof this.timers[id] !== 'undefined') {
return new Promise(function (resolve) {
/**
* Artifact function, remove the timer from the component index
*/
var deindex = function deindex() {
//keep a clone, without the component, for the event
var removed = _.omit(self.timers[id], 'countdown');
//remove the timer from the list
self.timers = _.omit(self.timers, id);
self.trigger('timerchange', 'remove', removed);
/**
* A timer has been updated with external values
* @event timerbox#timerupdate
* @param {Object} timer
*/
self.trigger('timerremove', removed);
resolve(removed);
};
if (self.timers[id].countdown) {
self.timers[id].countdown.on('destroy', deindex).destroy();
} else {
deindex();
}
});
}
return Promise.resolve();
},
/**
* Starts all the timers contained in the box
* @returns {timerbox} chains
*/
start: function start() {
_.forEach(this.timers, function (timer) {
if (timer.countdown) {
timer.countdown.start();
}
});
return this;
},
/**
* Stops all the timers contained in the box
* @returns {timerbox} chains
*/
stop: function stop() {
_.forEach(this.timers, function (timer) {
if (timer.countdown) {
timer.countdown.stop();
}
});
return this;
},
/**
* Show/hide the timers aka "zen mode"
* @returns {timerbox} chains
* @fires timerbox#zenchange
*/
toggleZenMode: function toggleZenMode() {
if (this.is('rendered') && this.config.zenMode.enabled) {
if (this.is('zen')) {
this.setState('zen', false);
$zenModeToggler.attr('title', __('Hide timers'));
} else {
this.setState('zen', true);
$zenModeToggler.attr('title', __('Show timers'));
}
/**
* @event timerbox#zenchange
* @param {Boolean} isZen
*/
this.trigger('zenchange', this.is('zen'));
}
return this;
}
}, defaults).on('init', function () {
//index the current timers
this.timers = {};
}).on('render', function () {
var self = this;
var $element = this.getElement();
//where we append the countdowns components
$countdownContainer = $$1('.timer-wrapper', $element);
//set up the zen mode toggler
if (this.config.zenMode.enabled) {
$zenModeToggler = $$1('.timer-toggler', $element);
self.setState('zen', !!self.config.zenMode.startHidden);
$zenModeToggler.on('click', function (e) {
e.preventDefault();
self.toggleZenMode();
});
}
//if timers are provided with the config, we perform the 1st update
if (this.config.timers) {
this.update(this.config.timers);
}
});
timerbox.setTemplate(timerboxTpl);
_.defer(function () {
timerbox.init(config);
});
return timerbox;
}
return timerboxFactory;
});