@oat-sa/tao-test-runner-qti
Version:
TAO Test Runner QTI implementation
168 lines (156 loc) • 8.09 kB
JavaScript
define(['i18n', 'taoTests/runner/plugin', 'taoQtiTest/runner/helpers/map', 'taoQtiTest/runner/helpers/currentItem', 'taoQtiTest/runner/plugins/navigation/next/dialogConfirmNext', 'taoQtiTest/runner/helpers/navigation'], function (__, pluginFactory, mapHelper, currentItemHelper, dialogConfirmNext, navigationHelper) { 'use strict';
__ = __ && Object.prototype.hasOwnProperty.call(__, 'default') ? __['default'] : __;
pluginFactory = pluginFactory && Object.prototype.hasOwnProperty.call(pluginFactory, 'default') ? pluginFactory['default'] : pluginFactory;
mapHelper = mapHelper && Object.prototype.hasOwnProperty.call(mapHelper, 'default') ? mapHelper['default'] : mapHelper;
currentItemHelper = currentItemHelper && Object.prototype.hasOwnProperty.call(currentItemHelper, 'default') ? currentItemHelper['default'] : currentItemHelper;
dialogConfirmNext = dialogConfirmNext && Object.prototype.hasOwnProperty.call(dialogConfirmNext, 'default') ? dialogConfirmNext['default'] : dialogConfirmNext;
navigationHelper = navigationHelper && Object.prototype.hasOwnProperty.call(navigationHelper, 'default') ? navigationHelper['default'] : navigationHelper;
/**
* 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) 2019 (original work) Open Assessment Technologies SA ;
*/
/**
* Returns the configured plugin
*/
var linearNextItemWarning = pluginFactory({
name: 'linearNextItemWarning',
/**
* Initialize the plugin (called during runner's init)
*/
init() {
const self = this;
const testRunner = this.getTestRunner();
const testRunnerOptions = testRunner.getOptions();
const testStore = testRunner.getTestStore(); // we'll store user's checkbox choice in here
testStore.setVolatile(self.getName());
/**
* Retrieve the required categories of the current item
* @returns {Object} the calculator categories
*/
function getNextItemCategories() {
const testContext = testRunner.getTestContext();
const testMap = testRunner.getTestMap();
return {
nextPartWarning: mapHelper.hasItemCategory(testMap, testContext.itemIdentifier, 'nextPartWarning', true),
nextSectionWarning: mapHelper.hasItemCategory(testMap, testContext.itemIdentifier, 'nextSectionWarning', true)
};
}
/**
* Provides different variants of message text
* @param {String} action - 'next' or 'skip'
* @returns {String}
*/
function getCustomNextMessage(action) {
var customNextMessage;
var itemPartiallyAnswered = currentItemHelper.isAnswered(testRunner, true);
if (!itemPartiallyAnswered) {
customNextMessage = __('Are you sure you want to go to the next item? You will not be able to go back and provide an answer.');
} else if (action === 'next') {
customNextMessage = __('Are you sure you want to go to the next item? You will not be able to go back and change your answer.');
} else if (action === 'skip') {
customNextMessage = __('Are you sure you want to clear your answer and go to the next item? You will not be able to go back and provide an answer.');
} else {
// more generic message for default case:
customNextMessage = __('Are you sure you want to go to the next item? You will not be able to go back.');
}
return customNextMessage;
}
//plugin behavior
/**
* Checks configuration, shows a dialog asking to confirm the nav action
*
* @param {String} action - 'next' or 'skip'
* @returns {Promise} - resolves if dialog accepted or not shown, rejects if dialog cancelled
*/
function doNextWarning(action) {
testRunner.trigger('disablenav');
// Load testStore checkbox value (async)
return testStore.getStore(self.getName()).then(function (store) {
return store.getItem('dontShowLinearNextItemWarning').then(function (checkboxValue) {
var checkboxParams = null;
// Show the warning unless user has turned it off:
if (checkboxValue !== true) {
// Define checkbox only if enabled by config:
if (testRunnerOptions.enableLinearNextItemWarningCheckbox) {
checkboxParams = {
checked: checkboxValue,
submitChecked: function () {
store.setItem('dontShowLinearNextItemWarning', true);
},
submitUnchecked: function () {
store.setItem('dontShowLinearNextItemWarning', false);
}
};
}
return new Promise(function (resolve, reject) {
// show special dialog:
dialogConfirmNext(__('Go to the next item?'), getCustomNextMessage(action), resolve,
// if the test taker accepts
function cancel() {
// if he refuses
reject({
cancel: true
});
}, checkboxParams);
});
}
});
}).catch(function (err) {
// if the rejection is due to an error, rethrow it
if (err && err instanceof Error) {
throw err;
}
if (err && err.cancel === true) {
testRunner.trigger('enablenav');
return Promise.reject(); // to cancel the move
}
});
}
// Attach this plugin to 'next' & 'skip' events
testRunner.on('init', function () {
// Clear the stored checkbox value before each test:
testStore.getStore(self.getName()).then(function (store) {
store.setItem('dontShowLinearNextItemWarning', null);
});
}).before('move skip', function (e, type, scope) {
const context = testRunner.getTestContext();
const map = testRunner.getTestMap();
const item = testRunner.getCurrentItem();
const currentPart = testRunner.getCurrentPart();
const categories = getNextItemCategories();
const isLast = navigationHelper.isLast(map, context.itemIdentifier);
if (currentPart && currentPart.isLinear) {
// Do nothing if nextSection warning imminent:
if (scope === 'section' && categories.nextSectionWarning) {
return;
// Do nothing if endOfPart warning imminent:
} else if (categories.nextPartWarning) {
return;
// Do nothing if 'informational item':
} else if (item.informational) {
return;
// Show dialog if conditions met:
} else if (type === 'next' && !isLast && testRunnerOptions.forceEnableLinearNextItemWarning) {
return doNextWarning('next');
} else if (e.name === 'skip' && !isLast && testRunnerOptions.forceEnableLinearNextItemWarning) {
return doNextWarning('skip');
}
}
});
}
});
return linearNextItemWarning;
});