diff --git a/question/type/ordering/amd/build/drag_reorder.min.js b/question/type/ordering/amd/build/drag_reorder.min.js index 277f1c5b406..a883b50a475 100644 --- a/question/type/ordering/amd/build/drag_reorder.min.js +++ b/question/type/ordering/amd/build/drag_reorder.min.js @@ -1,3 +1,3 @@ -define("qtype_ordering/drag_reorder",["exports","jquery","core/dragdrop","core/key_codes"],(function(_exports,_jquery,_dragdrop,_key_codes){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),_dragdrop=_interopRequireDefault(_dragdrop),_key_codes=_interopRequireDefault(_key_codes);class DragReorder{constructor(config){_defineProperty(this,"config",{reorderStart:"undefined",reorderEnd:"undefined"}),_defineProperty(this,"dragStart",null),_defineProperty(this,"originalOrder",null),_defineProperty(this,"itemDragging",null),_defineProperty(this,"itemMoving",null),_defineProperty(this,"orderList",null),_defineProperty(this,"proxy",null),this.config=config,this.config.itemInPage=this.combineSelectors(config.list,config.item),(0,_jquery.default)(this.config.list).on("mousedown touchstart",config.item,(e=>{const details=_dragdrop.default.prepare(e);details.start&&this.startDrag(e,details)})),(0,_jquery.default)(this.config.list).on("keydown",config.item,(e=>{this.itemMoving=(0,_jquery.default)(e.currentTarget).closest(config.itemInPage),this.originalOrder=this.getCurrentOrder(),this.itemMovedByKeyboard(e,this.itemMoving);const newOrder=this.getCurrentOrder();this.arrayEquals(this.originalOrder,newOrder)||this.config.reorderDone(this.itemMoving.closest(this.config.list),this.itemMoving,newOrder)})),(0,_jquery.default)(this.config.itemInPage).attr("tabindex","0")}startDrag(e,details){this.orderList=(0,_jquery.default)(this.config.list),this.dragStart={time:(new Date).getTime(),x:details.x,y:details.y},this.itemDragging=(0,_jquery.default)(e.currentTarget).closest(this.config.itemInPage),void 0!==this.config.reorderStart&&this.config.reorderStart(this.itemDragging.closest(this.config.list),this.itemDragging),this.originalOrder=this.getCurrentOrder(),this.proxy=(0,_jquery.default)(this.config.proxyHtml.replace("%%ITEM_HTML%%",this.itemDragging.html()).replace("%%ITEM_CLASS_NAME%%",this.itemDragging.attr("class")).replace("%%LIST_CLASS_NAME%%",this.orderList.attr("class"))),(0,_jquery.default)(document.body).append(this.proxy),this.proxy.css("position","absolute"),this.proxy.css(this.itemDragging.offset()),this.proxy.width(this.itemDragging.outerWidth()),this.proxy.height(this.itemDragging.outerHeight()),this.itemDragging.addClass(this.config.itemMovingClass),this.updateProxy(),_dragdrop.default.start(e,this.proxy,this.dragMove.bind(this),this.dragEnd.bind(this))}dragMove(){const list=this.itemDragging.closest(this.config.list);let closestItem=null,closestDistance=null;if(list.find(this.config.item).each(((index,element)=>{const distance=this.distanceBetweenElements(element,this.proxy);(null===closestItem||distance{inner.split(",").forEach((secondSelector=>{combined.push(firstSelector.trim()+" "+secondSelector.trim())}))})),combined.join(", ")}dragEnd(x,y){void 0!==this.config.reorderEnd&&this.config.reorderEnd(this.itemDragging.closest(this.config.list),this.itemDragging);const newOrder=this.getCurrentOrder();this.arrayEquals(this.originalOrder,newOrder)?(new Date).getTime()-this.dragStart.time<500&&Math.abs(this.dragStart.x-x)<10&&Math.abs(this.dragStart.y-y)<10&&this.itemDragging[0].focus():this.config.reorderDone(this.itemDragging.closest(this.config.list),this.itemDragging,newOrder),this.proxy.remove(),this.proxy=null,this.itemDragging.removeClass(this.config.itemMovingClass),this.itemDragging=null,this.dragStart=null}itemMovedByKeyboard(e,current){switch(e.keyCode){case _key_codes.default.space:case _key_codes.default.arrowRight:case _key_codes.default.arrowDown:e.preventDefault(),e.stopPropagation(),current.next().length&¤t.next().insertBefore(current);break;case _key_codes.default.arrowLeft:case _key_codes.default.arrowUp:e.preventDefault(),e.stopPropagation(),current.prev().length&¤t.prev().insertAfter(current)}}midX(node){return node.offset().left+node.outerWidth()/2}midY(node){return node.offset().top+node.outerHeight()/2}distanceBetweenElements(element1,element2){const[e1,e2]=[(0,_jquery.default)(element1),(0,_jquery.default)(element2)],[dx,dy]=[this.midX(e1)-this.midX(e2),this.midY(e1)-this.midY(e2)];return Math.sqrt(dx*dx+dy*dy)}getCurrentOrder(){return(this.itemDragging||this.itemMoving).closest(this.config.list).find(this.config.item).map(((index,item)=>this.config.idGetter(item))).get()}arrayEquals(a1,a2){return a1.length===a2.length&&a1.every(((v,i)=>v===a2[i]))}static init(sortableid,responseid){new DragReorder({list:"ul#"+sortableid,item:"li.sortableitem",proxyHtml:'
',itemMovingClass:"current-drop",idGetter:item=>(0,_jquery.default)(item).attr("id"),nameGetter:item=>(0,_jquery.default)(item).text,reorderDone:(list,item,newOrder)=>{(0,_jquery.default)("input#"+responseid)[0].value=newOrder.join(",")}})}}return _exports.default=DragReorder,_exports.default})); +define("qtype_ordering/drag_reorder",["exports","jquery","core/dragdrop","core/key_codes","core/templates","core/notification"],(function(_exports,_jquery,_dragdrop,_key_codes,_templates,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}function _defineProperty(obj,key,value){return key in obj?Object.defineProperty(obj,key,{value:value,enumerable:!0,configurable:!0,writable:!0}):obj[key]=value,obj}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.default=void 0,_jquery=_interopRequireDefault(_jquery),_dragdrop=_interopRequireDefault(_dragdrop),_key_codes=_interopRequireDefault(_key_codes),_templates=_interopRequireDefault(_templates),_notification=_interopRequireDefault(_notification);class DragReorder{constructor(config){_defineProperty(this,"config",{reorderStart:void 0,reorderEnd:void 0}),_defineProperty(this,"dragStart",null),_defineProperty(this,"originalOrder",null),_defineProperty(this,"orderList",null),_defineProperty(this,"itemDragging",null),_defineProperty(this,"proxy",null),this.config=config,this.orderList=document.querySelector(this.config.list),this.startListeners(),(0,_jquery.default)(this.combineSelectors(config.list,config.item)).attr("tabindex","0")}startListeners(){const pointerHandle=e=>{if(e.target.closest(this.config.item)){this.itemDragging=(0,_jquery.default)(e.target.closest(this.config.item));const details=_dragdrop.default.prepare(e);details.start&&this.startDrag(e,details)}};this.orderList.addEventListener("mousedown",pointerHandle),this.orderList.addEventListener("touchstart",pointerHandle),this.orderList.addEventListener("keydown",this.itemMovedByKeyboard.bind(this))}startDrag(e,details){this.dragStart={time:(new Date).getTime(),x:details.x,y:details.y},void 0!==this.config.reorderStart&&this.config.reorderStart(this.itemDragging.closest(this.config.list),this.itemDragging),this.originalOrder=this.getCurrentOrder(),_templates.default.renderForPromise("qtype_ordering/proxyhtml",{itemHtml:this.itemDragging.html(),itemClassName:this.itemDragging.attr("class"),listClassName:this.orderList.classList.toString(),proxyStyles:["width: ".concat(this.itemDragging.outerWidth(),"px;"),"height: ".concat(this.itemDragging.outerHeight(),"px;")].join(" ")}).then((_ref=>{let{html:html,js:js}=_ref;this.proxy=(0,_jquery.default)(_templates.default.appendNodeContents(document.body,html,js)[0]),this.proxy.css(this.itemDragging.offset()),this.itemDragging.addClass(this.config.itemMovingClass),this.updateProxy(),_dragdrop.default.start(e,this.proxy,this.dragMove.bind(this),this.dragEnd.bind(this))})).catch(_notification.default.exception)}dragMove(){let closestItem=null,closestDistance=null;if(this.orderList.querySelectorAll(this.config.item).forEach((element=>{const distance=this.distanceBetweenElements(element);(null===closestItem||distance{inner.split(",").forEach((secondSelector=>{combined.push(firstSelector.trim()+" "+secondSelector.trim())}))})),combined.join(", ")}midX(node){return node.offset().left+node.outerWidth()/2}midY(node){return node.offset().top+node.outerHeight()/2}distanceBetweenElements(element){const[e1,e2]=[(0,_jquery.default)(element),(0,_jquery.default)(this.proxy)],[dx,dy]=[this.midX(e1)-this.midX(e2),this.midY(e1)-this.midY(e2)];return Math.sqrt(dx*dx+dy*dy)}getCurrentOrder(){return this.itemDragging.closest(this.config.list).find(this.config.item).map(((index,item)=>this.config.idGetter(item))).get()}arrayEquals(a1,a2){return a1.length===a2.length&&a1.every(((v,i)=>v===a2[i]))}static init(sortableid,responseid){new DragReorder({list:"ul#"+sortableid,item:"li.sortableitem",itemMovingClass:"current-drop",idGetter:item=>item.id,reorderDone:(list,item,newOrder)=>{(0,_jquery.default)("input#"+responseid)[0].value=newOrder.join(",")}})}}return _exports.default=DragReorder,_exports.default})); //# sourceMappingURL=drag_reorder.min.js.map \ No newline at end of file diff --git a/question/type/ordering/amd/build/drag_reorder.min.js.map b/question/type/ordering/amd/build/drag_reorder.min.js.map index 6d180ef85ad..c8a99360462 100644 --- a/question/type/ordering/amd/build/drag_reorder.min.js.map +++ b/question/type/ordering/amd/build/drag_reorder.min.js.map @@ -1 +1 @@ -{"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 .\n\n/*\n * Generic library to allow things in a vertical list to be re-ordered using drag and drop.\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\n * @copyright 2018 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 keys from 'core/key_codes';\n\nexport default class DragReorder {\n\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.\n itemDragging = null; // Item being moved by dragging (jQuery object).\n itemMoving = null; // Item being moved using the accessible modal (jQuery object).\n orderList = null; // Order list (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 * // The user actually drags a proxy object, which is constructed from this string,\n * // and then added directly as a child of . The token %%ITEM_HTML%% is\n * // replaced with the innerHtml of the item being dragged. The token %%ITEM_CLASS_NAME%%\n * // is replaced with the class attribute of the item being dragged. Because of this,\n * // the styling of the contents of your list item needs to work for the proxy, as well as\n * // for items in place in the context of the list. Your CSS also needs to ensure\n * // that this proxy has position: absolute. You probably want other styles, like a\n * // drop shadow. Using class osep-itemmoving might be all you need to do.\n * proxyHtml: '
%%ITEM_HTML%%
,\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).data('id'); },\n *\n * // This is a callback which, when called with the DOM node for an item,\n * // returns a string that is the name of the item.\n * nameGetter: function(item) { return $(node).text(); },\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 * // This 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\n this.config = config;\n\n this.config.itemInPage = this.combineSelectors(config.list, config.item);\n\n // AJAX for section drag and click-to-move.\n $(this.config.list).on('mousedown touchstart', config.item, e => {\n const details = drag.prepare(e);\n if (details.start) {\n this.startDrag(e, details);\n }\n });\n\n $(this.config.list).on('keydown', config.item, e => {\n this.itemMoving = $(e.currentTarget).closest(config.itemInPage);\n this.originalOrder = this.getCurrentOrder();\n this.itemMovedByKeyboard(e, this.itemMoving);\n const newOrder = this.getCurrentOrder();\n if (!this.arrayEquals(this.originalOrder, newOrder)) {\n // Order has changed, call the callback.\n this.config.reorderDone(this.itemMoving.closest(this.config.list), this.itemMoving, newOrder);\n }\n });\n\n // Make the items tabbable.\n $(this.config.itemInPage).attr('tabindex', '0');\n }\n\n /**\n * Start dragging.\n *\n * @param {jQuery} e The jQuery 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.orderList = $(this.config.list);\n\n this.dragStart = {\n time: new Date().getTime(),\n x: details.x,\n y: details.y\n };\n\n this.itemDragging = $(e.currentTarget).closest(this.config.itemInPage);\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 this.proxy = $(this.config.proxyHtml.replace('%%ITEM_HTML%%', this.itemDragging.html())\n .replace('%%ITEM_CLASS_NAME%%', this.itemDragging.attr('class'))\n .replace('%%LIST_CLASS_NAME%%', this.orderList.attr('class')));\n\n $(document.body).append(this.proxy);\n this.proxy.css('position', 'absolute');\n this.proxy.css(this.itemDragging.offset());\n this.proxy.width(this.itemDragging.outerWidth());\n this.proxy.height(this.itemDragging.outerHeight());\n this.itemDragging.addClass(this.config.itemMovingClass);\n this.updateProxy();\n\n // Start drag.\n drag.start(e, this.proxy, this.dragMove.bind(this), this.dragEnd.bind(this));\n }\n\n /**\n * Move the proxy to the current mouse position.\n */\n dragMove() {\n const list = this.itemDragging.closest(this.config.list);\n let closestItem = null;\n let closestDistance = null;\n list.find(this.config.item).each((index, element) => {\n const distance = this.distanceBetweenElements(element, this.proxy);\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 list = this.itemDragging.closest('ol, ul');\n const items = list.find('li');\n const count = items.length;\n for (let i = 0; i < count; ++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 * Our outer and inner are two CSS selectors, which may contain commas.\n * We want to combine them safely. So for instance combineSelectors('a, b', 'c, d')\n * gives 'a c, a d, b c, b d'.\n *\n * @param {String} outer The selector for the outer element.\n * @param {String} inner The selector for the inner element.\n * @returns {String} The combined selector used to listen to the list item.\n */\n combineSelectors(outer, inner) {\n let combined = [];\n outer.split(',').forEach(firstSelector => {\n inner.split(',').forEach(secondSelector => {\n combined.push(firstSelector.trim() + ' ' + secondSelector.trim());\n });\n });\n return combined.join(', ');\n }\n\n /**\n * End dragging.\n *\n * @param {number} x X co-ordinate\n * @param {number} y Y co-ordinate\n */\n dragEnd(x, y) {\n if (typeof this.config.reorderEnd !== 'undefined') {\n this.config.reorderEnd(this.itemDragging.closest(this.config.list), this.itemDragging);\n }\n\n const newOrder = this.getCurrentOrder();\n if (!this.arrayEquals(this.originalOrder, newOrder)) {\n // Order has changed, call the callback.\n this.config.reorderDone(this.itemDragging.closest(this.config.list), this.itemDragging, newOrder);\n\n } else if (new Date().getTime() - this.dragStart.time < 500 &&\n Math.abs(this.dragStart.x - x) < 10 && Math.abs(this.dragStart.y - y) < 10) {\n // This was really a click. Set the focus on the current item.\n this.itemDragging[0].focus();\n }\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 * Items can be moved and placed using certain keys.\n * Tab for tabbing though and choose the item to be moved\n * space, arrow-right arrow-down for moving current element forwards.\n * arrow-right arrow-down for moving the current element backwards.\n *\n * @param {Event} e The keyboard event.\n * @param {jQuery} current An object representing the current moving item and the previous item we just moved past.\n */\n itemMovedByKeyboard(e, current) {\n switch (e.keyCode) {\n case keys.space:\n case keys.arrowRight:\n case keys.arrowDown:\n e.preventDefault();\n e.stopPropagation();\n if (current.next().length) {\n current.next().insertBefore(current);\n }\n break;\n\n case keys.arrowLeft:\n case keys.arrowUp:\n e.preventDefault();\n e.stopPropagation();\n if (current.prev().length) {\n current.prev().insertAfter(current);\n }\n break;\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 middle (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} element1 DOM node of a list item.\n * @param {HTMLLIElement} element2 DOM node of a list item.\n * @return {number} number the distance in pixels.\n */\n distanceBetweenElements(element1, element2) {\n const [e1, e2] = [$(element1), $(element2)];\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 || this.itemMoving).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 list: 'ul#' + sortableid,\n item: 'li.sortableitem',\n proxyHtml: '
' +\n '
  • ' +\n '%%ITEM_HTML%%
',\n itemMovingClass: \"current-drop\",\n idGetter: item => {\n return $(item).attr('id');\n },\n nameGetter: item => {\n return $(item).text;\n },\n reorderDone: (list, item, newOrder) => {\n $('input#' + responseid)[0].value = newOrder.join(',');\n }\n });\n }\n}\n"],"names":["DragReorder","constructor","config","reorderStart","reorderEnd","itemInPage","this","combineSelectors","list","item","on","e","details","drag","prepare","start","startDrag","itemMoving","currentTarget","closest","originalOrder","getCurrentOrder","itemMovedByKeyboard","newOrder","arrayEquals","reorderDone","attr","orderList","dragStart","time","Date","getTime","x","y","itemDragging","proxy","proxyHtml","replace","html","document","body","append","css","offset","width","outerWidth","height","outerHeight","addClass","itemMovingClass","updateProxy","dragMove","bind","dragEnd","closestItem","closestDistance","find","each","index","element","distance","distanceBetweenElements","offsetValue","midY","insertBefore","insertAfter","items","count","length","i","outer","inner","combined","split","forEach","firstSelector","secondSelector","push","trim","join","Math","abs","focus","remove","removeClass","current","keyCode","keys","space","arrowRight","arrowDown","preventDefault","stopPropagation","next","arrowLeft","arrowUp","prev","midX","node","left","top","element1","element2","e1","e2","dx","dy","sqrt","map","idGetter","get","a1","a2","every","v","sortableid","responseid","nameGetter","text","value"],"mappings":"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"} \ No newline at end of file +{"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 .\n\n/*\n * Generic library to allow things in a vertical list to be re-ordered using drag and drop.\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\n * @copyright 2018 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 keys from 'core/key_codes';\nimport Templates from 'core/templates';\nimport Notification from 'core/notification';\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 * // This 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 // Make the items tabbable.\n // TODO: This can be removed once we move to templates and add the tabindex there.\n $(this.combineSelectors(config.list, config.item)).attr('tabindex', '0');\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)) {\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('keydown', this.itemMovedByKeyboard.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 * @param {number} x X co-ordinate\n * @param {number} y Y co-ordinate\n */\n dragEnd(x, y) {\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 } else if (new Date().getTime() - this.dragStart.time < 500 &&\n Math.abs(this.dragStart.x - x) < 10 && Math.abs(this.dragStart.y - y) < 10) {\n // This was really a click. Set the focus on the current item.\n this.itemDragging[0].focus();\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 * Items can be moved and placed using certain keys.\n * Tab for tabbing though and choose the item to be moved\n * space, arrow-right arrow-down for moving current element forwards.\n * arrow-right arrow-down for moving the current element backwards.\n *\n * @param {Event} e The keyboard event.\n */\n itemMovedByKeyboard(e) {\n if (e.target.closest(this.config.item)) {\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 (e.keyCode) {\n case keys.space:\n case keys.arrowRight:\n case keys.arrowDown:\n e.preventDefault();\n e.stopPropagation();\n if (this.itemDragging.next().length) {\n this.itemDragging.next().insertBefore(this.itemDragging);\n }\n break;\n\n case keys.arrowLeft:\n case keys.arrowUp:\n e.preventDefault();\n e.stopPropagation();\n if (this.itemDragging.prev().length) {\n this.itemDragging.prev().insertAfter(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 }\n }\n\n /**\n * TODO: Once the tabindex is added to the template, this can be removed.\n * Our outer and inner are two CSS selectors, which may contain commas.\n * We want to combine them safely. So for instance combineSelectors('a, b', 'c, d')\n * gives 'a c, a d, b c, b d'.\n *\n * @param {String} outer The selector for the outer element.\n * @param {String} inner The selector for the inner element.\n * @returns {String} The combined selector used to listen to the list item.\n */\n combineSelectors(outer, inner) {\n let combined = [];\n outer.split(',').forEach(firstSelector => {\n inner.split(',').forEach(secondSelector => {\n combined.push(firstSelector.trim() + ' ' + secondSelector.trim());\n });\n });\n return combined.join(', ');\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 middle (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 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}\n"],"names":["DragReorder","constructor","config","reorderStart","undefined","reorderEnd","orderList","document","querySelector","this","list","startListeners","combineSelectors","item","attr","pointerHandle","e","target","closest","itemDragging","details","drag","prepare","start","startDrag","addEventListener","itemMovedByKeyboard","bind","dragStart","time","Date","getTime","x","y","originalOrder","getCurrentOrder","renderForPromise","itemHtml","html","itemClassName","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","element","distance","distanceBetweenElements","offsetValue","midY","insertBefore","insertAfter","items","i","length","find","arrayEquals","Math","abs","focus","reorderDone","remove","removeClass","keyCode","keys","space","arrowRight","arrowDown","preventDefault","stopPropagation","next","arrowLeft","arrowUp","prev","outer","inner","combined","split","firstSelector","secondSelector","push","trim","midX","node","left","top","e1","e2","dx","dy","sqrt","map","index","idGetter","get","a1","a2","every","v","sortableid","responseid","id","newOrder","value"],"mappings":"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"} \ No newline at end of file diff --git a/question/type/ordering/amd/src/drag_reorder.js b/question/type/ordering/amd/src/drag_reorder.js index a52d63b950c..e4843203a0f 100644 --- a/question/type/ordering/amd/src/drag_reorder.js +++ b/question/type/ordering/amd/src/drag_reorder.js @@ -29,15 +29,19 @@ import $ from 'jquery'; import drag from 'core/dragdrop'; import keys from 'core/key_codes'; +import Templates from 'core/templates'; +import Notification from 'core/notification'; export default class DragReorder { - config = {reorderStart: 'undefined', reorderEnd: 'undefined'}; // Config object with some basic definitions. + // 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. + 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). - itemMoving = null; // Item being moved using the accessible modal (jQuery object). - orderList = null; // Order list (jQuery object). proxy = null; // Drag proxy (jQuery object). /** @@ -52,16 +56,6 @@ export default class DragReorder { * // Selector, relative to the list selector, for the items that can be moved. * item: '> li', * - * // The user actually drags a proxy object, which is constructed from this string, - * // and then added directly as a child of . The token %%ITEM_HTML%% is - * // replaced with the innerHtml of the item being dragged. The token %%ITEM_CLASS_NAME%% - * // is replaced with the class attribute of the item being dragged. Because of this, - * // the styling of the contents of your list item needs to work for the proxy, as well as - * // for items in place in the context of the list. Your CSS also needs to ensure - * // that this proxy has position: absolute. You probably want other styles, like a - * // drop shadow. Using class osep-itemmoving might be all you need to do. - * proxyHtml: '
%%ITEM_HTML%%
, - * * // 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", @@ -70,11 +64,7 @@ export default class DragReorder { * // 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).data('id'); }, - * - * // This is a callback which, when called with the DOM node for an item, - * // returns a string that is the name of the item. - * nameGetter: function(item) { return $(node).text(); }, + * 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. @@ -106,81 +96,90 @@ export default class DragReorder { * @param {Object} config As above. */ constructor(config) { - + // Bring in the config to our state. this.config = config; - this.config.itemInPage = this.combineSelectors(config.list, config.item); + // Get the list we'll be working with this time. + this.orderList = document.querySelector(this.config.list); - // AJAX for section drag and click-to-move. - $(this.config.list).on('mousedown touchstart', config.item, e => { - const details = drag.prepare(e); - if (details.start) { - this.startDrag(e, details); - } - }); - - $(this.config.list).on('keydown', config.item, e => { - this.itemMoving = $(e.currentTarget).closest(config.itemInPage); - this.originalOrder = this.getCurrentOrder(); - this.itemMovedByKeyboard(e, this.itemMoving); - const newOrder = this.getCurrentOrder(); - if (!this.arrayEquals(this.originalOrder, newOrder)) { - // Order has changed, call the callback. - this.config.reorderDone(this.itemMoving.closest(this.config.list), this.itemMoving, newOrder); - } - }); + this.startListeners(); // Make the items tabbable. - $(this.config.itemInPage).attr('tabindex', '0'); + // TODO: This can be removed once we move to templates and add the tabindex there. + $(this.combineSelectors(config.list, config.item)).attr('tabindex', '0'); + } + + /** + * 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)) { + 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('keydown', this.itemMovedByKeyboard.bind(this)); } /** * Start dragging. * - * @param {jQuery} e The jQuery event which is either mousedown or touchstart. + * @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.orderList = $(this.config.list); - this.dragStart = { time: new Date().getTime(), x: details.x, y: details.y }; - this.itemDragging = $(e.currentTarget).closest(this.config.itemInPage); - if (typeof this.config.reorderStart !== 'undefined') { this.config.reorderStart(this.itemDragging.closest(this.config.list), this.itemDragging); } this.originalOrder = this.getCurrentOrder(); - this.proxy = $(this.config.proxyHtml.replace('%%ITEM_HTML%%', this.itemDragging.html()) - .replace('%%ITEM_CLASS_NAME%%', this.itemDragging.attr('class')) - .replace('%%LIST_CLASS_NAME%%', this.orderList.attr('class'))); - $(document.body).append(this.proxy); - this.proxy.css('position', 'absolute'); - this.proxy.css(this.itemDragging.offset()); - this.proxy.width(this.itemDragging.outerWidth()); - this.proxy.height(this.itemDragging.outerHeight()); - this.itemDragging.addClass(this.config.itemMovingClass); - this.updateProxy(); + 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()); - // Start drag. - drag.start(e, this.proxy, this.dragMove.bind(this), this.dragEnd.bind(this)); + 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() { - const list = this.itemDragging.closest(this.config.list); let closestItem = null; let closestDistance = null; - list.find(this.config.item).each((index, element) => { - const distance = this.distanceBetweenElements(element, this.proxy); + this.orderList.querySelectorAll(this.config.item).forEach(element => { + const distance = this.distanceBetweenElements(element); if (closestItem === null || distance < closestDistance) { closestItem = $(element); closestDistance = distance; @@ -205,10 +204,8 @@ export default class DragReorder { * Update proxy's position. */ updateProxy() { - const list = this.itemDragging.closest('ol, ul'); - const items = list.find('li'); - const count = items.length; - for (let i = 0; i < count; ++i) { + 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; @@ -217,6 +214,80 @@ export default class DragReorder { } /** + * End dragging. + * + * @param {number} x X co-ordinate + * @param {number} y Y co-ordinate + */ + dragEnd(x, y) { + 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()); + + } else if (new Date().getTime() - this.dragStart.time < 500 && + Math.abs(this.dragStart.x - x) < 10 && Math.abs(this.dragStart.y - y) < 10) { + // This was really a click. Set the focus on the current item. + this.itemDragging[0].focus(); + } + + // 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; + } + + /** + * Items can be moved and placed using certain keys. + * Tab for tabbing though and choose the item to be moved + * space, arrow-right arrow-down for moving current element forwards. + * arrow-right arrow-down for moving the current element backwards. + * + * @param {Event} e The keyboard event. + */ + itemMovedByKeyboard(e) { + if (e.target.closest(this.config.item)) { + this.itemDragging = $(e.target.closest(this.config.item)); + + // Store the current state of the list. + this.originalOrder = this.getCurrentOrder(); + + switch (e.keyCode) { + case keys.space: + case keys.arrowRight: + case keys.arrowDown: + e.preventDefault(); + e.stopPropagation(); + if (this.itemDragging.next().length) { + this.itemDragging.next().insertBefore(this.itemDragging); + } + break; + + case keys.arrowLeft: + case keys.arrowUp: + e.preventDefault(); + e.stopPropagation(); + if (this.itemDragging.prev().length) { + this.itemDragging.prev().insertAfter(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()); + } + } + } + + /** + * TODO: Once the tabindex is added to the template, this can be removed. * Our outer and inner are two CSS selectors, which may contain commas. * We want to combine them safely. So for instance combineSelectors('a, b', 'c, d') * gives 'a c, a d, b c, b d'. @@ -235,66 +306,6 @@ export default class DragReorder { return combined.join(', '); } - /** - * End dragging. - * - * @param {number} x X co-ordinate - * @param {number} y Y co-ordinate - */ - dragEnd(x, y) { - if (typeof this.config.reorderEnd !== 'undefined') { - this.config.reorderEnd(this.itemDragging.closest(this.config.list), this.itemDragging); - } - - const newOrder = this.getCurrentOrder(); - if (!this.arrayEquals(this.originalOrder, newOrder)) { - // Order has changed, call the callback. - this.config.reorderDone(this.itemDragging.closest(this.config.list), this.itemDragging, newOrder); - - } else if (new Date().getTime() - this.dragStart.time < 500 && - Math.abs(this.dragStart.x - x) < 10 && Math.abs(this.dragStart.y - y) < 10) { - // This was really a click. Set the focus on the current item. - this.itemDragging[0].focus(); - } - this.proxy.remove(); - this.proxy = null; - this.itemDragging.removeClass(this.config.itemMovingClass); - this.itemDragging = null; - this.dragStart = null; - } - - /** - * Items can be moved and placed using certain keys. - * Tab for tabbing though and choose the item to be moved - * space, arrow-right arrow-down for moving current element forwards. - * arrow-right arrow-down for moving the current element backwards. - * - * @param {Event} e The keyboard event. - * @param {jQuery} current An object representing the current moving item and the previous item we just moved past. - */ - itemMovedByKeyboard(e, current) { - switch (e.keyCode) { - case keys.space: - case keys.arrowRight: - case keys.arrowDown: - e.preventDefault(); - e.stopPropagation(); - if (current.next().length) { - current.next().insertBefore(current); - } - break; - - case keys.arrowLeft: - case keys.arrowUp: - e.preventDefault(); - e.stopPropagation(); - if (current.prev().length) { - current.prev().insertAfter(current); - } - break; - } - } - /** * Get the x-position of the middle of the DOM node represented by the given jQuery object. * @@ -318,12 +329,11 @@ export default class DragReorder { /** * Calculate the distance between the centres of two elements. * - * @param {HTMLLIElement} element1 DOM node of a list item. - * @param {HTMLLIElement} element2 DOM node of a list item. + * @param {HTMLLIElement} element DOM node of a list item. * @return {number} number the distance in pixels. */ - distanceBetweenElements(element1, element2) { - const [e1, e2] = [$(element1), $(element2)]; + 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); } @@ -334,7 +344,7 @@ export default class DragReorder { * @returns {Array} Array of strings, the id of each element in order. */ getCurrentOrder() { - return (this.itemDragging || this.itemMoving).closest(this.config.list).find(this.config.item).map( + return this.itemDragging.closest(this.config.list).find(this.config.item).map( (index, item) => { return this.config.idGetter(item); }).get(); @@ -363,15 +373,9 @@ export default class DragReorder { new DragReorder({ list: 'ul#' + sortableid, item: 'li.sortableitem', - proxyHtml: '
' + - '
  • ' + - '%%ITEM_HTML%%
', itemMovingClass: "current-drop", idGetter: item => { - return $(item).attr('id'); - }, - nameGetter: item => { - return $(item).text; + return item.id; }, reorderDone: (list, item, newOrder) => { $('input#' + responseid)[0].value = newOrder.join(','); diff --git a/question/type/ordering/templates/proxyhtml.mustache b/question/type/ordering/templates/proxyhtml.mustache new file mode 100644 index 00000000000..736ed883ba4 --- /dev/null +++ b/question/type/ordering/templates/proxyhtml.mustache @@ -0,0 +1,49 @@ +{{! + This file is part of Moodle - https://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 . +}} +{{! + @template question_ordering/proxyhtml + + The user actually drags a proxy object, which is constructed from this template. + The proxy node is then added directly as a child of . Your CSS also needs to ensure + that this proxy has position: absolute. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * listClassName The token is replaced with the class attribute of the list being dragged. + * itemClassName The token is replaced with the class attribute of the item being dragged. + Because of this, the styling of the contents of your list item needs to work for the proxy, + as well as for items in place in the context of the list + * itemHtml The token is replaced with the innerHtml of the item being dragged. + * proxyStyles Passed in styles detailing the size of the proxy, and its position relative to the mouse. + + Example context (json): + { + listClassName: 'osep-list', + itemClassName: 'osep-item osep-itemmoving', + itemHtml: 'Item 1', + proxyStyles: 'left: 0px; top: 0px; width: 100px; height: 100px;' +}} +
+
    +
  • {{{itemHtml}}}
  • +
+