░▒▓████████████████████████████████▓▒░ ░▒▓█ ▓▒░ ░▒▓█ ~ S W A M P ~ ▓▒░ ░▒▓█ ▓▒░ ░▒▓████████████████████████████████▓▒░
pplmvsvm
LOCATION:
/var/www/moodle/public/question/type/ordering/amd/src
☗ ROOT
↻ REFRESH
✎ CARVE FLESH
EDITING: drag_reorder.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 <http://www.gnu.org/licenses/>. /* * To make a set of things draggable, create a new instance of this object passing the * necessary config, as explained in the comment on the constructor. * * @package qtype_ordering\drag_reorder * @copyright 2018 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ 'use strict'; import $ from 'jquery'; import drag from 'core/dragdrop'; import Templates from 'core/templates'; import Notification from 'core/notification'; import {getString} from 'core/str'; import {prefetchString} from 'core/prefetch'; export default class DragReorder { // Class variables handling state. config = {reorderStart: undefined, reorderEnd: undefined}; // Config object with some basic definitions. dragStart = null; // Information about when and where the drag started. originalOrder = null; // Array of ids that's used to compare the state after the drag event finishes. // DOM Nodes and jQuery representations. orderList = null; // Order list (HTMLElement). itemDragging = null; // Item being moved by dragging (jQuery object). proxy = null; // Drag proxy (jQuery object). /** * Constructor. * * To make a list draggable, create a new instance of this object, passing the necessary config. * For example: * { * // Selector for the list (or lists) to be reordered. * list: 'ul.my-list', * * // Selector, relative to the list selector, for the items that can be moved. * item: '> li', * * // While the proxy is being dragged, this class is added to the item being moved. * // You can probably use "osep-itemmoving" here. * itemMovingClass: "osep-itemmoving", * * // This is a callback which, when called with the DOM node for an item, * // returns the string that uniquely identifies each item. * // Therefore, the result of the drag action will be represented by the array * // obtained by calling this method on each item in the list in order. * idGetter: function(item) { return node.id; }, * * // Function that will be called when a re-order starts (optional, can be not set). * // Useful if you need to save information about the initial state. * // This function should have two parameters. The first will be a * // jQuery object for the list that was reordered, the second will * // be the jQuery object for the item moved - which will not yet have been moved. * // Note, it is quite possible for reorderStart to be called with no * // subsequent call to reorderDone. * reorderStart: function($list, $item) { ... } * * // Function that will be called when a drag has finished, and the list * // has been reordered. This function should have three parameters. The first will be * // a jQuery object for the list that was reordered, the second will be the jQuery * // object for the item moved, and the third will be the new order, which is * // an array of ids obtained by calling idGetter on each item in the list in order. * // This callback will only be called in the new order is actually different from the old order. * reorderDone: function($list, $item, newOrder) { ... } * * // Function that is always called when a re-order ends (optional, can be not set) * // whether the order has changed. Useful if you need to undo changes made * // in reorderStart, since reorderDone is only called if the new order is different * // from the original order. * reorderEnd: function($list, $item) { ... } * } * * There is a subtlety, If you have items in your list that do not have a drag handle, * they are considered to be placeholders in otherwise empty containers. * * @param {Object} config As above. */ constructor(config) { // Bring in the config to our state. this.config = config; // Get the list we'll be working with this time. this.orderList = document.querySelector(this.config.list); this.startListeners(); } /** * Start the listeners for the list. */ startListeners() { /** * Handle mousedown or touchstart events on the list. * * @param {Event} e The event. */ const pointerHandle = e => { if (e.target.closest(this.config.item) && !e.target.closest(this.config.actionButton)) { this.itemDragging = $(e.target.closest(this.config.item)); const details = drag.prepare(e); if (details.start) { this.startDrag(e, details); } } }; // Set up the list listeners for moving list items around. this.orderList.addEventListener('mousedown', pointerHandle); this.orderList.addEventListener('touchstart', pointerHandle); this.orderList.addEventListener('click', this.itemMovedByClick.bind(this)); } /** * Start dragging. * * @param {Event} e The event which is either mousedown or touchstart. * @param {Object} details Object with start (boolean flag) and x, y (only if flag true) values */ startDrag(e, details) { this.dragStart = { time: new Date().getTime(), x: details.x, y: details.y }; if (typeof this.config.reorderStart !== 'undefined') { this.config.reorderStart(this.itemDragging.closest(this.config.list), this.itemDragging); } this.originalOrder = this.getCurrentOrder(); Templates.renderForPromise('qtype_ordering/proxyhtml', { itemHtml: this.itemDragging.html(), itemClassName: this.itemDragging.attr('class'), listClassName: this.orderList.classList.toString(), proxyStyles: [ `width: ${this.itemDragging.outerWidth()}px;`, `height: ${this.itemDragging.outerHeight()}px;`, ].join(' '), }).then(({html, js}) => { this.proxy = $(Templates.appendNodeContents(document.body, html, js)[0]); this.proxy.css(this.itemDragging.offset()); this.itemDragging.addClass(this.config.itemMovingClass); this.updateProxy(); // Start drag. drag.start(e, this.proxy, this.dragMove.bind(this), this.dragEnd.bind(this)); }).catch(Notification.exception); } /** * Move the proxy to the current mouse position. */ dragMove() { let closestItem = null; let closestDistance = null; this.orderList.querySelectorAll(this.config.item).forEach(element => { const distance = this.distanceBetweenElements(element); if (closestItem === null || distance < closestDistance) { closestItem = $(element); closestDistance = distance; } }); if (closestItem[0] === this.itemDragging[0]) { return; } // Set offset depending on if item is being dragged downwards/upwards. const offsetValue = this.midY(this.proxy) < this.midY(closestItem) ? 20 : -20; if (this.midY(this.proxy) + offsetValue < this.midY(closestItem)) { this.itemDragging.insertBefore(closestItem); } else { this.itemDragging.insertAfter(closestItem); } this.updateProxy(); } /** * Update proxy's position. */ updateProxy() { const items = [...this.orderList.querySelectorAll(this.config.item)]; for (let i = 0; i < items.length; ++i) { if (this.itemDragging[0] === items[i]) { this.proxy.find('li').attr('value', i + 1); break; } } } /** * End dragging. */ dragEnd() { if (typeof this.config.reorderEnd !== 'undefined') { this.config.reorderEnd(this.itemDragging.closest(this.config.list), this.itemDragging); } if (!this.arrayEquals(this.originalOrder, this.getCurrentOrder())) { // Order has changed, call the callback. this.config.reorderDone(this.itemDragging.closest(this.config.list), this.itemDragging, this.getCurrentOrder()); getString('moved', 'qtype_ordering', { item: this.itemDragging.find('[data-itemcontent]').text().trim(), position: this.itemDragging.index() + 1, total: this.orderList.querySelectorAll(this.config.item).length }).then((str) => { this.config.announcementRegion.innerHTML = str; }); } // Clean up after the drag is finished. this.proxy.remove(); this.proxy = null; this.itemDragging.removeClass(this.config.itemMovingClass); this.itemDragging = null; this.dragStart = null; } /** * Handles the movement of an item by click. * * @param {MouseEvent} e The pointer event. */ itemMovedByClick(e) { const actionButton = e.target.closest(this.config.actionButton); if (actionButton) { this.itemDragging = $(e.target.closest(this.config.item)); // Store the current state of the list. this.originalOrder = this.getCurrentOrder(); switch (actionButton.dataset.action) { case 'move-backward': e.preventDefault(); e.stopPropagation(); if (this.itemDragging.prev().length) { this.itemDragging.prev().insertAfter(this.itemDragging); } break; case 'move-forward': e.preventDefault(); e.stopPropagation(); if (this.itemDragging.next().length) { this.itemDragging.next().insertBefore(this.itemDragging); } break; } // After we have potentially moved the item, we need to check if the order has changed. if (!this.arrayEquals(this.originalOrder, this.getCurrentOrder())) { // Order has changed, call the callback. this.config.reorderDone(this.itemDragging.closest(this.config.list), this.itemDragging, this.getCurrentOrder()); // When moving an item to the first or last position, the button that was clicked will be hidden. // In this case, we need to focus the other button. if (!this.itemDragging.prev().length) { // Focus the 'next' action button. this.itemDragging.find('[data-action="move-forward"]').focus(); } else if (!this.itemDragging.next().length) { // Focus the 'previous' action button. this.itemDragging.find('[data-action="move-backward"]').focus(); } getString('moved', 'qtype_ordering', { item: this.itemDragging.find('[data-itemcontent]').text().trim(), position: this.itemDragging.index() + 1, total: this.orderList.querySelectorAll(this.config.item).length }).then((str) => { this.config.announcementRegion.innerHTML = str; }); } } } /** * Get the x-position of the middle of the DOM node represented by the given jQuery object. * * @param {jQuery} node jQuery wrapping a DOM node. * @returns {number} Number the x-coordinate of the middle (left plus half outerWidth). */ midX(node) { return node.offset().left + node.outerWidth() / 2; } /** * Get the y-position of the middle of the DOM node represented by the given jQuery object. * * @param {jQuery} node jQuery wrapped DOM node. * @returns {number} Number the y-coordinate of the middle (top plus half outerHeight). */ midY(node) { return node.offset().top + node.outerHeight() / 2; } /** * Calculate the distance between the centres of two elements. * * @param {HTMLLIElement} element DOM node of a list item. * @return {number} number the distance in pixels. */ distanceBetweenElements(element) { const [e1, e2] = [$(element), $(this.proxy)]; const [dx, dy] = [this.midX(e1) - this.midX(e2), this.midY(e1) - this.midY(e2)]; return Math.sqrt(dx * dx + dy * dy); } /** * Get the current order of the list containing itemDragging. * * @returns {Array} Array of strings, the id of each element in order. */ getCurrentOrder() { return this.itemDragging.closest(this.config.list).find(this.config.item).map( (index, item) => { return this.config.idGetter(item); }).get(); } /** * Compare two arrays which contain primitive types to see if they are equal. * @param {Array} a1 first array. * @param {Array} a2 second array. * @return {Boolean} boolean true if they both contain the same elements in the same order, else false. */ arrayEquals(a1, a2) { return a1.length === a2.length && a1.every((v, i) => { return v === a2[i]; }); } /** * Initialise one ordering question. * * @param {String} sortableid id of ul for this question. * @param {String} responseid id of hidden field for this question. */ static init(sortableid, responseid) { new DragReorder({ actionButton: '[data-action]', announcementRegion: document.querySelector(`#${sortableid}-announcement`), list: 'ul#' + sortableid, item: 'li.sortableitem', itemMovingClass: "current-drop", idGetter: item => { return item.id; }, reorderDone: (list, item, newOrder) => { $('input#' + responseid)[0].value = newOrder.join(','); } }); prefetchString('qtype_ordering', 'moved'); } }
CANCEL
Name
Type
Size
Modified
Actions
↩ ..
DIR
—
—
📄 drag_reorder.js
JS
14.7 KB
2026-02-11 01:44
EDIT