tao-test/app/taoQtiTest/views/js/testRunner/actionBarHook.js

273 lines
8.4 KiB
JavaScript

/*
* 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) 2015 (original work) Open Assessment Technologies SA ;
*
*/
/**
* This module allows adding extra buttons in the action bar of the test runner
*
*/
define([
'jquery',
'lodash',
'core/errorHandler',
'core/promise'
], function ($, _, errorHandler, Promise) {
'use strict';
/**
* Events namespace
* @type {String}
* @private
*/
var _ns = '.actionBarHook';
/**
* We need to access the root document to listen for some events
* @type {jQuery}
* @private
*/
var $doc = $(document);
/**
* List of loaded and visible hooks
* @type {Object}
* @private
*/
var tools = {};
/**
* Flag set to true when the item is loaded
* @type {Boolean}
* @private
*/
var itemIsLoaded = false;
// catch the item loaded event
$doc.off(_ns).on('serviceloaded' + _ns, function() {
itemIsLoaded = true;
_.forEach(tools, function(tool) {
triggerItemLoaded(tool);
});
});
/**
* Check that the toolConfig is correct
*
* @param {Object} toolconfig
* @param {String} toolconfig.hook - the amd module to be loaded to initialize the button
* @param {String} [toolconfig.label] - the label to be displayed in the button
* @param {String} [toolconfig.icon] - the icon to be displayed in the button
* @param {String} [toolconfig.title] - the title to be displayed in the button
* @param {Array} [toolconfig.items] - an optional list of menu items
* @returns {Boolean}
*/
function isValidConfig(toolconfig) {
return !!(_.isObject(toolconfig) && toolconfig.hook);
}
/**
* Triggers the itemLoaded event inside the provided actionBar hook
* @param {Object} tool
*/
function triggerItemLoaded(tool) {
if (tool && tool.itemLoaded) {
tool.itemLoaded();
}
}
/**
* Init a test runner button from its config
*
* @param {String} id
* @param {Object|String} toolconfig
* @param {String} toolconfig.hook - the amd module to be loaded to initialize the button
* @param {String} [toolconfig.label] - the label to be displayed in the button
* @param {String} [toolconfig.icon] - the icon to be displayed in the button
* @param {String} [toolconfig.title] - the title to be displayed in the button
* @param {Array} [toolconfig.items] - an optional list of menu items
* @param {Object} testContext - the complete state of the test
* @param {Object} testRunner - the test runner instance
* @fires ready.actionBarHook when the hook has been initialized
* @returns {Promise}
*/
function initQtiTool($toolsContainer, id, toolconfig, testContext, testRunner) {
// the tool is always initialized before the item is loaded, so we can safely false the flag
itemIsLoaded = false;
tools[id] = null;
if (_.isString(toolconfig)) {
toolconfig = {
hook: toolconfig
};
}
return new Promise(function(resolve) {
if (isValidConfig(toolconfig)) {
require([toolconfig.hook], function (hook) {
var $button;
var $existingBtn;
if (isValidHook(hook)) {
//init the control
hook.init(id, toolconfig, testContext, testRunner);
//if an instance of the tool is already attached, remove it:
$existingBtn = $toolsContainer.children('[data-control="' + id + '"]');
if ($existingBtn.length) {
hook.clear($existingBtn);
$existingBtn.remove();
}
//check if the tool is to be available
if (hook.isVisible()) {
//keep access to the tool
tools[id] = hook;
// renders the button from the config
$button = hook.render();
//only attach the button to the dom when everything is ready
_appendInOrder($toolsContainer, $button);
//ready !
$button.trigger('ready' + _ns, [hook]);
//fires the itemLoaded event if the item has already been loaded
if (itemIsLoaded) {
triggerItemLoaded(hook);
}
}
resolve(hook);
} else {
errorHandler.throw(_ns, 'invalid hook format');
resolve(null);
}
}, function (e) {
errorHandler.throw(_ns, 'the hook amd module cannot be found');
resolve(null);
});
} else {
errorHandler.throw(_ns, 'invalid tool config format');
resolve(null);
}
});
}
/**
* Append a dom element $button to a $container in a specific order
* The orders are provided by data-order attribute set to the $button
*
* @param {JQuery} $container
* @param {JQuery} $button
*/
function _appendInOrder($container, $button) {
var $after, $before;
var order = $button.data('order');
if ('last' === order) {
$container.append($button);
} else if ('first' === order) {
$container.prepend($button);
} else {
order = _.parseInt(order);
if (!_.isNaN(order)) {
$container.children('.action').each(function () {
var $btn = $(this),
_order = $btn.data('order');
if ('last' === _order) {
$before = $btn;
$after = null;
} else if ('first' === _order) {
$before = null;
$after = $btn;
} else {
_order = _.parseInt(_order);
if (_.isNaN(_order) || _order > order) {
$before = $btn;
$after = null;
//stops here because $container children returns the dom elements in the dom order
return false;
} else if (_order === order) {
$after = $btn;
} else if (_order < order) {
$after = $btn;
$before = null;
}
}
});
if ($after) {
$after.after($button);
} else if ($before) {
$before.before($button);
} else {
$container.append($button);
}
} else {
//unordered buttons are append at the end (including when order equals 0)
$container.append($button);
}
}
}
/**
* Check if the hook object is valid
*
* @param {Object} hook
* @param {Function} hook.init
* @param {Function} hook.clear
* @param {Function} hook.isVisible
* @returns {Boolean}
*/
function isValidHook(hook) {
return (_.isObject(hook) && _(['init', 'render', 'clear', 'isVisible']).reduce(function (result, method) {
return result && _.isFunction(hook[method]);
}, true));
}
return {
isValid: isValidConfig,
initQtiTool: initQtiTool
};
});