// ---- /lib/amd/src/fragment.js ---- // This file is part of Moodle - http://moodle.org/ // // Moodle 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, either version 3 of the License, or // (at your option) any later version. // // Moodle 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 Moodle. If not, see . /** * A way to call HTML fragments to be inserted as required via JavaScript. * * @module core/fragment * @class fragment * @package core * @copyright 2016 Adrian Greeve * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 3.1 */ define('core/fragment', ['jquery', 'core/ajax'], function($, ajax) { /** * Loads an HTML fragment through a callback. * * @method loadFragment * @param {string} component Component where callback is located. * @param {string} callback Callback function name. * @param {integer} contextid Context ID of the fragment. * @param {object} params Parameters for the callback. * @return {Promise} JQuery promise object resolved when the fragment has been loaded. */ var loadFragment = function(component, callback, contextid, params) { // Change params into required webservice format. var formattedparams = []; for (var index in params) { formattedparams.push({ name: index, value: params[index] }); } // Ajax stuff. var deferred = $.Deferred(); var promises = ajax.call([{ methodname: 'core_get_fragment', args: { component: component, callback: callback, contextid: contextid, args: formattedparams } }], false); promises[0].done(function(data) { deferred.resolve(data); }).fail(function(ex) { deferred.reject(ex); }); return deferred.promise(); }; return /** @alias module:core/fragment */{ /** * Appends HTML and JavaScript fragments to specified nodes. * Callbacks called by this AMD module are responsible for doing the appropriate security checks * to access the information that is returned. This only does minimal validation on the context. * * @method fragmentAppend * @param {string} component Component where callback is located. * @param {string} callback Callback function name. * @param {integer} contextid Context ID of the fragment. * @param {object} params Parameters for the callback. * @return {Deferred} new promise that is resolved with the html and js. */ loadFragment: function(component, callback, contextid, params) { var promise = $.Deferred(); $.when(loadFragment(component, callback, contextid, params)).then(function(data) { var jsNodes = $(data.javascript); var allScript = ''; jsNodes.each(function(index, scriptNode) { scriptNode = $(scriptNode); var tagName = scriptNode.prop('tagName'); if (tagName && (tagName.toLowerCase() == 'script')) { if (scriptNode.attr('src')) { // We only reload the script if it was not loaded already. var exists = false; $('script').each(function(index, s) { if ($(s).attr('src') == scriptNode.attr('src')) { exists = true; } return !exists; }); if (!exists) { allScript += ' { '; allScript += ' node = document.createElement("script"); '; allScript += ' node.type = "text/javascript"; '; allScript += ' node.src = decodeURI("' + encodeURI(scriptNode.attr('src')) + '"); '; allScript += ' document.getElementsByTagName("head")[0].appendChild(node); '; allScript += ' } '; } } else { allScript += ' ' + scriptNode.text(); } } }); promise.resolve(data.html, allScript); }).fail(function(ex) { promise.reject(ex); }); return promise.promise(); } }; }); // ---- /lib/amd/src/modal_registry.js ---- // This file is part of Moodle - http://moodle.org/ // // Moodle 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, either version 3 of the License, or // (at your option) any later version. // // Moodle 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 Moodle. If not, see . /** * A registry for the different types of modal. * * @module core/modal_registry * @class modal_registry * @package core * @copyright 2016 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define('core/modal_registry', ['core/notification'], function(Notification) { // A singleton registry for all modules to access. Allows types to be // added at runtime. var registry = {}; /** * Get a registered type of modal. * * @method get * @param {string} type The type of modal to get * @return {object} The registered config for the modal */ var get = function(type) { return registry[type]; }; /** * Register a modal with the registry. * * @method register * @param {string} type The type of modal (must be unique) * @param {function} module The modal module (must be a constructor function of type core/modal) * @param {string} template The template name of the modal */ var register = function(type, module, template) { if (get(type)) { Notification.exception({message: "Modal of type '" + type + "' is already registered"}); } if (!module || typeof module !== 'function') { Notification.exception({message: "You must provide a modal module"}); } if (!template) { Notification.exception({message: "You must provide a modal template"}); } registry[type] = { module: module, template: template, }; }; return { register: register, get: get, }; }); // ---- /lib/amd/src/icon_system.js ---- // This file is part of Moodle - http://moodle.org/ // // Moodle 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, either version 3 of the License, or // (at your option) any later version. // // Moodle 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 Moodle. If not, see . /** * Icon System base module. * * @package core * @copyright 2017 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define('core/icon_system', ['jquery'], function($) { /** * Icon System abstract class. * * Any icon system needs to define a module extending this one and return this module name from the php icon_system class. */ var IconSystem = function() { }; /** * Initialise the icon system. * * @return {Promise} * @method init */ IconSystem.prototype.init = function() { return $.when(this); }; /** * Render an icon. * * The key, component and title come from either the pix mustache helper tag, or the call to templates.renderIcon. * The template is the pre-loaded template string matching the template from getTemplateName() in this class. * This function must return a string (not a promise) because it is used during the internal rendering of the mustache * template (which is unfortunately synchronous). To render the mustache template in this function call * core/mustache.render() directly and do not use any partials, blocks or helper functions in the template. * * @param {String} key * @param {String} component * @param {String} title * @param {String} template * @return {String} * @method renderIcon */ IconSystem.prototype.renderIcon = function(key, component, title, template) { // eslint-disable-line no-unused-vars throw new Error('Abstract function not implemented.'); }; /** * getTemplateName * * @return {String} * @method getTemplateName */ IconSystem.prototype.getTemplateName = function() { throw new Error('Abstract function not implemented.'); }; return /** @alias module:core/icon_system */ IconSystem; }); // ---- /lib/amd/src/icon_system_fontawesome.js ---- // This file is part of Moodle - http://moodle.org/ // // Moodle 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, either version 3 of the License, or // (at your option) any later version. // // Moodle 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 Moodle. If not, see . /** * Competency rule points module. * * @package core * @copyright 2017 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define('core/icon_system_fontawesome', ['core/icon_system', 'jquery', 'core/ajax', 'core/mustache', 'core/localstorage', 'core/url'], function(IconSystem, $, Ajax, Mustache, LocalStorage, Url) { var staticMap = null; var fetchMap = null; /** * IconSystemFontawesome */ var IconSystemFontawesome = function() { IconSystem.apply(this, arguments); }; IconSystemFontawesome.prototype = Object.create(IconSystem.prototype); /** * Prefetch resources so later calls to renderIcon can be resolved synchronously. * * @method init * @return {Promise} */ IconSystemFontawesome.prototype.init = function() { if (staticMap) { return $.when(this); } var map = LocalStorage.get('core/iconmap-fontawesome'); if (map) { map = JSON.parse(map); } if (map) { staticMap = map; return $.when(this); } if (fetchMap === null) { fetchMap = Ajax.call([{ methodname: 'core_output_load_fontawesome_icon_map', args: [] }], true, false)[0]; } return fetchMap.then(function(map) { staticMap = {}; $.each(map, function(index, value) { staticMap[value.component + '/' + value.pix] = value.to; }); LocalStorage.set('core/iconmap-fontawesome', JSON.stringify(staticMap)); return this; }.bind(this)); }; /** * Render an icon. * * @param {String} key * @param {String} component * @param {String} title * @param {String} template * @return {String} * @method renderIcon */ IconSystemFontawesome.prototype.renderIcon = function(key, component, title, template) { var mappedIcon = staticMap[component + '/' + key]; var unmappedIcon = false; if (typeof mappedIcon === "undefined") { var url = Url.imageUrl(key, component); unmappedIcon = { attributes: [ {name: 'src', value: url}, {name: 'alt', value: title}, {name: 'title', value: title} ] }; } var context = { key: mappedIcon, title: title, alt: title, unmappedIcon: unmappedIcon }; return Mustache.render(template, context); }; /** * Get the name of the template to pre-cache for this icon system. * * @return {String} * @method getTemplateName */ IconSystemFontawesome.prototype.getTemplateName = function() { return 'core/pix_icon_fontawesome'; }; return /** @alias module:core/icon_system_fontawesome */ IconSystemFontawesome; }); // ---- /lib/amd/src/modal_events.js ---- // This file is part of Moodle - http://moodle.org/ // // Moodle 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, either version 3 of the License, or // (at your option) any later version. // // Moodle 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 Moodle. If not, see . /** * Contain the events a modal can fire. * * @module core/modal_events * @class modal_events * @package core * @copyright 2016 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define('core/modal_events', [], function() { return { // Default events. shown: 'modal:shown', hidden: 'modal:hidden', destroyed: 'modal:destroyed', // ModalSaveCancel events. save: 'modal-save-cancel:save', cancel: 'modal-save-cancel:cancel', // ModalConfirm events. yes: 'modal-confirm:yes', no: 'modal-confirm:no', }; }); // ---- /lib/amd/src/key_codes.js ---- // This file is part of Moodle - http://moodle.org/ // // Moodle 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, either version 3 of the License, or // (at your option) any later version. // // Moodle 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 Moodle. If not, see . /** * A list of human readable names for the keycodes. * * @module core/key_codes * @class key_codes * @package core * @copyright 2016 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 3.2 */ define('core/key_codes', function() { return /** @alias module:core/key_codes */ { 'tab': 9, 'enter': 13, 'escape': 27, 'space': 32, 'end': 35, 'home': 36, 'arrowLeft': 37, 'arrowUp': 38, 'arrowRight': 39, 'arrowDown': 40, '8': 56, 'asterix': 106, 'pageUp': 33, 'pageDown': 34, }; }); // ---- /lib/amd/src/custom_interaction_events.js ---- // This file is part of Moodle - http://moodle.org/ // // Moodle 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, either version 3 of the License, or // (at your option) any later version. // // Moodle 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 Moodle. If not, see . /** * This module provides a wrapper to encapsulate a lot of the common combinations of * user interaction we use in Moodle. * * @module core/custom_interaction_events * @class custom_interaction_events * @package core * @copyright 2016 Ryan Wyllie * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 3.2 */ define('core/custom_interaction_events', ['jquery', 'core/key_codes'], function($, keyCodes) { // The list of events provided by this module. Namespaced to avoid clashes. var events = { activate: 'cie:activate', keyboardActivate: 'cie:keyboardactivate', escape: 'cie:escape', down: 'cie:down', up: 'cie:up', home: 'cie:home', end: 'cie:end', next: 'cie:next', previous: 'cie:previous', asterix: 'cie:asterix', scrollLock: 'cie:scrollLock', scrollTop: 'cie:scrollTop', scrollBottom: 'cie:scrollBottom', ctrlPageUp: 'cie:ctrlPageUp', ctrlPageDown: 'cie:ctrlPageDown', enter: 'cie:enter', }; /** * Check if the caller has asked for the given event type to be * registered. * * @method shouldAddEvent * @private * @param {string} eventType name of the event (see events above) * @param {array} include the list of events to be added * @return {bool} true if the event should be added, false otherwise. */ var shouldAddEvent = function(eventType, include) { include = include || []; if (include.length && include.indexOf(eventType) !== -1) { return true; } return false; }; /** * Check if any of the modifier keys have been pressed on the event. * * @method isModifierPressed * @private * @param {event} e jQuery event * @return {bool} true if shift, meta (command on Mac), alt or ctrl are pressed */ var isModifierPressed = function(e) { return (e.shiftKey || e.metaKey || e.altKey || e.ctrlKey); }; /** * Register a keyboard event that ignores modifier keys. * * @method addKeyboardEvent * @private * @param {object} element A jQuery object of the element to bind events to * @param {string} event The custom interaction event name * @param {int} keyCode The key code. */ var addKeyboardEvent = function(element, event, keyCode) { element.off('keydown.' + event).on('keydown.' + event, function(e) { if (!isModifierPressed(e)) { if (e.keyCode == keyCode) { $(e.target).trigger(event, [{originalEvent: e}]); } } }); }; /** * Trigger the activate event on the given element if it is clicked or the enter * or space key are pressed without a modifier key. * * @method addActivateListener * @private * @param {object} element jQuery object to add event listeners to */ var addActivateListener = function(element) { element.off('click.cie.activate').on('click.cie.activate', function(e) { $(e.target).trigger(events.activate, [{originalEvent: e}]); }); element.off('keydown.cie.activate').on('keydown.cie.activate', function(e) { if (!isModifierPressed(e)) { if (e.keyCode == keyCodes.enter || e.keyCode == keyCodes.space) { $(e.target).trigger(events.activate, [{originalEvent: e}]); } } }); }; /** * Trigger the keyboard activate event on the given element if the enter * or space key are pressed without a modifier key. * * @method addKeyboardActivateListener * @private * @param {object} element jQuery object to add event listeners to */ var addKeyboardActivateListener = function(element) { element.off('keydown.cie.keyboardactivate').on('keydown.cie.keyboardactivate', function(e) { if (!isModifierPressed(e)) { if (e.keyCode == keyCodes.enter || e.keyCode == keyCodes.space) { $(e.target).trigger(events.keyboardActivate, [{originalEvent: e}]); } } }); }; /** * Trigger the escape event on the given element if the escape key is pressed * without a modifier key. * * @method addEscapeListener * @private * @param {object} element jQuery object to add event listeners to */ var addEscapeListener = function(element) { addKeyboardEvent(element, events.escape, keyCodes.escape); }; /** * Trigger the down event on the given element if the down arrow key is pressed * without a modifier key. * * @method addDownListener * @private * @param {object} element jQuery object to add event listeners to */ var addDownListener = function(element) { addKeyboardEvent(element, events.down, keyCodes.arrowDown); }; /** * Trigger the up event on the given element if the up arrow key is pressed * without a modifier key. * * @method addUpListener * @private * @param {object} element jQuery object to add event listeners to */ var addUpListener = function(element) { addKeyboardEvent(element, events.up, keyCodes.arrowUp); }; /** * Trigger the home event on the given element if the home key is pressed * without a modifier key. * * @method addHomeListener * @private * @param {object} element jQuery object to add event listeners to */ var addHomeListener = function(element) { addKeyboardEvent(element, events.home, keyCodes.home); }; /** * Trigger the end event on the given element if the end key is pressed * without a modifier key. * * @method addEndListener * @private * @param {object} element jQuery object to add event listeners to */ var addEndListener = function(element) { addKeyboardEvent(element, events.end, keyCodes.end); }; /** * Trigger the next event on the given element if the right arrow key is pressed * without a modifier key in LTR mode or left arrow key in RTL mode. * * @method addNextListener * @private * @param {object} element jQuery object to add event listeners to */ var addNextListener = function(element) { // Left and right are flipped in RTL mode. var keyCode = $('html').attr('dir') == "rtl" ? keyCodes.arrowLeft : keyCodes.arrowRight; addKeyboardEvent(element, events.next, keyCode); }; /** * Trigger the previous event on the given element if the left arrow key is pressed * without a modifier key in LTR mode or right arrow key in RTL mode. * * @method addPreviousListener * @private * @param {object} element jQuery object to add event listeners to */ var addPreviousListener = function(element) { // Left and right are flipped in RTL mode. var keyCode = $('html').attr('dir') == "rtl" ? keyCodes.arrowRight : keyCodes.arrowLeft; addKeyboardEvent(element, events.previous, keyCode); }; /** * Trigger the asterix event on the given element if the asterix key is pressed * without a modifier key. * * @method addAsterixListener * @private * @param {object} element jQuery object to add event listeners to */ var addAsterixListener = function(element) { addKeyboardEvent(element, events.asterix, keyCodes.asterix); }; /** * Trigger the scrollTop event on the given element if the user scrolls to * the top of the given element. * * @method addScrollTopListener * @private * @param {object} element jQuery object to add event listeners to */ var addScrollTopListener = function(element) { element.off('scroll.cie.scrollTop').on('scroll.cie.scrollTop', function(e) { var scrollTop = element.scrollTop(); if (scrollTop === 0) { element.trigger(events.scrollTop, [{originalEvent: e}]); } }); }; /** * Trigger the scrollBottom event on the given element if the user scrolls to * the bottom of the given element. * * @method addScrollBottomListener * @private * @param {object} element jQuery object to add event listeners to */ var addScrollBottomListener = function(element) { element.off('scroll.cie.scrollBottom').on('scroll.cie.scrollBottom', function(e) { var scrollTop = element.scrollTop(); var innerHeight = element.innerHeight(); var scrollHeight = element[0].scrollHeight; if (scrollTop + innerHeight >= scrollHeight) { element.trigger(events.scrollBottom, [{originalEvent: e}]); } }); }; /** * Trigger the scrollLock event on the given element if the user scrolls to * the bottom or top of the given element. * * @method addScrollLockListener * @private * @param {jQuery} element jQuery object to add event listeners to */ var addScrollLockListener = function(element) { // Lock mousewheel scrolling within the element to stop the annoying window scroll. element.off('DOMMouseScroll.cie.DOMMouseScrollLock mousewheel.cie.mousewheelLock') .on('DOMMouseScroll.cie.DOMMouseScrollLock mousewheel.cie.mousewheelLock', function(e) { var scrollTop = element.scrollTop(); var scrollHeight = element[0].scrollHeight; var height = element.height(); var delta = (e.type == 'DOMMouseScroll' ? e.originalEvent.detail * -40 : e.originalEvent.wheelDelta); var up = delta > 0; if (!up && -delta > scrollHeight - height - scrollTop) { // Scrolling down past the bottom. element.scrollTop(scrollHeight); e.stopPropagation(); e.preventDefault(); e.returnValue = false; // Fire the scroll lock event. element.trigger(events.scrollLock, [{originalEvent: e}]); return false; } else if (up && delta > scrollTop) { // Scrolling up past the top. element.scrollTop(0); e.stopPropagation(); e.preventDefault(); e.returnValue = false; // Fire the scroll lock event. element.trigger(events.scrollLock, [{originalEvent: e}]); return false; } return true; }); }; /** * Trigger the ctrlPageUp event on the given element if the user presses the * control and page up key. * * @method addCtrlPageUpListener * @private * @param {object} element jQuery object to add event listeners to */ var addCtrlPageUpListener = function(element) { element.off('keydown.cie.ctrlpageup').on('keydown.cie.ctrlpageup', function(e) { if (e.ctrlKey) { if (e.keyCode == keyCodes.pageUp) { $(e.target).trigger(events.ctrlPageUp, [{originalEvent: e}]); } } }); }; /** * Trigger the ctrlPageDown event on the given element if the user presses the * control and page down key. * * @method addCtrlPageDownListener * @private * @param {object} element jQuery object to add event listeners to */ var addCtrlPageDownListener = function(element) { element.off('keydown.cie.ctrlpagedown').on('keydown.cie.ctrlpagedown', function(e) { if (e.ctrlKey) { if (e.keyCode == keyCodes.pageDown) { $(e.target).trigger(events.ctrlPageDown, [{originalEvent: e}]); } } }); }; /** * Trigger the enter event on the given element if the enter key is pressed * without a modifier key. * * @method addEnterListener * @private * @param {object} element jQuery object to add event listeners to */ var addEnterListener = function(element) { addKeyboardEvent(element, events.enter, keyCodes.enter); }; /** * Get the list of events and their handlers. * * @method getHandlers * @private * @return {object} object key of event names and value of handler functions */ var getHandlers = function() { var handlers = {}; handlers[events.activate] = addActivateListener; handlers[events.keyboardActivate] = addKeyboardActivateListener; handlers[events.escape] = addEscapeListener; handlers[events.down] = addDownListener; handlers[events.up] = addUpListener; handlers[events.home] = addHomeListener; handlers[events.end] = addEndListener; handlers[events.next] = addNextListener; handlers[events.previous] = addPreviousListener; handlers[events.asterix] = addAsterixListener; handlers[events.scrollLock] = addScrollLockListener; handlers[events.scrollTop] = addScrollTopListener; handlers[events.scrollBottom] = addScrollBottomListener; handlers[events.ctrlPageUp] = addCtrlPageUpListener; handlers[events.ctrlPageDown] = addCtrlPageDownListener; handlers[events.enter] = addEnterListener; return handlers; }; /** * Add all of the listeners on the given element for the requested events. * * @method define * @public * @param {object} element the DOM element to register event listeners on * @param {array} include the array of events to be triggered */ var define = function(element, include) { element = $(element); include = include || []; if (!element.length || !include.length) { return; } $.each(getHandlers(), function(eventType, handler) { if (shouldAddEvent(eventType, include)) { handler(element); } }); }; return /** @module core/custom_interaction_events */ { define: define, events: events, }; }); // ---- /lib/amd/src/tree.js ---- // This file is part of Moodle - http://moodle.org/ // // Moodle 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, either version 3 of the License, or // (at your option) any later version. // // Moodle 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 Moodle. If not, see . /** * Implement an accessible aria tree widget, from a nested unordered list. * Based on http://oaa-accessibility.org/example/41/. * * @module tool_lp/tree * @package core * @copyright 2015 Damyon Wiese * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define('core/tree', ['jquery'], function($) { // Private variables and functions. var SELECTORS = { ITEM: '[role=treeitem]', GROUP: '[role=treeitem]:has([role=group]), [role=treeitem][aria-owns], [role=treeitem][data-requires-ajax=true]', CLOSED_GROUP: '[role=treeitem]:has([role=group])[aria-expanded=false], [role=treeitem][aria-owns][aria-expanded=false], ' + '[role=treeitem][data-requires-ajax=true][aria-expanded=false]', FIRST_ITEM: '[role=treeitem]:first', VISIBLE_ITEM: '[role=treeitem]:visible', UNLOADED_AJAX_ITEM: '[role=treeitem][data-requires-ajax=true][data-loaded=false][aria-expanded=true]' }; /** * Constructor. * * @param {String} selector * @param {function} selectCallback Called when the active node is changed. */ var Tree = function(selector, selectCallback) { this.treeRoot = $(selector); this.treeRoot.data('activeItem', null); this.selectCallback = selectCallback; this.keys = { tab: 9, enter: 13, space: 32, pageup: 33, pagedown: 34, end: 35, home: 36, left: 37, up: 38, right: 39, down: 40, asterisk: 106 }; // Apply the standard default initialisation for all nodes, starting with the tree root. this.initialiseNodes(this.treeRoot); // Make the first item the active item for the tree so that it is added to the tab order. this.setActiveItem(this.treeRoot.find(SELECTORS.FIRST_ITEM)); // Create the cache of the visible items. this.refreshVisibleItemsCache(); // Create the event handlers for the tree. this.bindEventHandlers(); }; /** * Find all visible tree items and save a cache of them on the tree object. * * @method refreshVisibleItemsCache */ Tree.prototype.refreshVisibleItemsCache = function() { this.treeRoot.data('visibleItems', this.treeRoot.find(SELECTORS.VISIBLE_ITEM)); }; /** * Get all visible tree items. * * @method getVisibleItems * @return {Object} visible items */ Tree.prototype.getVisibleItems = function() { return this.treeRoot.data('visibleItems'); }; /** * Mark the given item as active within the tree and fire the callback for when the active item is set. * * @method setActiveItem * @param {object} item jquery object representing an item on the tree. */ Tree.prototype.setActiveItem = function(item) { var currentActive = this.treeRoot.data('activeItem'); if (item === currentActive) { return; } // Remove previous active from tab order. if (currentActive) { currentActive.attr('tabindex', '-1'); currentActive.attr('aria-selected', 'false'); } item.attr('tabindex', '0'); item.attr('aria-selected', 'true'); // Set the new active item. this.treeRoot.data('activeItem', item); if (typeof this.selectCallback === 'function') { this.selectCallback(item); } }; /** * Determines if the given item is a group item (contains child tree items) in the tree. * * @method isGroupItem * @param {object} item jquery object representing an item on the tree. * @returns {bool} */ Tree.prototype.isGroupItem = function(item) { return item.is(SELECTORS.GROUP); }; /** * Determines if the given item is a group item (contains child tree items) in the tree. * * @method isGroupItem * @param {object} item jquery object representing an item on the tree. * @returns {bool} */ Tree.prototype.getGroupFromItem = function(item) { return this.treeRoot.find('#' + item.attr('aria-owns')) || item.children('[role=group]'); }; /** * Determines if the given group item (contains child tree items) is collapsed. * * @method isGroupCollapsed * @param {object} item jquery object representing a group item on the tree. * @returns {bool} */ Tree.prototype.isGroupCollapsed = function(item) { return item.attr('aria-expanded') === 'false'; }; /** * Determines if the given group item (contains child tree items) can be collapsed. * * @method isGroupCollapsible * @param {object} item jquery object representing a group item on the tree. * @returns {bool} */ Tree.prototype.isGroupCollapsible = function(item) { return item.attr('data-collapsible') !== 'false'; }; /** * Performs the tree initialisation for all child items from the given node, * such as removing everything from the tab order and setting aria selected * on items. * * @method initialiseNodes * @param {object} node jquery object representing a node. */ Tree.prototype.initialiseNodes = function(node) { this.removeAllFromTabOrder(node); this.setAriaSelectedFalseOnItems(node); // Get all ajax nodes that have been rendered as expanded but haven't loaded the child items yet. var thisTree = this; node.find(SELECTORS.UNLOADED_AJAX_ITEM).each(function() { var unloadedNode = $(this); // Collapse and then expand to trigger the ajax loading. thisTree.collapseGroup(unloadedNode); thisTree.expandGroup(unloadedNode); }); }; /** * Removes all child DOM elements of the given node from the tab order. * * @method removeAllFromTabOrder * @param {object} node jquery object representing a node. */ Tree.prototype.removeAllFromTabOrder = function(node) { node.find('*').attr('tabindex', '-1'); this.getGroupFromItem($(node)).find('*').attr('tabindex', '-1'); }; /** * Find all child tree items from the given node and set the aria selected attribute to false. * * @method setAriaSelectedFalseOnItems * @param {object} node jquery object representing a node. */ Tree.prototype.setAriaSelectedFalseOnItems = function(node) { node.find(SELECTORS.ITEM).attr('aria-selected', 'false'); }; /** * Expand all group nodes within the tree. * * @method expandAllGroups */ Tree.prototype.expandAllGroups = function() { var thisTree = this; this.treeRoot.find(SELECTORS.CLOSED_GROUP).each(function() { var groupNode = $(this); thisTree.expandGroup($(this)).done(function() { thisTree.expandAllChildGroups(groupNode); }); }); }; /** * Find all child group nodes from the given node and expand them. * * @method expandAllChildGroups * @param {Object} item is the jquery id of the group. */ Tree.prototype.expandAllChildGroups = function(item) { var thisTree = this; this.getGroupFromItem(item).find(SELECTORS.CLOSED_GROUP).each(function() { var groupNode = $(this); thisTree.expandGroup($(this)).done(function() { thisTree.expandAllChildGroups(groupNode); }); }); }; /** * Expand a collapsed group. * * Handles expanding nodes that are ajax loaded (marked with a data-requires-ajax attribute). * * @method expandGroup * @param {Object} item is the jquery id of the parent item of the group. * @return {Object} a promise that is resolved when the group has been expanded. */ Tree.prototype.expandGroup = function(item) { var promise = $.Deferred(); // Ignore nodes that are explicitly maked as not expandable or are already expanded. if (item.attr('data-expandable') !== 'false' && this.isGroupCollapsed(item)) { // If this node requires ajax load and we haven't already loaded it. if (item.attr('data-requires-ajax') === 'true' && item.attr('data-loaded') !== 'true') { item.attr('data-loaded', false); // Get the closes ajax loading module specificed in the tree. var moduleName = item.closest('[data-ajax-loader]').attr('data-ajax-loader'); var thisTree = this; // Flag this node as loading. item.addClass('loading'); // Require the ajax module (must be AMD) and try to load the items. require([moduleName], function(loader) { // All ajax module must implement a "load" method. loader.load(item).done(function() { item.attr('data-loaded', true); // Set defaults on the newly constructed part of the tree. thisTree.initialiseNodes(item); thisTree.finishExpandingGroup(item); // Make sure no child elements of the item we just loaded are tabbable. item.removeClass('loading'); promise.resolve(); }); }); } else { this.finishExpandingGroup(item); promise.resolve(); } } else { promise.resolve(); } return promise; }; /** * Perform the necessary DOM changes to display a group item. * * @method finishExpandingGroup * @param {Object} item is the jquery id of the parent item of the group. */ Tree.prototype.finishExpandingGroup = function(item) { // Expand the group. var group = this.getGroupFromItem(item); group.attr('aria-hidden', 'false'); item.attr('aria-expanded', 'true'); // Update the list of visible items. this.refreshVisibleItemsCache(); }; /** * Collapse an expanded group. * * @method collapseGroup * @param {Object} item is the jquery id of the parent item of the group. */ Tree.prototype.collapseGroup = function(item) { // If the item is not collapsible or already collapsed then do nothing. if (!this.isGroupCollapsible(item) || this.isGroupCollapsed(item)) { return; } // Collapse the group. var group = this.getGroupFromItem(item); group.attr('aria-hidden', 'true'); item.attr('aria-expanded', 'false'); // Update the list of visible items. this.refreshVisibleItemsCache(); }; /** * Expand or collapse a group. * * @method toggleGroup * @param {Object} item is the jquery id of the parent item of the group. */ Tree.prototype.toggleGroup = function(item) { if (item.attr('aria-expanded') === 'true') { this.collapseGroup(item); } else { this.expandGroup(item); } }; /** * Handle a key down event - ie navigate the tree. * * @method handleKeyDown * @param {Object} item is the jquery id of the parent item of the group. * @param {Event} e The event. * @return {Boolean} */ // This function should be simplified. In the meantime.. // eslint-disable-next-line complexity Tree.prototype.handleKeyDown = function(item, e) { var currentIndex = this.getVisibleItems().index(item); if ((e.altKey || e.ctrlKey || e.metaKey) || (e.shiftKey && e.keyCode != this.keys.tab)) { // Do nothing. return true; } switch (e.keyCode) { case this.keys.home: { // Jump to first item in tree. this.getVisibleItems().first().focus(); e.stopPropagation(); return false; } case this.keys.end: { // Jump to last visible item. this.getVisibleItems().last().focus(); e.stopPropagation(); return false; } case this.keys.enter: { var links = item.children('a').length ? item.children('a') : item.children().not(SELECTORS.GROUP).find('a'); if (links.length) { window.location.href = links.first().attr('href'); } else if (this.isGroupItem(item)) { this.toggleGroup(item, true); } e.stopPropagation(); return false; } case this.keys.space: { if (this.isGroupItem(item)) { this.toggleGroup(item, true); } e.stopPropagation(); return false; } case this.keys.left: { var focusParent = function(tree) { // Get the immediate visible parent group item that contains this element. tree.getVisibleItems().filter(function() { return tree.getGroupFromItem($(this)).has(item).length; }).focus(); }; // If this is a goup item then collapse it and focus the parent group // in accordance with the aria spec. if (this.isGroupItem(item)) { if (this.isGroupCollapsed(item)) { focusParent(this); } else { this.collapseGroup(item); } } else { focusParent(this); } e.stopPropagation(); return false; } case this.keys.right: { // If this is a group item then expand it and focus the first child item // in accordance with the aria spec. if (this.isGroupItem(item)) { if (this.isGroupCollapsed(item)) { this.expandGroup(item); } else { // Move to the first item in the child group. this.getGroupFromItem(item).find(SELECTORS.ITEM).first().focus(); } } e.stopPropagation(); return false; } case this.keys.up: { if (currentIndex > 0) { var prev = this.getVisibleItems().eq(currentIndex - 1); prev.focus(); } e.stopPropagation(); return false; } case this.keys.down: { if (currentIndex < this.getVisibleItems().length - 1) { var next = this.getVisibleItems().eq(currentIndex + 1); next.focus(); } e.stopPropagation(); return false; } case this.keys.asterisk: { // Expand all groups. this.expandAllGroups(); e.stopPropagation(); return false; } } return true; }; /** * Handle a click (select). * * @method handleClick * @param {Object} item The jquery id of the parent item of the group. * @param {Event} e The event. * @return {Boolean} */ Tree.prototype.handleClick = function(item, e) { if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) { // Do nothing. return true; } // Update the active item. item.focus(); // If the item is a group node. if (this.isGroupItem(item)) { this.toggleGroup(item); } e.stopPropagation(); return true; }; /** * Handle a focus event. * * @method handleFocus * @param {Object} item The jquery id of the parent item of the group. * @param {Event} e The event. * @return {Boolean} */ Tree.prototype.handleFocus = function(item, e) { this.setActiveItem(item); e.stopPropagation(); return true; }; /** * Bind the event listeners we require. * * @method bindEventHandlers */ Tree.prototype.bindEventHandlers = function() { var thisObj = this; // Bind event handlers to the tree items. Use event delegates to allow // for dynamically loaded parts of the tree. this.treeRoot.on({ click: function(e) { return thisObj.handleClick($(this), e); }, keydown: function(e) { return thisObj.handleKeyDown($(this), e); }, focus: function(e) { return thisObj.handleFocus($(this), e); }, }, SELECTORS.ITEM); }; return /** @alias module:tool_lp/tree */ Tree; }); // ---- /lib/amd/src/chart_builder.js ---- // This file is part of Moodle - http://moodle.org/ // // Moodle 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, either version 3 of the License, or // (at your option) any later version. // // Moodle 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 Moodle. If not, see . /** * Chart builder. * * @package core * @copyright 2016 Frédéric Massart - FMCorz.net * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ define('core/chart_builder', ['jquery'], function($) { /** * Chart builder. * * @exports core/chart_builder */ var module = { /** * Make a chart instance. * * This takes data, most likely generated in PHP, and creates a chart instance from it * deferring most of the logic to {@link module:core/chart_base.create}. * * @param {Object} data The data. * @return {Promise} A promise resolved with the chart instance. */ make: function(data) { var deferred = $.Deferred(); require(['core/chart_' + data.type], function(Klass) { var instance = Klass.prototype.create(Klass, data); deferred.resolve(instance); }); return deferred.promise(); } }; return module; }); // ---- /lib/amd/src/inplace_editable.js ---- // This file is part of Moodle - http://moodle.org/ // // Moodle 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, either version 3 of the License, or // (at your option) any later version. // // Moodle 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 Moodle. If not, see . /** * AJAX helper for the inline editing a value. * * This script is automatically included from template core/inplace_editable * It registers a click-listener on [data-inplaceeditablelink] link (the "inplace edit" icon), * then replaces the displayed value with an input field. On "Enter" it sends a request * to web service core_update_inplace_editable, which invokes the specified callback. * Any exception thrown by the web service (or callback) is displayed as an error popup. * * @module core/inplace_editable * @package core * @copyright 2016 Marina Glancy * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 3.1 */ define('core/inplace_editable', ['jquery', 'core/ajax', 'core/templates', 'core/notification', 'core/str', 'core/config', 'core/url'], function($, ajax, templates, notification, str, cfg, url) { $('body').on('click keypress', '[data-inplaceeditable] [data-inplaceeditablelink]', function(e) { if (e.type === 'keypress' && e.keyCode !== 13) { return; } e.stopImmediatePropagation(); e.preventDefault(); var target = $(this), mainelement = target.closest('[data-inplaceeditable]'); var addSpinner = function(element) { element.addClass('updating'); var spinner = element.find('img.spinner'); if (spinner.length) { spinner.show(); } else { spinner = $('') .attr('src', url.imageUrl('i/loading_small')) .addClass('spinner').addClass('smallicon') ; element.append(spinner); } }; var removeSpinner = function(element) { element.removeClass('updating'); element.find('img.spinner').hide(); }; var updateValue = function(mainelement, value) { var pendingId = [ mainelement.attr('data-itemid'), mainelement.attr('data-component'), mainelement.attr('data-itemtype'), ].join('-'); M.util.js_pending(pendingId); addSpinner(mainelement); ajax .call([{ methodname: 'core_update_inplace_editable', args: { itemid: mainelement.attr('data-itemid'), component: mainelement.attr('data-component'), itemtype: mainelement.attr('data-itemtype'), value: value }, done: function(data) { var oldvalue = mainelement.attr('data-value'); templates.render('core/inplace_editable', data).done(function(html, js) { var newelement = $(html); templates.replaceNode(mainelement, newelement, js); newelement.find('[data-inplaceeditablelink]').focus(); newelement.trigger({type: 'updated', ajaxreturn: data, oldvalue: oldvalue}); M.util.js_complete(pendingId); }); }, fail: function(ex) { var e = $.Event('updatefailed', { exception: ex, newvalue: value }); removeSpinner(mainelement); M.util.js_complete(pendingId); mainelement.trigger(e); if (!e.isDefaultPrevented()) { notification.exception(ex); } } }], true); }; var turnEditingOff = function(el) { el.find('input').off(); el.find('select').off(); el.html(el.attr('data-oldcontent')); el.removeAttr('data-oldcontent'); el.removeClass('inplaceeditingon'); el.find('[data-inplaceeditablelink]').focus(); }; var turnEditingOffEverywhere = function() { $('span.inplaceeditable.inplaceeditingon').each(function() { turnEditingOff($(this)); }); }; var uniqueId = function(prefix, idlength) { var uniqid = prefix, i; for (i = 0; i < idlength; i++) { uniqid += String(Math.floor(Math.random() * 10)); } // Make sure this ID is not already taken by an existing element. if ($("#" + uniqid).length === 0) { return uniqid; } return uniqueId(prefix, idlength); }; var turnEditingOnText = function(el) { str.get_string('edittitleinstructions').done(function(s) { var instr = $('' + s + ''). attr('id', uniqueId('id_editinstructions_', 20)), inputelement = $(''). attr('id', uniqueId('id_inplacevalue_', 20)). attr('value', el.attr('data-value')). attr('aria-describedby', instr.attr('id')). addClass('ignoredirty'). addClass('form-control'), lbl = $(''). attr('for', inputelement.attr('id')); el.html('').append(instr).append(lbl).append(inputelement); inputelement.focus(); inputelement.select(); inputelement.on('keyup keypress focusout', function(e) { if (cfg.behatsiterunning && e.type === 'focusout') { // Behat triggers focusout too often. return; } if (e.type === 'keypress' && e.keyCode === 13) { // We need 'keypress' event for Enter because keyup/keydown would catch Enter that was // pressed in other fields. var val = inputelement.val(); turnEditingOff(el); updateValue(el, val); } if ((e.type === 'keyup' && e.keyCode === 27) || e.type === 'focusout') { // We need 'keyup' event for Escape because keypress does not work with Escape. turnEditingOff(el); } }); }); }; var turnEditingOnToggle = function(el, newvalue) { turnEditingOff(el); updateValue(el, newvalue); }; var turnEditingOnSelect = function(el, options) { var i, inputelement = $(''). attr('id', uniqueId('id_inplacevalue_', 20)). addClass('custom-select'), lbl = $('') .attr('for', inputelement.attr('id')); for (i in options) { inputelement .append($('