UNPKG

@oat-sa/tao-item-runner-qti

Version:
311 lines (272 loc) 12.2 kB
define(['jquery', 'lodash', 'context', 'module', 'iframeNotifier', 'taoQtiItem/qtiItem/core/Loader', 'taoQtiItem/qtiRunner/modalFeedback/inlineRenderer', 'taoQtiItem/qtiRunner/modalFeedback/modalRenderer'], function ($, _, context, module, iframeNotifier, ItemLoader, modalFeedbackInline, modalFeedbackModal) { 'use strict'; $ = $ && Object.prototype.hasOwnProperty.call($, 'default') ? $['default'] : $; _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _; context = context && Object.prototype.hasOwnProperty.call(context, 'default') ? context['default'] : context; module = module && Object.prototype.hasOwnProperty.call(module, 'default') ? module['default'] : module; iframeNotifier = iframeNotifier && Object.prototype.hasOwnProperty.call(iframeNotifier, 'default') ? iframeNotifier['default'] : iframeNotifier; ItemLoader = ItemLoader && Object.prototype.hasOwnProperty.call(ItemLoader, 'default') ? ItemLoader['default'] : ItemLoader; modalFeedbackInline = modalFeedbackInline && Object.prototype.hasOwnProperty.call(modalFeedbackInline, 'default') ? modalFeedbackInline['default'] : modalFeedbackInline; modalFeedbackModal = modalFeedbackModal && Object.prototype.hasOwnProperty.call(modalFeedbackModal, 'default') ? modalFeedbackModal['default'] : modalFeedbackModal; /* * 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) 2013 (original work) Open Assessment Techonologies SA (under the project TAO-PRODUCT); * * */ var timeout = (context.timeout > 0 ? context.timeout + 1 : 30) * 1000; var QtiRunner = function() { this.item = null; this.rpEngine = null; this.renderer = null; this.loader = null; this.itemApi = undefined; }; QtiRunner.prototype.updateItemApi = function() { var responses = this.getResponses(); var states = this.getStates(); var variables = []; // Transform responses into state variables. for (var key in states) { var value = states[key]; // This is where we set variables that will be collected and stored // as the Item State. We do not want to store large files into // the state, and force the client to download these files // all over again. We then transform them as a place holder, that will // simply indicate that a file composes the state. if (value.response && typeof value.response.base !== 'undefined') { for (var property in value.response.base) { if (property === 'file') { var file = value.response.base.file; // QTI File found! Replace it with an appropriate placeholder. // The data is base64('qti_file_datatype_placeholder_data') value.response = { base: { file: { name: file.name, mime: 'qti+application/octet-stream', data: 'cXRpX2ZpbGVfZGF0YXR5cGVfcGxhY2Vob2xkZXJfZGF0YQ==' } } }; } } } variables[key] = value; } //set all variables at once this.itemApi.setVariables(variables); // Save the responses that will be used for response processing. this.itemApi.saveResponses(responses); this.itemApi.resultApi.setQtiRunner(this); }; QtiRunner.prototype.setItemApi = function(itemApi) { this.itemApi = itemApi; var that = this; var oldStateVariables = JSON.stringify(itemApi.stateVariables); itemApi.onKill(function(killCallback) { // If the responses did not change, // just close gracefully. // Collect new responses and update item API. that.updateItemApi(); var newStateVariables = JSON.stringify(itemApi.stateVariables); // Store the results. if (oldStateVariables !== newStateVariables || itemApi.serviceApi.getHasBeenPaused()) { itemApi.submit(function() { // Send successful signal. itemApi.serviceApi.setHasBeenPaused(false); killCallback(0); }); } else { killCallback(0); } }); }; QtiRunner.prototype.setRenderer = function(renderer) { if (renderer.isRenderer) { this.renderer = renderer; } else { throw 'invalid renderer'; } }; QtiRunner.prototype.getLoader = function() { if (!this.loader) { this.loader = new ItemLoader(); } return this.loader; }; QtiRunner.prototype.loadItemData = function(data, callback) { var self = this; this.getLoader().loadItemData(data, function(item) { self.item = item; callback(self.item); }); }; QtiRunner.prototype.loadElements = function(elements, callback) { if (this.getLoader().item) { this.getLoader().loadElements(elements, callback); } else { throw 'QtiRunner : cannot load elements in empty item'; } }; QtiRunner.prototype.renderItem = function(data, done) { var self = this; done = _.isFunction(done) ? done : _.noop; var render = function() { if (!self.item) { throw 'cannot render item: empty item'; } if (self.renderer) { self.renderer.load(function() { self.item.setRenderer(self.renderer); self.item.render({}, $('#qti_item')); // Race between postRendering and timeout // postRendering waits for everything to be resolved or one reject Promise.race([ Promise.all(self.item.postRender()), new Promise(function(resolve, reject) { _.delay(reject, timeout, new Error('Post rendering ran out of time.')); }) ]) .then(function() { self.item.getContainer().on('responseChange', function(e, data) { if (data.interaction && data.interaction.attr('responseIdentifier') && data.response) { iframeNotifier.parent('responsechange', [ data.interaction.attr('responseIdentifier'), data.response ]); } }); self.initInteractionsResponse(); self.listenForThemeChange(); done(); }) .catch(function(err) { //in case of postRendering issue, we are also done done(); throw new Error('Error in post rendering : ' + err); }); }, self.getLoader().getLoadedClasses()); } else { throw 'cannot render item: no rendered set'; } }; if (typeof data === 'object') { this.loadItemData(data, render); } else { render(); } }; QtiRunner.prototype.initInteractionsResponse = function() { var self = this; if (self.item) { var interactions = self.item.getInteractions(); for (var i in interactions) { var interaction = interactions[i]; var responseId = interaction.attr('responseIdentifier'); self.itemApi.getVariable(responseId, function(values) { if (values) { interaction.setState(values); iframeNotifier.parent('stateready', [responseId, values]); } else { var states = self.getStates(); if (_.indexOf(states, responseId)) { self.itemApi.setVariable(responseId, states[responseId]); interaction.setState(states[responseId]); iframeNotifier.parent('stateready', [responseId, states[responseId]]); } } }); } } }; /** * If an event 'themechange' bubbles to "#qti_item" node * then we tell the renderer to switch the theme. */ QtiRunner.prototype.listenForThemeChange = function listenForThemeChange() { var self = this; var $container = this.renderer.getContainer(this.item); if (!$container.length) { $container = $('.qti-item'); } $container.off('themechange').on('themechange', function(e, themeName) { var themeLoader = self.renderer.getThemeLoader(); themeName = themeName || e.originalEvent.detail; if (themeLoader) { themeLoader.change(themeName); } }); }; QtiRunner.prototype.validate = function() { this.updateItemApi(); this.itemApi.finish(); }; QtiRunner.prototype.getResponses = function() { var responses = {}; var interactions = this.item.getInteractions(); _.forEach(interactions, function(interaction) { var response = {}; try { response = interaction.getResponse(); } catch (e) { console.error(e); } responses[interaction.attr('responseIdentifier')] = response; }); return responses; }; QtiRunner.prototype.getStates = function() { var states = {}; var interactions = this.item.getInteractions(); _.forEach(interactions, function(interaction) { var state = {}; try { state = interaction.getState(); } catch (e) { console.error(e); } states[interaction.attr('responseIdentifier')] = state; }); return states; }; QtiRunner.prototype.setResponseProcessing = function(callback) { this.rpEngine = callback; }; QtiRunner.prototype.showFeedbacks = function(itemSession, callback, onShowCallback) { var inlineDisplay = !!module.config().inlineModalFeedback; //currently only modal feedbacks are available if (inlineDisplay) { return modalFeedbackInline.showFeedbacks( this.item, this.getLoader(), this.renderer, itemSession, callback, onShowCallback ); } else { return modalFeedbackModal.showFeedbacks( this.item, this.getLoader(), this.renderer, itemSession, callback, onShowCallback ); } }; return QtiRunner; });