AutorÃa | Ultima modificación | Ver Log |
{"version":3,"file":"drag_reorder.min.js","sources":["../src/drag_reorder.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/*\n * To make a set of things draggable, create a new instance of this object passing the\n * necessary config, as explained in the comment on the constructor.\n *\n * @package qtype_ordering\\drag_reorder\n * @copyright 20
18 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\n'use strict';\n\nimport $ from 'jquery';\nimport drag from 'core/dragdrop';\nimport Templates from 'core/templates';\nimport Notification from 'core/notification';\nimport {getString} from 'core/str';\nimport {prefetchString} from 'core/prefetch';\n\nexport default class DragReorder {\n\n // Class variables handling state.\n config = {reorderStart: undefined, reorderEnd: undefined}; // Config object with some basic definitions.\n dragStart = null; // Information about when and where the drag started.\n originalOrder = null; // Array of ids that's used to compare the state after the drag event finishes.\n\n // DOM Nodes and jQuery representations.\n orderList = null; // Order list (HTMLElement).\n itemDragging = null; // Item being moved by dragging (jQuery object).\n proxy = null; // Drag proxy (jQuery object).\n\n /**\n * Constructor.\n *\n * To make a list draggable,
create a new instance of this object, passing the necessary config.\n * For example:\n * {\n * // Selector for the list (or lists) to be reordered.\n * list: 'ul.my-list',\n *\n * // Selector, relative to the list selector, for the items that can be moved.\n * item: '> li',\n *\n * // While the proxy is being dragged, this class is added to the item being moved.\n * // You can probably use \"osep-itemmoving\" here.\n * itemMovingClass: \"osep-itemmoving\",\n *\n * // This is a callback which, when called with the DOM node for an item,\n * // returns the string that uniquely identifies each item.\n * // Therefore, the result of the drag action will be represented by the array\n * // obtained by calling this method on each item in the list in order.\n * idGetter: function(item) { return node.id; },\n *\n * // Function that will be called when a re-order starts (optional,
can be not set).\n * // Useful if you need to save information about the initial state.\n * // This function should have two parameters. The first will be a\n * // jQuery object for the list that was reordered, the second will\n * // be the jQuery object for the item moved - which will not yet have been moved.\n * // Note, it is quite possible for reorderStart to be called with no\n * // subsequent call to reorderDone.\n * reorderStart: function($list, $item) { ... }\n *\n * // Function that will be called when a drag has finished, and the list\n * // has been reordered. This function should have three parameters. The first will be\n * // a jQuery object for the list that was reordered, the second will be the jQuery\n * // object for the item moved, and the third will be the new order, which is\n * // an array of ids obtained by calling idGetter on each item in the list in order.\n * // T
his callback will only be called in the new order is actually different from the old order.\n * reorderDone: function($list, $item, newOrder) { ... }\n *\n * // Function that is always called when a re-order ends (optional, can be not set)\n * // whether the order has changed. Useful if you need to undo changes made\n * // in reorderStart, since reorderDone is only called if the new order is different\n * // from the original order.\n * reorderEnd: function($list, $item) { ... }\n * }\n *\n * There is a subtlety, If you have items in your list that do not have a drag handle,\n * they are considered to be placeholders in otherwise empty containers.\n *\n * @param {Object} config As above.\n */\n constructor(config) {\n // Bring in the config to our state.\n this.config = config;\n\n // Get the list we'll be working with this time.\n this.orderList = document.querySelector(this.config.list);\n\
n this.startListeners();\n }\n\n /**\n * Start the listeners for the list.\n */\n startListeners() {\n /**\n * Handle mousedown or touchstart events on the list.\n *\n * @param {Event} e The event.\n */\n const pointerHandle = e => {\n if (e.target.closest(this.config.item) && !e.target.closest(this.config.actionButton)) {\n this.itemDragging = $(e.target.closest(this.config.item));\n const details = drag.prepare(e);\n if (details.start) {\n this.startDrag(e, details);\n }\n }\n };\n // Set up the list listeners for moving list items around.\n this.orderList.addEventListener('mousedown', pointerHandle);\n this.orderList.addEventListener('touchstart', pointerHandle);\n this.orderList.addEventListener('click', this.itemMovedByClick.bind(this));\n }\n\n /**\n * Start dragging.\n *\n * @param
{Event} e The event which is either mousedown or touchstart.\n * @param {Object} details Object with start (boolean flag) and x, y (only if flag true) values\n */\n startDrag(e, details) {\n this.dragStart = {\n time: new Date().getTime(),\n x: details.x,\n y: details.y\n };\n\n if (typeof this.config.reorderStart !== 'undefined') {\n this.config.reorderStart(this.itemDragging.closest(this.config.list), this.itemDragging);\n }\n\n this.originalOrder = this.getCurrentOrder();\n\n Templates.renderForPromise('qtype_ordering/proxyhtml', {\n itemHtml: this.itemDragging.html(),\n itemClassName: this.itemDragging.attr('class'),\n listClassName: this.orderList.classList.toString(),\n proxyStyles: [\n `width: ${this.itemDragging.outerWidth()}px;`,\n `height: ${this.itemDragging.outerHeight()}px;`,\n ].join(' '),\n }).then(({html,
js}) => {\n this.proxy = $(Templates.appendNodeContents(document.body, html, js)[0]);\n this.proxy.css(this.itemDragging.offset());\n\n this.itemDragging.addClass(this.config.itemMovingClass);\n\n this.updateProxy();\n // Start drag.\n drag.start(e, this.proxy, this.dragMove.bind(this), this.dragEnd.bind(this));\n }).catch(Notification.exception);\n }\n\n /**\n * Move the proxy to the current mouse position.\n */\n dragMove() {\n let closestItem = null;\n let closestDistance = null;\n this.orderList.querySelectorAll(this.config.item).forEach(element => {\n const distance = this.distanceBetweenElements(element);\n if (closestItem === null || distance < closestDistance) {\n closestItem = $(element);\n closestDistance = distance;\n }\n });\n\n if (closestItem[0] === this.itemDragging[0]) {\n return;\n }\n\n
// Set offset depending on if item is being dragged downwards/upwards.\n const offsetValue = this.midY(this.proxy) < this.midY(closestItem) ? 20 : -20;\n if (this.midY(this.proxy) + offsetValue < this.midY(closestItem)) {\n this.itemDragging.insertBefore(closestItem);\n } else {\n this.itemDragging.insertAfter(closestItem);\n }\n this.updateProxy();\n }\n\n /**\n * Update proxy's position.\n */\n updateProxy() {\n const items = [...this.orderList.querySelectorAll(this.config.item)];\n for (let i = 0; i < items.length; ++i) {\n if (this.itemDragging[0] === items[i]) {\n this.proxy.find('li').attr('value', i + 1);\n break;\n }\n }\n }\n\n /**\n * End dragging.\n */\n dragEnd() {\n if (typeof this.config.reorderEnd !== 'undefined') {\n this.config.reorderEnd(this.itemDragging.closest(this.config.list), this.itemDragging);\n
}\n\n if (!this.arrayEquals(this.originalOrder, this.getCurrentOrder())) {\n // Order has changed, call the callback.\n this.config.reorderDone(this.itemDragging.closest(this.config.list), this.itemDragging, this.getCurrentOrder());\n\n getString('moved', 'qtype_ordering', {\n item: this.itemDragging.find('[data-itemcontent]').text().trim(),\n position: this.itemDragging.index() + 1,\n total: this.orderList.querySelectorAll(this.config.item).length\n }).then((str) => {\n this.config.announcementRegion.innerHTML = str;\n });\n }\n\n // Clean up after the drag is finished.\n this.proxy.remove();\n this.proxy = null;\n this.itemDragging.removeClass(this.config.itemMovingClass);\n this.itemDragging = null;\n this.dragStart = null;\n }\n\n /**\n * Handles the movement of an item by click.\n *\n * @param {MouseEvent} e The
pointer event.\n */\n itemMovedByClick(e) {\n const actionButton = e.target.closest(this.config.actionButton);\n if (actionButton) {\n this.itemDragging = $(e.target.closest(this.config.item));\n\n // Store the current state of the list.\n this.originalOrder = this.getCurrentOrder();\n\n switch (actionButton.dataset.action) {\n case 'move-backward':\n e.preventDefault();\n e.stopPropagation();\n if (this.itemDragging.prev().length) {\n this.itemDragging.prev().insertAfter(this.itemDragging);\n }\n break;\n case 'move-forward':\n e.preventDefault();\n e.stopPropagation();\n if (this.itemDragging.next().length) {\n this.itemDragging.next().insertBefore(this.itemDragging);\n }\n break;\
n }\n\n // After we have potentially moved the item, we need to check if the order has changed.\n if (!this.arrayEquals(this.originalOrder, this.getCurrentOrder())) {\n // Order has changed, call the callback.\n this.config.reorderDone(this.itemDragging.closest(this.config.list), this.itemDragging, this.getCurrentOrder());\n\n // When moving an item to the first or last position, the button that was clicked will be hidden.\n // In this case, we need to focus the other button.\n if (!this.itemDragging.prev().length) {\n // Focus the 'next' action button.\n this.itemDragging.find('[data-action=\"move-forward\"]').focus();\n } else if (!this.itemDragging.next().length) {\n // Focus the 'previous' action button.\n this.itemDragging.find('[data-action=\"move-backward\"]').focus();\n }\n\n
getString('moved', 'qtype_ordering', {\n item: this.itemDragging.find('[data-itemcontent]').text().trim(),\n position: this.itemDragging.index() + 1,\n total: this.orderList.querySelectorAll(this.config.item).length\n }).then((str) => {\n this.config.announcementRegion.innerHTML = str;\n });\n }\n }\n }\n\n /**\n * Get the x-position of the middle of the DOM node represented by the given jQuery object.\n *\n * @param {jQuery} node jQuery wrapping a DOM node.\n * @returns {number} Number the x-coordinate of the middle (left plus half outerWidth).\n */\n midX(node) {\n return node.offset().left + node.outerWidth() / 2;\n }\n\n /**\n * Get the y-position of the middle of the DOM node represented by the given jQuery object.\n *\n * @param {jQuery} node jQuery wrapped DOM node.\n * @returns {number} Number the y-coordinate of the mid
dle (top plus half outerHeight).\n */\n midY(node) {\n return node.offset().top + node.outerHeight() / 2;\n }\n\n /**\n * Calculate the distance between the centres of two elements.\n *\n * @param {HTMLLIElement} element DOM node of a list item.\n * @return {number} number the distance in pixels.\n */\n distanceBetweenElements(element) {\n const [e1, e2] = [$(element), $(this.proxy)];\n const [dx, dy] = [this.midX(e1) - this.midX(e2), this.midY(e1) - this.midY(e2)];\n return Math.sqrt(dx * dx + dy * dy);\n }\n\n /**\n * Get the current order of the list containing itemDragging.\n *\n * @returns {Array} Array of strings, the id of each element in order.\n */\n getCurrentOrder() {\n return this.itemDragging.closest(this.config.list).find(this.config.item).map(\n (index, item) => {\n return this.config.idGetter(item);\n }).get();\n }\n\n /**\n * Compare two arrays which
contain primitive types to see if they are equal.\n * @param {Array} a1 first array.\n * @param {Array} a2 second array.\n * @return {Boolean} boolean true if they both contain the same elements in the same order, else false.\n */\n arrayEquals(a1, a2) {\n return a1.length === a2.length &&\n a1.every((v, i) => {\n return v === a2[i];\n });\n }\n\n /**\n * Initialise one ordering question.\n *\n * @param {String} sortableid id of ul for this question.\n * @param {String} responseid id of hidden field for this question.\n */\n static init(sortableid, responseid) {\n new DragReorder({\n actionButton: '[data-action]',\n announcementRegion: document.querySelector(`#${sortableid}-announcement`),\n list: 'ul#' + sortableid,\n item: 'li.sortableitem',\n itemMovingClass: \"current-drop\",\n idGetter: item => {\n return item.id;\n
},\n reorderDone: (list, item, newOrder) => {\n $('input#' + responseid)[0].value = newOrder.join(',');\n }\n });\n\n prefetchString('qtype_ordering', 'moved');\n }\n}\n"],"names":["DragReorder","constructor","config","reorderStart","undefined","reorderEnd","orderList","document","querySelector","this","list","startListeners","pointerHandle","e","target","closest","item","actionButton","itemDragging","details","drag","prepare","start","startDrag","addEventListener","itemMovedByClick","bind","dragStart","time","Date","getTime","x","y","originalOrder","getCurrentOrder","renderForPromise","itemHtml","html","itemClassName","attr","listClassName","classList","toString","proxyStyles","outerWidth","outerHeight","join","then","_ref","js","proxy","Templates","appendNodeContents","body","css","offset","addClass","itemMovingClass","updateProxy","dragMove","dragEnd","catch","Notification","exception","closestItem","closestDistance","querySelectorAll","forEach","elem
ent","distance","distanceBetweenElements","offsetValue","midY","insertBefore","insertAfter","items","i","length","find","arrayEquals","reorderDone","text","trim","position","index","total","str","announcementRegion","innerHTML","remove","removeClass","dataset","action","preventDefault","stopPropagation","prev","next","focus","midX","node","left","top","e1","e2","dx","dy","Math","sqrt","map","idGetter","get","a1","a2","every","v","sortableid","responseid","id","newOrder","value"],"mappings":"0tBAiCqBA,YA+DjBC,YAAYC,sCA5DH,CAACC,kBAAcC,EAAWC,gBAAYD,qCACnC,2CACI,uCAGJ,0CACG,mCACP,WAuDCF,OAASA,YAGTI,UAAYC,SAASC,cAAcC,KAAKP,OAAOQ,WAE/CC,iBAMTA,uBAMUC,cAAgBC,OACdA,EAAEC,OAAOC,QAAQN,KAAKP,OAAOc,QAAUH,EAAEC,OAAOC,QAAQN,KAAKP,OAAOe,cAAe,MAC9EC,cAAe,mBAAEL,EAAEC,OAAOC,QAAQN,KAAKP,OAAOc,aAC7CG,QAAUC,kBAAKC,QAAQR,GACzBM,QAAQG,YACHC,UAAUV,EAAGM,gBAKzBb,UAAUkB,iBAAiB,YAAaZ,oBACxCN,UAAUkB,iBAAiB,aAAcZ,oBACzCN,UAAUkB,iBAAiB,QAASf,KAAKgB,iBAAiBC,KAAKjB,OASxEc,UAAUV,EAAGM,cACJQ,UAAY,CACbC,MAAM,IAAIC,MAAOC,UACjBC,EAAGZ,QAAQY,E
ACXC,EAAGb,QAAQa,QAGyB,IAA7BvB,KAAKP,OAAOC,mBACdD,OAAOC,aAAaM,KAAKS,aAAaH,QAAQN,KAAKP,OAAOQ,MAAOD,KAAKS,mBAG1Ee,cAAgBxB,KAAKyB,qCAEhBC,iBAAiB,2BAA4B,CACnDC,SAAU3B,KAAKS,aAAamB,OAC5BC,cAAe7B,KAAKS,aAAaqB,KAAK,SACtCC,cAAe/B,KAAKH,UAAUmC,UAAUC,WACxCC,YAAa,kBACClC,KAAKS,aAAa0B,sCACjBnC,KAAKS,aAAa2B,sBAC/BC,KAAK,OACRC,MAAKC,WAACX,KAACA,KAADY,GAAOA,cACPC,OAAQ,mBAAEC,mBAAUC,mBAAmB7C,SAAS8C,KAAMhB,KAAMY,IAAI,SAChEC,MAAMI,IAAI7C,KAAKS,aAAaqC,eAE5BrC,aAAasC,SAAS/C,KAAKP,OAAOuD,sBAElCC,gCAEApC,MAAMT,EAAGJ,KAAKyC,MAAOzC,KAAKkD,SAASjC,KAAKjB,MAAOA,KAAKmD,QAAQlC,KAAKjB,UACvEoD,MAAMC,sBAAaC,WAM1BJ,eACQK,YAAc,KACdC,gBAAkB,aACjB3D,UAAU4D,iBAAiBzD,KAAKP,OAAOc,MAAMmD,SAAQC,gBAChDC,SAAW5D,KAAK6D,wBAAwBF,UAC1B,OAAhBJ,aAAwBK,SAAWJ,mBACnCD,aAAc,mBAAEI,SAChBH,gBAAkBI,aAItBL,YAAY,KAAOvD,KAAKS,aAAa,gBAKnCqD,YAAc9D,KAAK+D,KAAK/D,KAAKyC,OAASzC,KAAK+D,KAAKR,aAAe,IAAM,GACvEvD,KAAK+D,KAAK/D,KAAKyC,OAASqB,YAAc9D,KAAK+D,KAAKR,kBAC3C9C,aAAauD,aAAaT,kBAE1B9C,aAAawD,YAAYV,kBAE7BN,cAMTA,oBACUiB,MAAQ,IAAIlE,KAAKH,UAAU4D,iBAAiBzD,KAAKP,OAAOc,WACz
D,IAAI4D,EAAI,EAAGA,EAAID,MAAME,SAAUD,KAC5BnE,KAAKS,aAAa,KAAOyD,MAAMC,GAAI,MAC9B1B,MAAM4B,KAAK,MAAMvC,KAAK,QAASqC,EAAI,UASpDhB,eAC0C,IAA3BnD,KAAKP,OAAOG,iBACdH,OAAOG,WAAWI,KAAKS,aAAaH,QAAQN,KAAKP,OAAOQ,MAAOD,KAAKS,cAGxET,KAAKsE,YAAYtE,KAAKwB,cAAexB,KAAKyB,0BAEtChC,OAAO8E,YAAYvE,KAAKS,aAAaH,QAAQN,KAAKP,OAAOQ,MAAOD,KAAKS,aAAcT,KAAKyB,sCAEnF,QAAS,iBAAkB,CACjClB,KAAMP,KAAKS,aAAa4D,KAAK,sBAAsBG,OAAOC,OAC1DC,SAAU1E,KAAKS,aAAakE,QAAU,EACtCC,MAAO5E,KAAKH,UAAU4D,iBAAiBzD,KAAKP,OAAOc,MAAM6D,SAC1D9B,MAAMuC,WACApF,OAAOqF,mBAAmBC,UAAYF,aAK9CpC,MAAMuC,cACNvC,MAAQ,UACRhC,aAAawE,YAAYjF,KAAKP,OAAOuD,sBACrCvC,aAAe,UACfS,UAAY,KAQrBF,iBAAiBZ,SACPI,aAAeJ,EAAEC,OAAOC,QAAQN,KAAKP,OAAOe,iBAC9CA,aAAc,aACTC,cAAe,mBAAEL,EAAEC,OAAOC,QAAQN,KAAKP,OAAOc,YAG9CiB,cAAgBxB,KAAKyB,kBAElBjB,aAAa0E,QAAQC,YACpB,gBACD/E,EAAEgF,iBACFhF,EAAEiF,kBACErF,KAAKS,aAAa6E,OAAOlB,aACpB3D,aAAa6E,OAAOrB,YAAYjE,KAAKS,wBAG7C,eACDL,EAAEgF,iBACFhF,EAAEiF,kBACErF,KAAKS,aAAa8E,OAAOnB,aACpB3D,aAAa8E,OAAOvB,aAAahE,KAAKS,cAMlDT,KAAKsE,YAAYtE,KAAKwB,cAAexB,KAAKyB,0BAEtC
hC,OAAO8E,YAAYvE,KAAKS,aAAaH,QAAQN,KAAKP,OAAOQ,MAAOD,KAAKS,aAAcT,KAAKyB,mBAIxFzB,KAAKS,aAAa6E,OAAOlB,OAGlBpE,KAAKS,aAAa8E,OAAOnB,aAE5B3D,aAAa4D,KAAK,iCAAiCmB,aAHnD/E,aAAa4D,KAAK,gCAAgCmB,2BAMjD,QAAS,iBAAkB,CACjCjF,KAAMP,KAAKS,aAAa4D,KAAK,sBAAsBG,OAAOC,OAC1DC,SAAU1E,KAAKS,aAAakE,QAAU,EACtCC,MAAO5E,KAAKH,UAAU4D,iBAAiBzD,KAAKP,OAAOc,MAAM6D,SAC1D9B,MAAMuC,WACApF,OAAOqF,mBAAmBC,UAAYF,SAY3DY,KAAKC,aACMA,KAAK5C,SAAS6C,KAAOD,KAAKvD,aAAe,EASpD4B,KAAK2B,aACMA,KAAK5C,SAAS8C,IAAMF,KAAKtD,cAAgB,EASpDyB,wBAAwBF,eACbkC,GAAIC,IAAM,EAAC,mBAAEnC,UAAU,mBAAE3D,KAAKyC,SAC9BsD,GAAIC,IAAM,CAAChG,KAAKyF,KAAKI,IAAM7F,KAAKyF,KAAKK,IAAK9F,KAAK+D,KAAK8B,IAAM7F,KAAK+D,KAAK+B,YACpEG,KAAKC,KAAKH,GAAKA,GAAKC,GAAKA,IAQpCvE,yBACWzB,KAAKS,aAAaH,QAAQN,KAAKP,OAAOQ,MAAMoE,KAAKrE,KAAKP,OAAOc,MAAM4F,KACtE,CAACxB,MAAOpE,OACGP,KAAKP,OAAO2G,SAAS7F,QAC7B8F,MASX/B,YAAYgC,GAAIC,WACLD,GAAGlC,SAAWmC,GAAGnC,QACpBkC,GAAGE,OAAM,CAACC,EAAGtC,IACFsC,IAAMF,GAAGpC,iBAUhBuC,WAAYC,gBAChBpH,YAAY,CACZiB,aAAc,gBACdsE,mBAAoBhF,SAASC,yBAAkB2G,6BAC/CzG,KAAM,MAAQyG,WACdn
G,KAAM,kBACNyC,gBAAiB,eACjBoD,SAAU7F,MACCA,KAAKqG,GAEhBrC,YAAa,CAACtE,KAAMM,KAAMsG,gCACpB,SAAWF,YAAY,GAAGG,MAAQD,SAASxE,KAAK,qCAI3C,iBAAkB"}