UNPKG

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

Version:
293 lines (271 loc) 10.9 kB
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; });