UNPKG

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

Version:
283 lines (248 loc) 13.3 kB
define(['jquery', 'lodash', 'i18n', 'context', 'taoQtiItem/qtiItem/core/Loader', 'taoQtiItem/qtiItem/core/Element', 'taoQtiItem/portableElementRegistry/ciRegistry', 'taoQtiItem/portableElementRegistry/icRegistry', 'taoQtiItem/portableElementRegistry/provider/sideLoadingProviderFactory', 'taoQtiItem/runner/rendererStrategies', 'taoQtiItem/runner/provider/manager/picManager', 'taoQtiItem/runner/provider/manager/userModules', 'taoQtiItem/qtiItem/helper/modalFeedback', 'taoItems/assets/manager', 'util/locale'], function ($, _, __, context, QtiLoader, Element, ciRegistry, icRegistry, sideLoadingProviderFactory, rendererStrategies, picManager, userModules, modalFeedbackHelper, manager, locale) { 'use strict'; $ = $ && Object.prototype.hasOwnProperty.call($, 'default') ? $['default'] : $; _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _; __ = __ && Object.prototype.hasOwnProperty.call(__, 'default') ? __['default'] : __; context = context && Object.prototype.hasOwnProperty.call(context, 'default') ? context['default'] : context; QtiLoader = QtiLoader && Object.prototype.hasOwnProperty.call(QtiLoader, 'default') ? QtiLoader['default'] : QtiLoader; Element = Element && Object.prototype.hasOwnProperty.call(Element, 'default') ? Element['default'] : Element; ciRegistry = ciRegistry && Object.prototype.hasOwnProperty.call(ciRegistry, 'default') ? ciRegistry['default'] : ciRegistry; icRegistry = icRegistry && Object.prototype.hasOwnProperty.call(icRegistry, 'default') ? icRegistry['default'] : icRegistry; sideLoadingProviderFactory = sideLoadingProviderFactory && Object.prototype.hasOwnProperty.call(sideLoadingProviderFactory, 'default') ? sideLoadingProviderFactory['default'] : sideLoadingProviderFactory; rendererStrategies = rendererStrategies && Object.prototype.hasOwnProperty.call(rendererStrategies, 'default') ? rendererStrategies['default'] : rendererStrategies; picManager = picManager && Object.prototype.hasOwnProperty.call(picManager, 'default') ? picManager['default'] : picManager; userModules = userModules && Object.prototype.hasOwnProperty.call(userModules, 'default') ? userModules['default'] : userModules; modalFeedbackHelper = modalFeedbackHelper && Object.prototype.hasOwnProperty.call(modalFeedbackHelper, 'default') ? modalFeedbackHelper['default'] : modalFeedbackHelper; locale = locale && Object.prototype.hasOwnProperty.call(locale, 'default') ? locale['default'] : locale; /* * 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) 2014-2020 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT); * */ var timeout = (context.timeout > 0 ? context.timeout + 1 : 30) * 1000; /** * @exports taoQtiItem/runner/provider/qti */ var qtiItemRuntimeProvider = { init: function (itemData, done) { var self = this; var rendererOptions = _.merge( { assetManager: this.assetManager }, _.pick(this.options, ['themes', 'preload', 'view']) ); const Renderer = rendererStrategies(rendererOptions.view).getRenderer(); this._renderer = new Renderer(rendererOptions); this._loader = new QtiLoader(); this._loader.loadItemData(itemData, function (item) { if (!item) { return self.trigger('error', __('Unable to load item from the given data.')); } self._item = item; self._renderer.load(function () { self._item.setRenderer(this); done(); }, this.getLoadedClasses()); }); }, render: function (elt, done, options) { var self = this; options = _.defaults(options || {}, { state: {} }); if (this._item) { try { //render item html elt.innerHTML = this._item.render({}); // apply RTL layout according to item language const $item = $(elt).find('.qti-item'); const $itemBody = $item.find('.qti-itemBody'); const itemDir = $itemBody.attr('dir'); if (!itemDir) { const itemLang = $item.attr('lang'); $itemBody.attr('dir', locale.getLanguageDirection(itemLang)); } } catch (e) { self.trigger('error', __('Error in template rendering: %s', e.message)); } try { if (options.portableElements) { //if the option to directly load portable elements is provided, use only this one if (options.portableElements.pci) { ciRegistry.resetProviders(); ciRegistry.registerProvider( 'pciDeliveryProvider', sideLoadingProviderFactory(options.portableElements.pci) ); } if (options.portableElements.pic) { icRegistry.resetProviders(); icRegistry.registerProvider( 'picDeliveryProvider', sideLoadingProviderFactory(options.portableElements.pic) ); } } // Race between postRendering and timeout // postRendering waits for everything to be resolved or one reject Promise.race([ Promise.all(this._item.postRender(options)), new Promise(function (resolve, reject) { _.delay( reject, timeout, new Error(__('It seems that there is an error during item loading. The error has been reported. The test will be paused.')) ); }) ]) .then(function () { $(elt) .off('responseChange') .on('responseChange', function () { self.trigger('statechange', self.getState()); self.trigger('responsechange', self.getResponses()); }) .off('endattempt') .on('endattempt', function (e, responseIdentifier) { self.trigger('endattempt', responseIdentifier || e.originalEvent.detail); }) .off('themechange') .on('themechange', function (e, themeName) { var themeLoader = self._renderer.getThemeLoader(); themeName = themeName || e.originalEvent.detail; if (themeLoader) { themeLoader.change(themeName); } }); /** * Lists the PIC provided by this item. * @event qti#listpic */ self.trigger('listpic', picManager.collection(self._item)); return userModules.load().then(done); }) .catch(function (renderingError) { done(); // in case of postRendering issue, we are also done const errorMsg = renderingError instanceof Error ? renderingError.message : renderingError; const error = new Error(__('Error in post rendering: %s', errorMsg)); error.unrecoverable = true; self.trigger('error', error); }); } catch (err) { self.trigger('error', __('Error in post rendering: %s', err.message)); } } }, /** * Clean up stuffs */ clear: function (elt, done) { var self = this; if (self._item) { Promise.all( this._item.getInteractions().map(function (interaction) { return interaction.clear(); }) ) .then(function () { self._item.clear(); $(elt).off('responseChange').off('endattempt').off('themechange').off('feedback').empty(); if (self._renderer) { self._renderer.unload(); } self._item = null; }) .then(done) .catch(function (err) { self.trigger('error', __('Something went wrong while destroying an interaction: %s', err.message)); }); } else { done(); } }, /** * Get state implementation. * @returns {Object} that represents the state */ getState: function getState() { var state = {}; if (this._item) { //get the state from interactions _.forEach(this._item.getInteractions(), function (interaction) { state[interaction.attr('responseIdentifier')] = interaction.getState(); }); //get the state from infoControls _.forEach(this._item.getElements(), function (element) { if (Element.isA(element, 'infoControl') && element.attr('id')) { state.pic = state.pic || {}; state.pic[element.attr('id')] = element.getState(); } }); } return state; }, /** * Set state implementation. * @param {Object} state - the state */ setState: function setState(state) { if (this._item && state) { //set interaction state _.forEach(this._item.getInteractions(), function (interaction) { var id = interaction.attr('responseIdentifier'); if (id && state[id]) { interaction.setState(state[id]); } }); //set info control state if (state.pic) { _.forEach(this._item.getElements(), function (element) { if (Element.isA(element, 'infoControl') && state.pic[element.attr('id')]) { element.setState(state.pic[element.attr('id')]); } }); } } }, getResponses: function () { var responses = {}; if (this._item) { _.reduce( this._item.getInteractions(), function (res, interaction) { responses[interaction.attr('responseIdentifier')] = interaction.getResponse(); return responses; }, responses ); } return responses; }, renderFeedbacks: function (feedbacks, itemSession, done) { var self = this; var _renderer = self._item.getRenderer(); var _loader = new QtiLoader(self._item); // loading feedbacks from response into the current item _loader.loadElements(feedbacks, function (item) { _renderer.load(function () { var renderingQueue = modalFeedbackHelper.getFeedbacks(item, itemSession); done(renderingQueue); }, this.getLoadedClasses()); }); } }; return qtiItemRuntimeProvider; });