@oat-sa/tao-test-runner-qti
Version:
TAO Test Runner QTI implementation
293 lines (271 loc) • 10.9 kB
JavaScript
define(['lodash', 'jquery', 'taoTests/runner/plugin'], function (_, $, pluginFactory) { 'use strict';
_ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
$ = $ && Object.prototype.hasOwnProperty.call($, 'default') ? $['default'] : $;
pluginFactory = pluginFactory && Object.prototype.hasOwnProperty.call(pluginFactory, 'default') ? pluginFactory['default'] : pluginFactory;
/**
* 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) 2016 (original work) Open Assessment Technologies SA ;
*/
/**
* Event namespace
* @type {String}
*/
var ns = '.collapser';
/**
* Name of the CSS class used to collapse the buttons
* @type {String}
*/
var noLabelCls = 'tool-label-collapsed';
/**
* Name of the CSS class used to collapse the buttons and allow to expand on mouse over
* @type {String}
*/
var noLabelHoverCls = 'tool-label-collapsed-hover';
/**
* Name of the CSS class used to hide the label of the button independently of responsiveness
* @type {string}
*/
var labelHiddenCls = 'no-tool-label';
/**
* Name of the CSS class for separators
* @type {string}
*/
var separatorCls = 'separator';
/**
* Default plugin options
* @type {Object}
*/
var defaults = {
collapseTools: true,
// collapse the tools buttons
collapseNavigation: false,
// collapse the navigation buttons
collapseInOrder: false,
// collapse any button in the given order
hover: false,
// expand when the mouse is over a button,
/**
* Allow to set manually which buttons should collapse and in which order.
* This can be set by triggering the "collapser-set-order" event on the testRunner.
* Given as an array of jQuery selectors: first index will be the first to be collapsed, and so on.
* If no selector is given for a button, then this one will never collapse.
* ex:
* var collapseOrder = [
* '[data-control="highlight-clear"],[data-control="highlight-trigger"]', // those will collapse first...
* '[data-control="hide-review"]', // this one second...
* '[data-control="set-item-flag"]', // third...
* ... // ...
* ];
* @type {String[]}
*/
collapseOrder: []
};
var $window = $(window);
/**
* Creates the responsiveness collapser plugin.
* Reduce the size of the action bar tools when the available space is below the needed one.
*/
var collapser = pluginFactory({
name: 'collapser',
/**
* Installs the plugin (called when the runner bind the plugin)
*/
init: function init() {
const testRunner = this.getTestRunner();
const config = Object.assign({}, defaults, this.getConfig());
const collapseCls = config.hover ? noLabelHoverCls : noLabelCls;
var areaBroker = testRunner.getAreaBroker();
var $actionsBar = areaBroker.getArea('actionsBar'),
$toolbox = areaBroker.getToolboxArea(),
$navigation = areaBroker.getNavigationArea();
var allCollapsibles, availableWidth, previousAvailableWidth;
/**
* Get a reference of all collapsibles
*/
function buildCollapsiblesList() {
// use the given order to build the collapsibles list or generate on in natural order
if (config.collapseInOrder && config.collapseOrder.length) {
allCollapsibles = getCollapsiblesFromConfig();
}
// get values from DOM, grouped by prefix
else if (config.collapseInOrder) {
allCollapsibles = getSortedCollapsiblesFromDom();
}
// get all in one chunk
else {
allCollapsibles = getUnsortedCollapsiblesFromDom();
}
}
/**
* @param {jQuery} $element
* @returns {number} Size difference, in pixels, between collapsed and expanded state of $element
*/
function getExtraWidth($element) {
var expandedWidth, collapsedWidth;
$element.removeClass(collapseCls);
expandedWidth = $element.outerWidth(true);
$element.addClass(collapseCls);
collapsedWidth = $element.outerWidth(true);
$element.removeClass(collapseCls);
return expandedWidth - collapsedWidth;
}
/**
* Expand or collapse elements
*/
function toggleCollapsibles() {
availableWidth = getAvailableWidth();
availableWidth < previousAvailableWidth ? collapseInOrder() : expandInOrder();
previousAvailableWidth = availableWidth;
}
function collapseInOrder() {
var collapsiblesCopy = _.clone(allCollapsibles),
toCollapse;
while (collapseNeeded() && collapsiblesCopy.length) {
toCollapse = collapsiblesCopy.shift();
toCollapse.$elements.addClass(collapseCls);
}
}
function collapseNeeded() {
return getToolbarWidth() > getAvailableWidth();
}
function expandInOrder() {
_.forEachRight(allCollapsibles, function (toExpand) {
if (toExpand.$elements.hasClass(collapseCls)) {
if (expandPossible(toExpand.extraWidth)) {
toExpand.$elements.removeClass(collapseCls);
} else {
return false;
}
}
});
}
function expandPossible(extraWidth) {
return getToolbarWidth() + extraWidth < getAvailableWidth();
}
function getAvailableWidth() {
// Scrollbars are commonly between ~12px and ~18px in width. Subtracting 20px from the available width
// makes sure that scrollbars are always taken in account. The worst case scenario is that the buttons
// start to collapse, although they would still have had 20px available.
return $actionsBar.width() - 20;
}
function getToolbarWidth() {
return $toolbox.outerWidth(true) + $navigation.outerWidth(true);
}
/**
* Parse DOM for controls that can be collapsed
* @returns {*|jQuery|HTMLElement}
*/
function getControlsFromDom() {
var $controls = $(),
selector = '>ul>[data-control]';
if (config.collapseTools) {
$controls = $controls.add($toolbox.find(selector).not(`.${labelHiddenCls}`).not(`.${separatorCls}`));
}
if (config.collapseNavigation) {
$controls = $controls.add($navigation.find(selector).not(`.${labelHiddenCls}`).not(`.${separatorCls}`));
}
return $controls;
}
/**
* Get allCollapsibles based on configuration
*
* @returns {Array}
*/
function getCollapsiblesFromConfig() {
return _.compact(config.collapseOrder.map(function (selector) {
// some buttons are collapsed by configuration, some other are only separators: we should leave them alone
var $elements = $(selector).not(`.${labelHiddenCls}`).not(`.${separatorCls}`);
var extraWidth = 0;
if ($elements.length) {
$elements.each(function () {
extraWidth += getExtraWidth($(this));
});
return {
$elements: $elements,
extraWidth: extraWidth
};
}
return false;
}));
}
/**
* Get allCollapsibles based on DOM
* Build the collapse order from the left to the right, related elements are grouped.
*
* @returns {Array}
*/
function getSortedCollapsiblesFromDom() {
var $controlElements = getControlsFromDom(),
_allCollapsibles = [],
order = {};
// group items by prefix
// eg. zoomIn and zoomOut -> zoom
$controlElements.each(function () {
var ctrl = this.dataset.control,
// re makes group `foo` from `foo-bar`, `fooBar` and `foo_bar`
// if we do not have a prefix use the control name as key to ensure uniqueness
key = ctrl.substring(0, ctrl.search(/[A-Z-_]/)) || ctrl;
order[key] = order[key] || $();
order[key] = order[key].add($(this));
});
// move items to allCollapsibles
_.forOwn(order, function ($elements) {
var extraWidth = 0;
$elements.each(function () {
extraWidth += getExtraWidth($(this));
});
_allCollapsibles.push({
$elements: $elements,
extraWidth: extraWidth
});
});
return _.compact(_allCollapsibles);
}
/**
* Get allCollapsibles based on DOM, all buttons will be collapsed at once
*
* @returns {Array}
*/
function getUnsortedCollapsiblesFromDom() {
var $elements = getControlsFromDom(),
_allCollapsibles = [],
extraWidth = 0;
$elements.each(function () {
extraWidth += getExtraWidth($(this));
});
_allCollapsibles.push({
$elements: $elements,
extraWidth: extraWidth
});
return _.compact(_allCollapsibles);
}
$window.on(`resize${ns}`, _.throttle(function () {
testRunner.trigger('collapseTools');
}, 40));
testRunner.after('renderitem loaditem', function () {
previousAvailableWidth = Infinity;
buildCollapsiblesList();
testRunner.trigger('collapseTools');
}).on(`collapseTools${ns}`, function () {
toggleCollapsibles();
});
},
destroy: function destroy() {
$window.off(ns);
}
});
return collapser;
});