UNPKG

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

Version:
189 lines (179 loc) 10.3 kB
define(['jquery', 'lodash', 'util/typeCaster', 'taoTests/runner/plugin', 'taoQtiTest/runner/helpers/verticalWriting', 'css!taoQtiTest/runner/plugins/content/itemScrolling/css/scrolling.css'], function ($, _, typeCaster, pluginFactory, verticalWriting, scrolling_css) { 'use strict'; $ = $ && Object.prototype.hasOwnProperty.call($, 'default') ? $['default'] : $; _ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _; typeCaster = typeCaster && Object.prototype.hasOwnProperty.call(typeCaster, 'default') ? typeCaster['default'] : typeCaster; 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) 2020 (original work) Open Assessment Technologies SA; */ const minimalAcceptableSizePx = 20; /** * Creates the loading bar plugin. * Displays a loading bar when a blocking task is running */ var itemScrolling = pluginFactory({ name: 'itemScrolling', /** * Initializes the plugin (called during runner's init) */ init: function init() { const testRunner = this.getTestRunner(); let $root; testRunner.on('renderitem', () => { $root = testRunner.getAreaBroker().getContainer(); const isItemVerticalWriting = verticalWriting.getIsItemWritingModeVerticalRl(); const $itemScrollContainer = getItemScrollContainer(isItemVerticalWriting); if ($itemScrollContainer.length) { this.itemResizeCallback = _.throttle(() => requestAnimationFrame(adaptBlockSize), 200); this.itemResizeObserver = new ResizeObserver(this.itemResizeCallback); this.itemResizeObserver.observe($itemScrollContainer.get(0)); } }).on('unloaditem', () => { if (this.itemResizeObserver) { this.itemResizeObserver.disconnect(); } if (this.itemResizeCallback && this.itemResizeCallback.cancel) { this.itemResizeCallback.cancel(); } }); function getItemScrollContainer(isItemVerticalWriting) { return isItemVerticalWriting ? $root.find('.qti-itemBody') : $root.find('.test-runner-sections > .content-wrapper'); } function adaptBlockSize() { const isItemVerticalWriting = verticalWriting.getIsItemWritingModeVerticalRl(); const $itemScrollContainer = getItemScrollContainer(isItemVerticalWriting); const $blockContainers = $itemScrollContainer.find('[data-scrolling="true"]'); const innerItemSize = getItemRunnerBlockSize($itemScrollContainer, isItemVerticalWriting) - getQtiItemAndItemBodyPadding($itemScrollContainer, isItemVerticalWriting) - 2; const contentBlockSize = innerItemSize - getGridRowBlockMargin() - getExtraGridRowBlockSize(isItemVerticalWriting) - getSpaceAroundQtiContent($itemScrollContainer, isItemVerticalWriting); $blockContainers.each(function () { const $block = $(this); const isBlockVerticalWriting = verticalWriting.getIsWritingModeVerticalRl($block); const isDifferentWritingMode = isBlockVerticalWriting !== isItemVerticalWriting; const isScrollable = typeCaster.strToBool($block.attr('data-scrolling') || 'false'); const selectedBlockSize = parseFloat($block.attr('data-scrolling-height')) || 100; const containerParent = $block.parent().closest('[data-scrolling="true"]'); const containerBlockSize = isItemVerticalWriting ? containerParent.width() : containerParent.height(); const normalSizeProp = isItemVerticalWriting ? 'width' : 'height'; const maxSizeProp = isItemVerticalWriting ? 'max-width' : 'max-height'; const sizeProp = isDifferentWritingMode ? normalSizeProp : maxSizeProp; if ($block.length && isScrollable) { $block.data('scrollable', true); const cssObj = {}; if (containerParent.length > 0) { cssObj[sizeProp] = `${containerBlockSize * (selectedBlockSize * 0.01)}px`; } else { const maxSize = contentBlockSize * (selectedBlockSize * 0.01); if (maxSize > minimalAcceptableSizePx) { cssObj[sizeProp] = `${maxSize}px`; } else { // contentBlockSize could turn out to be negative or very small because of // 'getExtraGridRowBlockSize' [other grid-row's content is unexpectedly long] or 'getSpaceAroundQtiContent'; // then we show block with natural size (no scrollbars); // but for block with different writing-mode need to define *some* size. if (isBlockVerticalWriting !== isItemVerticalWriting) { cssObj[sizeProp] = `${innerItemSize / 2}px`; } } } $block.css(cssObj); } }); } function getItemRunnerBlockSize($itemScrollContainer, isItemVerticalWriting) { const rect = $itemScrollContainer.get(0).getBoundingClientRect(); return isItemVerticalWriting ? rect.width : rect.height; } // if layout is: text (scroll-block) on top/bottom of the question (interaction), then: // ensure whole item fits on the screen (fit interaction, and let scroll-block fill remaining space; // if there are several scroll-blocks, split this remaining space between them based on their `data-scrolling-height ) // notes: // - should have been enabled by the special option? it may not always be a desirable behavior! // - applies to `.grid-row`, but not to `.colrow` function getExtraGridRowBlockSize(isItemVerticalWriting) { var $gridRows = $root.find('.qti-itemBody > .grid-row'), extraBlockSize = 0; $gridRows.each(function () { var $gridRow = $(this), $blockContainer = $gridRow.find('[data-scrolling="true"]'); if (!$blockContainer.length) { extraBlockSize += isItemVerticalWriting ? $gridRow.outerWidth(true) : $gridRow.outerHeight(true); } }); return extraBlockSize; } // rubrick's block height function getSpaceAroundQtiContent($itemScrollContainer, isItemVerticalWriting) { if (isItemVerticalWriting) { return 0; } const itemScrollContainer = $itemScrollContainer.get(0); const $qtiContent = $root.find('#qti-content'); if ($qtiContent.length && itemScrollContainer.contains($qtiContent.get(0))) { const qtiContentRect = $qtiContent.get(0).getBoundingClientRect(); const itemRunnerContainerRect = itemScrollContainer.getBoundingClientRect(); return qtiContentRect.top - itemRunnerContainerRect.top + itemScrollContainer.scrollTop; } return 0; } // all elems between item-scroll-container and itemBody, including itemBody function getQtiItemAndItemBodyPadding($itemScrollContainer, isItemVerticalWriting) { let padding = 0; const $itemBody = $root.find('.qti-itemBody'); const propNames = isItemVerticalWriting ? ['padding-left', 'padding-right'] : ['padding-top', 'padding-bottom']; const parentsUntil = $itemScrollContainer.get(0).contains($itemBody.get(0)) ? $itemBody.parentsUntil($itemScrollContainer).toArray() : []; for (const el of [...parentsUntil, $itemBody.get(0)]) { if ($itemScrollContainer.get(0).contains(el)) { const compStyle = getComputedStyle(el); for (const propName of propNames) { padding += parseFloat(compStyle.getPropertyValue(propName) || 0); } } } return padding; } // a) dual-column: one row, 2 columns - one with full-height scrollable block, other with interaction // b) 'getExtraGridRowBlockSize' where text (scroll-block) on top/bottom of the question (interaction) should fit on screen function getGridRowBlockMargin() { let margin = 0; const $gridRows = $root.find('.qti-itemBody > .grid-row'); $gridRows.each(function () { const $gridRow = $(this); const $blockContainer = $gridRow.find('[data-scrolling="true"]'); if ($blockContainer.length) { // "getExtraGridRowBlockSize" includes height of rows *without* scroll containers; // here we include margins of rows *with* scroll containers const $col = $blockContainer.closest('[class*=" col-"], [class^="col-"]'); const margins = [[$col, 'margin-block-start'], [$gridRow, 'margin-block-start'], [$col, 'margin-block-end'], [$gridRow, 'margin-block-end']]; for (const [$el, prop] of margins) { margin += $el.length ? parseFloat(getComputedStyle($el.get(0)).getPropertyValue(prop) || 0) : 0; } } }); return margin; } }, destroy: function destroy() { if (this.itemResizeObserver) { this.itemResizeObserver.disconnect(); } if (this.itemResizeCallback && this.itemResizeCallback.cancel) { this.itemResizeCallback.cancel(); } } }); return itemScrolling; });