From a410223c7420949d33de6a4fd3b70467286ae4b5 Mon Sep 17 00:00:00 2001 From: mkassaei Date: Fri, 23 Jun 2023 09:52:15 +0100 Subject: [PATCH] MDL-77679 Drag and drop question type weird behavior --- question/type/ddwtos/amd/build/ddwtos.min.js | 2 +- .../type/ddwtos/amd/build/ddwtos.min.js.map | 2 +- question/type/ddwtos/amd/src/ddwtos.js | 24 +++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/question/type/ddwtos/amd/build/ddwtos.min.js b/question/type/ddwtos/amd/build/ddwtos.min.js index 411efbbf90e..f2af9705969 100644 --- a/question/type/ddwtos/amd/build/ddwtos.min.js +++ b/question/type/ddwtos/amd/build/ddwtos.min.js @@ -22,6 +22,6 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 3.6 */ -define("qtype_ddwtos/ddwtos",["jquery","core/dragdrop","core/key_codes","core_form/changechecker"],(function($,dragDrop,keys,FormChangeChecker){function DragDropToTextQuestion(containerId,readOnly){this.containerId=containerId,this.questionAnswer={},readOnly&&this.getRoot().addClass("qtype_ddwtos-readonly"),this.resizeAllDragsAndDrops(),this.cloneDrags(),this.positionDrags()}DragDropToTextQuestion.prototype.resizeAllDragsAndDrops=function(){var thisQ=this;this.getRoot().find(".answercontainer > div").each((function(i,node){thisQ.resizeAllDragsAndDropsInGroup(thisQ.getClassnameNumericSuffix($(node),"draggrouphomes"))}))},DragDropToTextQuestion.prototype.resizeAllDragsAndDropsInGroup=function(group){var thisQ=this,dragHomes=this.getRoot().find(".draggrouphomes"+group+" span.draghome"),maxWidth=0,maxHeight=0;dragHomes.each((function(i,drag){maxWidth=Math.max(maxWidth,Math.ceil(drag.offsetWidth)),maxHeight=Math.max(maxHeight,Math.ceil(0+drag.offsetHeight))})),maxWidth+=8,maxHeight+=2,dragHomes.each((function(i,drag){thisQ.setElementSize(drag,maxWidth,maxHeight)})),this.getRoot().find("span.drop.group"+group).each((function(i,drop){thisQ.setElementSize(drop,maxWidth,maxHeight)}))},DragDropToTextQuestion.prototype.setElementSize=function(element,width,height){$(element).width(width).height(height).css("lineHeight",height+"px")},DragDropToTextQuestion.prototype.cloneDrags=function(){var thisQ=this;thisQ.getRoot().find("span.draghome").each((function(index,draghome){var drag=$(draghome),placeHolder=drag.clone();placeHolder.removeClass(),placeHolder.addClass("draghome choice"+thisQ.getChoice(drag)+" group"+thisQ.getGroup(drag)+" dragplaceholder"),drag.before(placeHolder)}))},DragDropToTextQuestion.prototype.positionDrags=function(){var thisQ=this,root=this.getRoot();root.find("span.draghome").not(".dragplaceholder").each((function(i,dragNode){var drag=$(dragNode),currentPlace=thisQ.getClassnameNumericSuffix(drag,"inplace");drag.addClass("unplaced").removeClass("placed"),drag.removeAttr("tabindex"),null!==currentPlace&&drag.removeClass("inplace"+currentPlace)})),root.find("input.placeinput").each((function(i,inputNode){var input=$(inputNode),choice=input.val(),place=thisQ.getPlace(input),drop=root.find(".drop.place"+place),dropPosition=drop.offset();if(drop.data("prev-top",dropPosition.top).data("prev-left",dropPosition.left),"0"!==choice){var unplacedDrag=thisQ.getUnplacedChoice(thisQ.getGroup(input),choice),hiddenDrag=thisQ.getDragClone(unplacedDrag);if(hiddenDrag.length)if(unplacedDrag.hasClass("infinite")){var noOfDrags=thisQ.noOfDropsInGroup(thisQ.getGroup(unplacedDrag));if(thisQ.getInfiniteDragClones(unplacedDrag,!1).length{result[inputNode.id]=inputNode.value})),result},DragDropToTextQuestion.prototype.isQuestionInteracted=function(){const oldAnswer=this.questionAnswer,newAnswer=this.getQuestionAnsweredValues();let isInteracted=!1;return JSON.stringify(newAnswer)!==JSON.stringify(oldAnswer)?(isInteracted=!0,isInteracted):(Object.keys(newAnswer).forEach((key=>{newAnswer[key]!==oldAnswer[key]&&(isInteracted=!0)})),isInteracted)},DragDropToTextQuestion.prototype.handleDragStart=function(e){var thisQ=this,drag=$(e.target).closest(".draghome");if(dragDrop.prepare(e).start&&!drag.hasClass("beingdragged")){drag.addClass("beingdragged");var currentPlace=this.getClassnameNumericSuffix(drag,"inplace");if(null!==currentPlace){this.setInputValue(currentPlace,0),drag.removeClass("inplace"+currentPlace);var hiddenDrop=thisQ.getDrop(drag,currentPlace);hiddenDrop.length&&(hiddenDrop.addClass("active"),drag.offset(hiddenDrop.offset()))}else{var hiddenDrag=thisQ.getDragClone(drag);if(hiddenDrag.length)if(drag.hasClass("infinite")){var noOfDrags=this.noOfDropsInGroup(this.getGroup(drag));if(this.getInfiniteDragClones(drag,!1).length1;)choice--,previous=this.getUnplacedChoice(group,choice);return previous},DragDropToTextQuestion.prototype.animateTo=function(drag,target){var currentPos=drag.offset(),targetPos=target.offset(),thisQ=this;M.util.js_pending("qtype_ddwtos-animate-"+thisQ.containerId),drag.animate({left:parseInt(drag.css("left"))+targetPos.left-currentPos.left,top:parseInt(drag.css("top"))+targetPos.top-currentPos.top},{duration:"fast",done:function(){$("body").trigger("qtype_ddwtos-dragmoved",[drag,target,thisQ]),M.util.js_complete("qtype_ddwtos-animate-"+thisQ.containerId)}})},DragDropToTextQuestion.prototype.isPointInDrop=function(pageX,pageY,drop){var position=drop.offset();return pageX>=position.left&&pageX=position.top&&pageY1&&thisQ.getInfiniteDragClones(drag,!0).first().remove()),void 0!==drag.data("isfocus")&&!0===drag.data("isfocus")&&(drag.focus(),drag.removeData("isfocus")),void 0!==target.data("isfocus")&&!0===target.data("isfocus")&&target.removeData("isfocus"),questionManager.isKeyboardNavigation&&(questionManager.isKeyboardNavigation=!1),thisQ.isQuestionInteracted()&&(questionManager.handleFormDirty(),thisQ.questionAnswer=thisQ.getQuestionAnsweredValues())},handleFormDirty:function(){const responseForm=document.getElementById("responseform");FormChangeChecker.markFormAsDirty(responseForm)}};return{init:questionManager.init}})); +define("qtype_ddwtos/ddwtos",["jquery","core/dragdrop","core/key_codes","core_form/changechecker"],(function($,dragDrop,keys,FormChangeChecker){function DragDropToTextQuestion(containerId,readOnly){this.containerId=containerId,this.questionAnswer={},readOnly&&this.getRoot().addClass("qtype_ddwtos-readonly"),this.resizeAllDragsAndDrops(),this.cloneDrags(),this.positionDrags()}DragDropToTextQuestion.prototype.resizeAllDragsAndDrops=function(){var thisQ=this;this.getRoot().find(".answercontainer > div").each((function(i,node){thisQ.resizeAllDragsAndDropsInGroup(thisQ.getClassnameNumericSuffix($(node),"draggrouphomes"))}))},DragDropToTextQuestion.prototype.resizeAllDragsAndDropsInGroup=function(group){var thisQ=this,dragHomes=this.getRoot().find(".draggrouphomes"+group+" span.draghome"),maxWidth=0,maxHeight=0;dragHomes.each((function(i,drag){maxWidth=Math.max(maxWidth,Math.ceil(drag.offsetWidth)),maxHeight=Math.max(maxHeight,Math.ceil(0+drag.offsetHeight))})),maxWidth+=8,maxHeight+=2,dragHomes.each((function(i,drag){thisQ.setElementSize(drag,maxWidth,maxHeight)})),this.getRoot().find("span.drop.group"+group).each((function(i,drop){thisQ.setElementSize(drop,maxWidth,maxHeight)}))},DragDropToTextQuestion.prototype.setElementSize=function(element,width,height){$(element).width(width).height(height).css("lineHeight",height+"px")},DragDropToTextQuestion.prototype.cloneDrags=function(){var thisQ=this;thisQ.getRoot().find("span.draghome").each((function(index,draghome){var drag=$(draghome),placeHolder=drag.clone();placeHolder.removeClass(),placeHolder.addClass("draghome choice"+thisQ.getChoice(drag)+" group"+thisQ.getGroup(drag)+" dragplaceholder"),drag.before(placeHolder)}))},DragDropToTextQuestion.prototype.positionDrags=function(){var thisQ=this,root=this.getRoot();root.find("span.draghome").not(".dragplaceholder").each((function(i,dragNode){var drag=$(dragNode),currentPlace=thisQ.getClassnameNumericSuffix(drag,"inplace");drag.addClass("unplaced").removeClass("placed"),drag.removeAttr("tabindex"),null!==currentPlace&&drag.removeClass("inplace"+currentPlace)})),root.find("input.placeinput").each((function(i,inputNode){var input=$(inputNode),choice=input.val(),place=thisQ.getPlace(input),drop=root.find(".drop.place"+place),dropPosition=drop.offset();if(drop.data("prev-top",dropPosition.top).data("prev-left",dropPosition.left),"0"!==choice){var unplacedDrag=thisQ.getUnplacedChoice(thisQ.getGroup(input),choice),hiddenDrag=thisQ.getDragClone(unplacedDrag);if(hiddenDrag.length)if(unplacedDrag.hasClass("infinite")){var noOfDrags=thisQ.noOfDropsInGroup(thisQ.getGroup(unplacedDrag));if(thisQ.getInfiniteDragClones(unplacedDrag,!1).length{result[inputNode.id]=inputNode.value})),result},DragDropToTextQuestion.prototype.isQuestionInteracted=function(){const oldAnswer=this.questionAnswer,newAnswer=this.getQuestionAnsweredValues();let isInteracted=!1;return JSON.stringify(newAnswer)!==JSON.stringify(oldAnswer)?(isInteracted=!0,isInteracted):(Object.keys(newAnswer).forEach((key=>{newAnswer[key]!==oldAnswer[key]&&(isInteracted=!0)})),isInteracted)},DragDropToTextQuestion.prototype.handleDragStart=function(e){var thisQ=this,drag=$(e.target).closest(".draghome");if(dragDrop.prepare(e).start&&!drag.hasClass("beingdragged")){drag.addClass("beingdragged");var currentPlace=this.getClassnameNumericSuffix(drag,"inplace");if(null!==currentPlace){this.setInputValue(currentPlace,0),drag.removeClass("inplace"+currentPlace);var hiddenDrop=thisQ.getDrop(drag,currentPlace);hiddenDrop.length&&(hiddenDrop.addClass("active"),drag.offset(hiddenDrop.offset()))}else{var hiddenDrag=thisQ.getDragClone(drag);if(hiddenDrag.length)if(drag.hasClass("infinite")){var noOfDrags=this.noOfDropsInGroup(this.getGroup(drag));if(this.getInfiniteDragClones(drag,!1).length1;)choice--,previous=this.getUnplacedChoice(group,choice);return previous},DragDropToTextQuestion.prototype.animateTo=function(drag,target){var currentPos=drag.offset(),targetPos=target.offset(),thisQ=this;M.util.js_pending("qtype_ddwtos-animate-"+thisQ.containerId),drag.animate({left:parseInt(drag.css("left"))+targetPos.left-currentPos.left,top:parseInt(drag.css("top"))+targetPos.top-currentPos.top},{duration:"fast",done:function(){$("body").trigger("qtype_ddwtos-dragmoved",[drag,target,thisQ]),M.util.js_complete("qtype_ddwtos-animate-"+thisQ.containerId)}})},DragDropToTextQuestion.prototype.isPointInDrop=function(pageX,pageY,drop){var position=drop.offset();return pageX>=position.left&&pageX=position.top&&pageY1&&thisQ.getInfiniteDragClones(drag,!0).first().remove()),void 0!==drag.data("isfocus")&&!0===drag.data("isfocus")&&(drag.focus(),drag.removeData("isfocus")),void 0!==target.data("isfocus")&&!0===target.data("isfocus")&&target.removeData("isfocus"),questionManager.isKeyboardNavigation&&(questionManager.isKeyboardNavigation=!1),thisQ.isQuestionInteracted()&&(questionManager.handleFormDirty(),thisQ.questionAnswer=thisQ.getQuestionAnsweredValues())},handleFormDirty:function(){const responseForm=document.getElementById("responseform");FormChangeChecker.markFormAsDirty(responseForm)}};return{init:questionManager.init}})); //# sourceMappingURL=ddwtos.min.js.map \ No newline at end of file diff --git a/question/type/ddwtos/amd/build/ddwtos.min.js.map b/question/type/ddwtos/amd/build/ddwtos.min.js.map index 87d5396e2c8..6c77f41e6c9 100644 --- a/question/type/ddwtos/amd/build/ddwtos.min.js.map +++ b/question/type/ddwtos/amd/build/ddwtos.min.js.map @@ -1 +1 @@ -{"version":3,"file":"ddwtos.min.js","sources":["../src/ddwtos.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 * JavaScript to make drag-drop into text questions work.\n *\n * Some vocabulary to help understand this code:\n *\n * The question text contains 'drops' - blanks into which the 'drags', the missing\n * words, can be put.\n *\n * The thing that can be moved into the drops are called 'drags'. There may be\n * multiple copies of the 'same' drag which does not really cause problems.\n * Each drag has a 'choice' number which is the value set on the drop's hidden\n * input when this drag is placed in a drop.\n *\n * These may be in separate 'groups', distinguished by colour.\n * Things can only interact with other things in the same group.\n * The groups are numbered from 1.\n *\n * The place where a given drag started from is called its 'home'.\n *\n * @module qtype_ddwtos/ddwtos\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.6\n */\ndefine([\n 'jquery',\n 'core/dragdrop',\n 'core/key_codes',\n 'core_form/changechecker'\n], function(\n $,\n dragDrop,\n keys,\n FormChangeChecker\n) {\n\n \"use strict\";\n\n /**\n * Object to handle one drag-drop into text question.\n *\n * @param {String} containerId id of the outer div for this question.\n * @param {boolean} readOnly whether the question is being displayed read-only.\n * @constructor\n */\n function DragDropToTextQuestion(containerId, readOnly) {\n this.containerId = containerId;\n this.questionAnswer = {};\n if (readOnly) {\n this.getRoot().addClass('qtype_ddwtos-readonly');\n }\n this.resizeAllDragsAndDrops();\n this.cloneDrags();\n this.positionDrags();\n }\n\n /**\n * In each group, resize all the items to be the same size.\n */\n DragDropToTextQuestion.prototype.resizeAllDragsAndDrops = function() {\n var thisQ = this;\n this.getRoot().find('.answercontainer > div').each(function(i, node) {\n thisQ.resizeAllDragsAndDropsInGroup(\n thisQ.getClassnameNumericSuffix($(node), 'draggrouphomes'));\n });\n };\n\n /**\n * In a given group, set all the drags and drops to be the same size.\n *\n * @param {int} group the group number.\n */\n DragDropToTextQuestion.prototype.resizeAllDragsAndDropsInGroup = function(group) {\n var thisQ = this,\n dragHomes = this.getRoot().find('.draggrouphomes' + group + ' span.draghome'),\n maxWidth = 0,\n maxHeight = 0;\n\n // Find the maximum size of any drag in this groups.\n dragHomes.each(function(i, drag) {\n maxWidth = Math.max(maxWidth, Math.ceil(drag.offsetWidth));\n maxHeight = Math.max(maxHeight, Math.ceil(0 + drag.offsetHeight));\n });\n\n // The size we will want to set is a bit bigger than this.\n maxWidth += 8;\n maxHeight += 2;\n\n // Set each drag home to that size.\n dragHomes.each(function(i, drag) {\n thisQ.setElementSize(drag, maxWidth, maxHeight);\n });\n\n // Set each drop to that size.\n this.getRoot().find('span.drop.group' + group).each(function(i, drop) {\n thisQ.setElementSize(drop, maxWidth, maxHeight);\n });\n };\n\n /**\n * Set a given DOM element to be a particular size.\n *\n * @param {HTMLElement} element\n * @param {int} width\n * @param {int} height\n */\n DragDropToTextQuestion.prototype.setElementSize = function(element, width, height) {\n $(element).width(width).height(height).css('lineHeight', height + 'px');\n };\n\n /**\n * Invisible 'drag homes' are output by the renderer. These have the same properties\n * as the drag items but are invisible. We clone these invisible elements to make the\n * actual drag items.\n */\n DragDropToTextQuestion.prototype.cloneDrags = function() {\n var thisQ = this;\n thisQ.getRoot().find('span.draghome').each(function(index, draghome) {\n var drag = $(draghome);\n var placeHolder = drag.clone();\n placeHolder.removeClass();\n placeHolder.addClass('draghome choice' +\n thisQ.getChoice(drag) + ' group' +\n thisQ.getGroup(drag) + ' dragplaceholder');\n drag.before(placeHolder);\n });\n };\n\n /**\n * Update the position of drags.\n */\n DragDropToTextQuestion.prototype.positionDrags = function() {\n var thisQ = this,\n root = this.getRoot();\n\n // First move all items back home.\n root.find('span.draghome').not('.dragplaceholder').each(function(i, dragNode) {\n var drag = $(dragNode),\n currentPlace = thisQ.getClassnameNumericSuffix(drag, 'inplace');\n drag.addClass('unplaced')\n .removeClass('placed');\n drag.removeAttr('tabindex');\n if (currentPlace !== null) {\n drag.removeClass('inplace' + currentPlace);\n }\n });\n\n // Then place the once that should be placed.\n root.find('input.placeinput').each(function(i, inputNode) {\n var input = $(inputNode),\n choice = input.val(),\n place = thisQ.getPlace(input);\n\n // Record the last known position of the drop.\n var drop = root.find('.drop.place' + place),\n dropPosition = drop.offset();\n drop.data('prev-top', dropPosition.top).data('prev-left', dropPosition.left);\n\n if (choice === '0') {\n // No item in this place.\n return;\n }\n\n // Get the unplaced drag.\n var unplacedDrag = thisQ.getUnplacedChoice(thisQ.getGroup(input), choice);\n // Get the clone of the drag.\n var hiddenDrag = thisQ.getDragClone(unplacedDrag);\n if (hiddenDrag.length) {\n if (unplacedDrag.hasClass('infinite')) {\n var noOfDrags = thisQ.noOfDropsInGroup(thisQ.getGroup(unplacedDrag));\n var cloneDrags = thisQ.getInfiniteDragClones(unplacedDrag, false);\n if (cloneDrags.length < noOfDrags) {\n var cloneDrag = unplacedDrag.clone();\n hiddenDrag.after(cloneDrag);\n questionManager.addEventHandlersToDrag(cloneDrag);\n } else {\n hiddenDrag.addClass('active');\n }\n } else {\n hiddenDrag.addClass('active');\n }\n }\n // Send the drag to drop.\n thisQ.sendDragToDrop(thisQ.getUnplacedChoice(thisQ.getGroup(input), choice), drop);\n });\n\n // Save the question answer.\n thisQ.questionAnswer = thisQ.getQuestionAnsweredValues();\n };\n\n /**\n * Get the question answered values.\n *\n * @return {Object} Contain key-value with key is the input id and value is the input value.\n */\n DragDropToTextQuestion.prototype.getQuestionAnsweredValues = function() {\n let result = {};\n this.getRoot().find('input.placeinput').each((i, inputNode) => {\n result[inputNode.id] = inputNode.value;\n });\n\n return result;\n };\n\n /**\n * Check if the question is being interacted or not.\n *\n * @return {boolean} Return true if the user has changed the question-answer.\n */\n DragDropToTextQuestion.prototype.isQuestionInteracted = function() {\n const oldAnswer = this.questionAnswer;\n const newAnswer = this.getQuestionAnsweredValues();\n let isInteracted = false;\n\n // First, check both answers have the same structure or not.\n if (JSON.stringify(newAnswer) !== JSON.stringify(oldAnswer)) {\n isInteracted = true;\n return isInteracted;\n }\n // Check the values.\n Object.keys(newAnswer).forEach(key => {\n if (newAnswer[key] !== oldAnswer[key]) {\n isInteracted = true;\n }\n });\n\n return isInteracted;\n };\n\n /**\n * Handles the start of dragging an item.\n *\n * @param {Event} e the touch start or mouse down event.\n */\n DragDropToTextQuestion.prototype.handleDragStart = function(e) {\n var thisQ = this,\n drag = $(e.target).closest('.draghome');\n\n var info = dragDrop.prepare(e);\n if (!info.start || drag.hasClass('beingdragged')) {\n return;\n }\n\n drag.addClass('beingdragged');\n var currentPlace = this.getClassnameNumericSuffix(drag, 'inplace');\n if (currentPlace !== null) {\n this.setInputValue(currentPlace, 0);\n drag.removeClass('inplace' + currentPlace);\n var hiddenDrop = thisQ.getDrop(drag, currentPlace);\n if (hiddenDrop.length) {\n hiddenDrop.addClass('active');\n drag.offset(hiddenDrop.offset());\n }\n } else {\n var hiddenDrag = thisQ.getDragClone(drag);\n if (hiddenDrag.length) {\n if (drag.hasClass('infinite')) {\n var noOfDrags = this.noOfDropsInGroup(this.getGroup(drag));\n var cloneDrags = this.getInfiniteDragClones(drag, false);\n if (cloneDrags.length < noOfDrags) {\n var cloneDrag = drag.clone();\n cloneDrag.removeClass('beingdragged');\n hiddenDrag.after(cloneDrag);\n questionManager.addEventHandlersToDrag(cloneDrag);\n drag.offset(cloneDrag.offset());\n } else {\n hiddenDrag.addClass('active');\n drag.offset(hiddenDrag.offset());\n }\n } else {\n hiddenDrag.addClass('active');\n drag.offset(hiddenDrag.offset());\n }\n }\n }\n\n dragDrop.start(e, drag, function(x, y, drag) {\n thisQ.dragMove(x, y, drag);\n }, function(x, y, drag) {\n thisQ.dragEnd(x, y, drag);\n });\n };\n\n /**\n * Called whenever the currently dragged items moves.\n *\n * @param {Number} pageX the x position.\n * @param {Number} pageY the y position.\n * @param {jQuery} drag the item being moved.\n */\n DragDropToTextQuestion.prototype.dragMove = function(pageX, pageY, drag) {\n var thisQ = this;\n this.getRoot().find('span.group' + this.getGroup(drag)).not('.beingdragged').each(function(i, dropNode) {\n var drop = $(dropNode);\n if (thisQ.isPointInDrop(pageX, pageY, drop)) {\n drop.addClass('valid-drag-over-drop');\n } else {\n drop.removeClass('valid-drag-over-drop');\n }\n });\n };\n\n /**\n * Called when user drops a drag item.\n *\n * @param {Number} pageX the x position.\n * @param {Number} pageY the y position.\n * @param {jQuery} drag the item being moved.\n */\n DragDropToTextQuestion.prototype.dragEnd = function(pageX, pageY, drag) {\n var thisQ = this,\n root = this.getRoot(),\n placed = false;\n root.find('span.group' + this.getGroup(drag)).not('.beingdragged').each(function(i, dropNode) {\n if (placed) {\n return false;\n }\n const dropZone = $(dropNode);\n if (!thisQ.isPointInDrop(pageX, pageY, dropZone)) {\n // Not this drop zone.\n return true;\n }\n let drop = null;\n if (dropZone.hasClass('placed')) {\n // This is an placed drag item in a drop.\n dropZone.removeClass('valid-drag-over-drop');\n // Get the correct drop.\n drop = thisQ.getDrop(drag, thisQ.getClassnameNumericSuffix(dropZone, 'inplace'));\n } else {\n // Empty drop.\n drop = dropZone;\n }\n // Now put this drag into the drop.\n drop.removeClass('valid-drag-over-drop');\n thisQ.sendDragToDrop(drag, drop);\n placed = true;\n return false; // Stop the each() here.\n });\n if (!placed) {\n this.sendDragHome(drag);\n }\n };\n\n /**\n * Animate a drag item into a given place (or back home).\n *\n * @param {jQuery|null} drag the item to place. If null, clear the place.\n * @param {jQuery} drop the place to put it.\n */\n DragDropToTextQuestion.prototype.sendDragToDrop = function(drag, drop) {\n // Send drag home if there is no place in drop.\n if (this.getPlace(drop) === null) {\n this.sendDragHome(drag);\n return;\n }\n\n // Is there already a drag in this drop? if so, evict it.\n var oldDrag = this.getCurrentDragInPlace(this.getPlace(drop));\n if (oldDrag.length !== 0) {\n var currentPlace = this.getClassnameNumericSuffix(oldDrag, 'inplace');\n var hiddenDrop = this.getDrop(oldDrag, currentPlace);\n hiddenDrop.addClass('active');\n oldDrag.addClass('beingdragged');\n oldDrag.offset(hiddenDrop.offset());\n this.sendDragHome(oldDrag);\n }\n\n if (drag.length === 0) {\n this.setInputValue(this.getPlace(drop), 0);\n if (drop.data('isfocus')) {\n drop.focus();\n }\n } else {\n // Prevent the drag item drop into two drop-zone.\n if (this.getClassnameNumericSuffix(drag, 'inplace')) {\n return;\n }\n\n this.setInputValue(this.getPlace(drop), this.getChoice(drag));\n drag.removeClass('unplaced')\n .addClass('placed inplace' + this.getPlace(drop));\n drag.attr('tabindex', 0);\n this.animateTo(drag, drop);\n }\n };\n\n /**\n * Animate a drag back to its home.\n *\n * @param {jQuery} drag the item being moved.\n */\n DragDropToTextQuestion.prototype.sendDragHome = function(drag) {\n var currentPlace = this.getClassnameNumericSuffix(drag, 'inplace');\n if (currentPlace !== null) {\n drag.removeClass('inplace' + currentPlace);\n }\n drag.data('unplaced', true);\n\n this.animateTo(drag, this.getDragHome(this.getGroup(drag), this.getChoice(drag)));\n };\n\n /**\n * Handles keyboard events on drops.\n *\n * Drops are focusable. Once focused, right/down/space switches to the next choice, and\n * left/up switches to the previous. Escape clear.\n *\n * @param {KeyboardEvent} e\n */\n DragDropToTextQuestion.prototype.handleKeyPress = function(e) {\n var drop = $(e.target).closest('.drop');\n if (drop.length === 0) {\n var placedDrag = $(e.target);\n var currentPlace = this.getClassnameNumericSuffix(placedDrag, 'inplace');\n if (currentPlace !== null) {\n drop = this.getDrop(placedDrag, currentPlace);\n }\n }\n var currentDrag = this.getCurrentDragInPlace(this.getPlace(drop)),\n nextDrag = $();\n\n switch (e.keyCode) {\n case keys.space:\n case keys.arrowRight:\n case keys.arrowDown:\n nextDrag = this.getNextDrag(this.getGroup(drop), currentDrag);\n break;\n\n case keys.arrowLeft:\n case keys.arrowUp:\n nextDrag = this.getPreviousDrag(this.getGroup(drop), currentDrag);\n break;\n\n case keys.escape:\n break;\n\n default:\n questionManager.isKeyboardNavigation = false;\n return; // To avoid the preventDefault below.\n }\n\n if (nextDrag.length) {\n nextDrag.data('isfocus', true);\n nextDrag.addClass('beingdragged');\n var hiddenDrag = this.getDragClone(nextDrag);\n if (hiddenDrag.length) {\n if (nextDrag.hasClass('infinite')) {\n var noOfDrags = this.noOfDropsInGroup(this.getGroup(nextDrag));\n var cloneDrags = this.getInfiniteDragClones(nextDrag, false);\n if (cloneDrags.length < noOfDrags) {\n var cloneDrag = nextDrag.clone();\n cloneDrag.removeClass('beingdragged');\n cloneDrag.removeAttr('tabindex');\n hiddenDrag.after(cloneDrag);\n questionManager.addEventHandlersToDrag(cloneDrag);\n nextDrag.offset(cloneDrag.offset());\n } else {\n hiddenDrag.addClass('active');\n nextDrag.offset(hiddenDrag.offset());\n }\n } else {\n hiddenDrag.addClass('active');\n nextDrag.offset(hiddenDrag.offset());\n }\n }\n } else {\n drop.data('isfocus', true);\n }\n\n e.preventDefault();\n this.sendDragToDrop(nextDrag, drop);\n };\n\n /**\n * Choose the next drag in a group.\n *\n * @param {int} group which group.\n * @param {jQuery} drag current choice (empty jQuery if there isn't one).\n * @return {jQuery} the next drag in that group, or null if there wasn't one.\n */\n DragDropToTextQuestion.prototype.getNextDrag = function(group, drag) {\n var choice,\n numChoices = this.noOfChoicesInGroup(group);\n\n if (drag.length === 0) {\n choice = 1; // Was empty, so we want to select the first choice.\n } else {\n choice = this.getChoice(drag) + 1;\n }\n\n var next = this.getUnplacedChoice(group, choice);\n while (next.length === 0 && choice < numChoices) {\n choice++;\n next = this.getUnplacedChoice(group, choice);\n }\n\n return next;\n };\n\n /**\n * Choose the previous drag in a group.\n *\n * @param {int} group which group.\n * @param {jQuery} drag current choice (empty jQuery if there isn't one).\n * @return {jQuery} the next drag in that group, or null if there wasn't one.\n */\n DragDropToTextQuestion.prototype.getPreviousDrag = function(group, drag) {\n var choice;\n\n if (drag.length === 0) {\n choice = this.noOfChoicesInGroup(group);\n } else {\n choice = this.getChoice(drag) - 1;\n }\n\n var previous = this.getUnplacedChoice(group, choice);\n while (previous.length === 0 && choice > 1) {\n choice--;\n previous = this.getUnplacedChoice(group, choice);\n }\n\n // Does this choice exist?\n return previous;\n };\n\n /**\n * Animate an object to the given destination.\n *\n * @param {jQuery} drag the element to be animated.\n * @param {jQuery} target element marking the place to move it to.\n */\n DragDropToTextQuestion.prototype.animateTo = function(drag, target) {\n var currentPos = drag.offset(),\n targetPos = target.offset(),\n thisQ = this;\n\n M.util.js_pending('qtype_ddwtos-animate-' + thisQ.containerId);\n // Animate works in terms of CSS position, whereas locating an object\n // on the page works best with jQuery offset() function. So, to get\n // the right target position, we work out the required change in\n // offset() and then add that to the current CSS position.\n drag.animate(\n {\n left: parseInt(drag.css('left')) + targetPos.left - currentPos.left,\n top: parseInt(drag.css('top')) + targetPos.top - currentPos.top\n },\n {\n duration: 'fast',\n done: function() {\n $('body').trigger('qtype_ddwtos-dragmoved', [drag, target, thisQ]);\n M.util.js_complete('qtype_ddwtos-animate-' + thisQ.containerId);\n }\n }\n );\n };\n\n /**\n * Detect if a point is inside a given DOM node.\n *\n * @param {Number} pageX the x position.\n * @param {Number} pageY the y position.\n * @param {jQuery} drop the node to check (typically a drop).\n * @return {boolean} whether the point is inside the node.\n */\n DragDropToTextQuestion.prototype.isPointInDrop = function(pageX, pageY, drop) {\n var position = drop.offset();\n return pageX >= position.left && pageX < position.left + drop.width()\n && pageY >= position.top && pageY < position.top + drop.height();\n };\n\n /**\n * Set the value of the hidden input for a place, to record what is currently there.\n *\n * @param {int} place which place to set the input value for.\n * @param {int} choice the value to set.\n */\n DragDropToTextQuestion.prototype.setInputValue = function(place, choice) {\n this.getRoot().find('input.placeinput.place' + place).val(choice);\n };\n\n /**\n * Get the outer div for this question.\n *\n * @returns {jQuery} containing that div.\n */\n DragDropToTextQuestion.prototype.getRoot = function() {\n return $(document.getElementById(this.containerId));\n };\n\n /**\n * Get drag home for a given choice.\n *\n * @param {int} group the group.\n * @param {int} choice the choice number.\n * @returns {jQuery} containing that div.\n */\n DragDropToTextQuestion.prototype.getDragHome = function(group, choice) {\n if (!this.getRoot().find('.draghome.dragplaceholder.group' + group + '.choice' + choice).is(':visible')) {\n return this.getRoot().find('.draggrouphomes' + group +\n ' span.draghome.infinite' +\n '.choice' + choice +\n '.group' + group);\n }\n return this.getRoot().find('.draghome.dragplaceholder.group' + group + '.choice' + choice);\n };\n\n /**\n * Get an unplaced choice for a particular group.\n *\n * @param {int} group the group.\n * @param {int} choice the choice number.\n * @returns {jQuery} jQuery wrapping the unplaced choice. If there isn't one, the jQuery will be empty.\n */\n DragDropToTextQuestion.prototype.getUnplacedChoice = function(group, choice) {\n return this.getRoot().find('.draghome.group' + group + '.choice' + choice + '.unplaced').slice(0, 1);\n };\n\n /**\n * Get the drag that is currently in a given place.\n *\n * @param {int} place the place number.\n * @return {jQuery} the current drag (or an empty jQuery if none).\n */\n DragDropToTextQuestion.prototype.getCurrentDragInPlace = function(place) {\n return this.getRoot().find('span.draghome.inplace' + place);\n };\n\n /**\n * Return the number of blanks in a given group.\n *\n * @param {int} group the group number.\n * @returns {int} the number of drops.\n */\n DragDropToTextQuestion.prototype.noOfDropsInGroup = function(group) {\n return this.getRoot().find('.drop.group' + group).length;\n };\n\n /**\n * Return the number of choices in a given group.\n *\n * @param {int} group the group number.\n * @returns {int} the number of choices.\n */\n DragDropToTextQuestion.prototype.noOfChoicesInGroup = function(group) {\n return this.getRoot().find('.draghome.group' + group).length;\n };\n\n /**\n * Return the number at the end of the CSS class name with the given prefix.\n *\n * @param {jQuery} node\n * @param {String} prefix name prefix\n * @returns {Number|null} the suffix if found, else null.\n */\n DragDropToTextQuestion.prototype.getClassnameNumericSuffix = function(node, prefix) {\n var classes = node.attr('class');\n if (classes !== undefined && classes !== '') {\n var classesArr = classes.split(' ');\n for (var index = 0; index < classesArr.length; index++) {\n var patt1 = new RegExp('^' + prefix + '([0-9])+$');\n if (patt1.test(classesArr[index])) {\n var patt2 = new RegExp('([0-9])+$');\n var match = patt2.exec(classesArr[index]);\n return Number(match[0]);\n }\n }\n }\n return null;\n };\n\n /**\n * Get the choice number of a drag.\n *\n * @param {jQuery} drag the drag.\n * @returns {Number} the choice number.\n */\n DragDropToTextQuestion.prototype.getChoice = function(drag) {\n return this.getClassnameNumericSuffix(drag, 'choice');\n };\n\n /**\n * Given a DOM node that is significant to this question\n * (drag, drop, ...) get the group it belongs to.\n *\n * @param {jQuery} node a DOM node.\n * @returns {Number} the group it belongs to.\n */\n DragDropToTextQuestion.prototype.getGroup = function(node) {\n return this.getClassnameNumericSuffix(node, 'group');\n };\n\n /**\n * Get the place number of a drop, or its corresponding hidden input.\n *\n * @param {jQuery} node the DOM node.\n * @returns {Number} the place number.\n */\n DragDropToTextQuestion.prototype.getPlace = function(node) {\n return this.getClassnameNumericSuffix(node, 'place');\n };\n\n /**\n * Get drag clone for a given drag.\n *\n * @param {jQuery} drag the drag.\n * @returns {jQuery} the drag's clone.\n */\n DragDropToTextQuestion.prototype.getDragClone = function(drag) {\n return this.getRoot().find('.draggrouphomes' +\n this.getGroup(drag) +\n ' span.draghome' +\n '.choice' + this.getChoice(drag) +\n '.group' + this.getGroup(drag) +\n '.dragplaceholder');\n };\n\n /**\n * Get infinite drag clones for given drag.\n *\n * @param {jQuery} drag the drag.\n * @param {Boolean} inHome in the home area or not.\n * @returns {jQuery} the drag's clones.\n */\n DragDropToTextQuestion.prototype.getInfiniteDragClones = function(drag, inHome) {\n if (inHome) {\n return this.getRoot().find('.draggrouphomes' +\n this.getGroup(drag) +\n ' span.draghome' +\n '.choice' + this.getChoice(drag) +\n '.group' + this.getGroup(drag) +\n '.infinite').not('.dragplaceholder');\n }\n return this.getRoot().find('span.draghome' +\n '.choice' + this.getChoice(drag) +\n '.group' + this.getGroup(drag) +\n '.infinite').not('.dragplaceholder');\n };\n\n /**\n * Get drop for a given drag and place.\n *\n * @param {jQuery} drag the drag.\n * @param {Integer} currentPlace the current place of drag.\n * @returns {jQuery} the drop's clone.\n */\n DragDropToTextQuestion.prototype.getDrop = function(drag, currentPlace) {\n return this.getRoot().find('.drop.group' + this.getGroup(drag) + '.place' + currentPlace);\n };\n\n /**\n * Singleton that tracks all the DragDropToTextQuestions on this page, and deals\n * with event dispatching.\n *\n * @type {Object}\n */\n var questionManager = {\n /**\n * {boolean} used to ensure the event handlers are only initialised once per page.\n */\n eventHandlersInitialised: false,\n\n /**\n * {Object} ensures that the drag event handlers are only initialised once per question,\n * indexed by containerId (id on the .que div).\n */\n dragEventHandlersInitialised: {},\n\n /**\n * {boolean} is keyboard navigation or not.\n */\n isKeyboardNavigation: false,\n\n /**\n * {DragDropToTextQuestion[]} all the questions on this page, indexed by containerId (id on the .que div).\n */\n questions: {},\n\n /**\n * Initialise questions.\n *\n * @param {String} containerId id of the outer div for this question.\n * @param {boolean} readOnly whether the question is being displayed read-only.\n */\n init: function(containerId, readOnly) {\n questionManager.questions[containerId] = new DragDropToTextQuestion(containerId, readOnly);\n if (!questionManager.eventHandlersInitialised) {\n questionManager.setupEventHandlers();\n questionManager.eventHandlersInitialised = true;\n }\n if (!questionManager.dragEventHandlersInitialised.hasOwnProperty(containerId)) {\n questionManager.dragEventHandlersInitialised[containerId] = true;\n // We do not use the body event here to prevent the other event on Mobile device, such as scroll event.\n var questionContainer = document.getElementById(containerId);\n if (questionContainer.classList.contains('ddwtos') &&\n !questionContainer.classList.contains('qtype_ddwtos-readonly')) {\n // TODO: Convert all the jQuery selectors and events to native Javascript.\n questionManager.addEventHandlersToDrag($(questionContainer).find('span.draghome'));\n }\n }\n },\n\n /**\n * Set up the event handlers that make this question type work. (Done once per page.)\n */\n setupEventHandlers: function() {\n $('body')\n .on('keydown',\n '.que.ddwtos:not(.qtype_ddwtos-readonly) span.drop',\n questionManager.handleKeyPress)\n .on('keydown',\n '.que.ddwtos:not(.qtype_ddwtos-readonly) span.draghome.placed:not(.beingdragged)',\n questionManager.handleKeyPress)\n .on('qtype_ddwtos-dragmoved', questionManager.handleDragMoved);\n },\n\n /**\n * Binding the drag/touch event again for newly created element.\n *\n * @param {jQuery} element Element to bind the event\n */\n addEventHandlersToDrag: function(element) {\n // Unbind all the mousedown and touchstart events to prevent double binding.\n element.unbind('mousedown touchstart');\n element.on('mousedown touchstart', questionManager.handleDragStart);\n },\n\n /**\n * Handle mouse down / touch start on drags.\n * @param {Event} e the DOM event.\n */\n handleDragStart: function(e) {\n e.preventDefault();\n var question = questionManager.getQuestionForEvent(e);\n if (question) {\n question.handleDragStart(e);\n }\n },\n\n /**\n * Handle key down / press on drops.\n * @param {KeyboardEvent} e\n */\n handleKeyPress: function(e) {\n if (questionManager.isKeyboardNavigation) {\n return;\n }\n questionManager.isKeyboardNavigation = true;\n var question = questionManager.getQuestionForEvent(e);\n if (question) {\n question.handleKeyPress(e);\n }\n },\n\n /**\n * Given an event, work out which question it affects.\n *\n * @param {Event} e the event.\n * @returns {DragDropToTextQuestion|undefined} The question, or undefined.\n */\n getQuestionForEvent: function(e) {\n var containerId = $(e.currentTarget).closest('.que.ddwtos').attr('id');\n return questionManager.questions[containerId];\n },\n\n /**\n * Handle when drag moved.\n *\n * @param {Event} e the event.\n * @param {jQuery} drag the drag\n * @param {jQuery} target the target\n * @param {DragDropToTextQuestion} thisQ the question.\n */\n handleDragMoved: function(e, drag, target, thisQ) {\n drag.removeClass('beingdragged');\n drag.css('top', '').css('left', '');\n target.after(drag);\n target.removeClass('active');\n if (typeof drag.data('unplaced') !== 'undefined' && drag.data('unplaced') === true) {\n drag.removeClass('placed').addClass('unplaced');\n drag.removeAttr('tabindex');\n drag.removeData('unplaced');\n if (drag.hasClass('infinite') && thisQ.getInfiniteDragClones(drag, true).length > 1) {\n thisQ.getInfiniteDragClones(drag, true).first().remove();\n }\n }\n if (typeof drag.data('isfocus') !== 'undefined' && drag.data('isfocus') === true) {\n drag.focus();\n drag.removeData('isfocus');\n }\n if (typeof target.data('isfocus') !== 'undefined' && target.data('isfocus') === true) {\n target.removeData('isfocus');\n }\n if (questionManager.isKeyboardNavigation) {\n questionManager.isKeyboardNavigation = false;\n }\n if (thisQ.isQuestionInteracted()) {\n // The user has interacted with the draggable items. We need to mark the form as dirty.\n questionManager.handleFormDirty();\n // Save the new answered value.\n thisQ.questionAnswer = thisQ.getQuestionAnsweredValues();\n }\n },\n\n /**\n * Handle when the form is dirty.\n */\n handleFormDirty: function() {\n const responseForm = document.getElementById('responseform');\n FormChangeChecker.markFormAsDirty(responseForm);\n }\n };\n\n /**\n * @alias module:qtype_ddwtos/ddwtos\n */\n return {\n /**\n * Initialise one drag-drop into text question.\n *\n * @param {String} containerId id of the outer div for this question.\n * @param {boolean} readOnly whether the question is being displayed read-only.\n */\n init: questionManager.init\n };\n});\n"],"names":["define","$","dragDrop","keys","FormChangeChecker","DragDropToTextQuestion","containerId","readOnly","questionAnswer","getRoot","addClass","resizeAllDragsAndDrops","cloneDrags","positionDrags","prototype","thisQ","this","find","each","i","node","resizeAllDragsAndDropsInGroup","getClassnameNumericSuffix","group","dragHomes","maxWidth","maxHeight","drag","Math","max","ceil","offsetWidth","offsetHeight","setElementSize","drop","element","width","height","css","index","draghome","placeHolder","clone","removeClass","getChoice","getGroup","before","root","not","dragNode","currentPlace","removeAttr","inputNode","input","choice","val","place","getPlace","dropPosition","offset","data","top","left","unplacedDrag","getUnplacedChoice","hiddenDrag","getDragClone","length","hasClass","noOfDrags","noOfDropsInGroup","getInfiniteDragClones","cloneDrag","after","questionManager","addEventHandlersToDrag","sendDragToDrop","getQuestionAnsweredValues","result","id","value","isQuestionInteracted","oldAnswer","newAnswer","isInteracted","JSON","stringify","Object","forEach","key","handleDragStart","e","target","closest","prepare","start","setInputValue","hiddenDrop","getDrop","x","y","dragMove","dragEnd","pageX","pageY","dropNode","isPointInDrop","placed","dropZone","sendDragHome","oldDrag","getCurrentDragInPlace","focus","attr","animateTo","getDragHome","handleKeyPress","placedDrag","currentDrag","nextDrag","keyCode","space","arrowRight","arrowDown","getNextDrag","arrowLeft","arrowUp","getPreviousDrag","escape","isKeyboardNavigation","preventDefault","numChoices","noOfChoicesInGroup","next","previous","currentPos","targetPos","M","util","js_pending","animate","parseInt","duration","done","trigger","js_complete","position","document","getElementById","is","slice","prefix","classes","undefined","classesArr","split","RegExp","test","match","exec","Number","inHome","eventHandlersInitialised","dragEventHandlersInitialised","questions","init","setupEventHandlers","hasOwnProperty","questionContainer","classList","contains","on","handleDragMoved","unbind","question","getQuestionForEvent","currentTarget","removeData","first","remove","handleFormDirty","responseForm","markFormAsDirty"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"ddwtos.min.js","sources":["../src/ddwtos.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 * JavaScript to make drag-drop into text questions work.\n *\n * Some vocabulary to help understand this code:\n *\n * The question text contains 'drops' - blanks into which the 'drags', the missing\n * words, can be put.\n *\n * The thing that can be moved into the drops are called 'drags'. There may be\n * multiple copies of the 'same' drag which does not really cause problems.\n * Each drag has a 'choice' number which is the value set on the drop's hidden\n * input when this drag is placed in a drop.\n *\n * These may be in separate 'groups', distinguished by colour.\n * Things can only interact with other things in the same group.\n * The groups are numbered from 1.\n *\n * The place where a given drag started from is called its 'home'.\n *\n * @module qtype_ddwtos/ddwtos\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.6\n */\ndefine([\n 'jquery',\n 'core/dragdrop',\n 'core/key_codes',\n 'core_form/changechecker'\n], function(\n $,\n dragDrop,\n keys,\n FormChangeChecker\n) {\n\n \"use strict\";\n\n /**\n * Object to handle one drag-drop into text question.\n *\n * @param {String} containerId id of the outer div for this question.\n * @param {boolean} readOnly whether the question is being displayed read-only.\n * @constructor\n */\n function DragDropToTextQuestion(containerId, readOnly) {\n this.containerId = containerId;\n this.questionAnswer = {};\n if (readOnly) {\n this.getRoot().addClass('qtype_ddwtos-readonly');\n }\n this.resizeAllDragsAndDrops();\n this.cloneDrags();\n this.positionDrags();\n }\n\n /**\n * In each group, resize all the items to be the same size.\n */\n DragDropToTextQuestion.prototype.resizeAllDragsAndDrops = function() {\n var thisQ = this;\n this.getRoot().find('.answercontainer > div').each(function(i, node) {\n thisQ.resizeAllDragsAndDropsInGroup(\n thisQ.getClassnameNumericSuffix($(node), 'draggrouphomes'));\n });\n };\n\n /**\n * In a given group, set all the drags and drops to be the same size.\n *\n * @param {int} group the group number.\n */\n DragDropToTextQuestion.prototype.resizeAllDragsAndDropsInGroup = function(group) {\n var thisQ = this,\n dragHomes = this.getRoot().find('.draggrouphomes' + group + ' span.draghome'),\n maxWidth = 0,\n maxHeight = 0;\n\n // Find the maximum size of any drag in this groups.\n dragHomes.each(function(i, drag) {\n maxWidth = Math.max(maxWidth, Math.ceil(drag.offsetWidth));\n maxHeight = Math.max(maxHeight, Math.ceil(0 + drag.offsetHeight));\n });\n\n // The size we will want to set is a bit bigger than this.\n maxWidth += 8;\n maxHeight += 2;\n\n // Set each drag home to that size.\n dragHomes.each(function(i, drag) {\n thisQ.setElementSize(drag, maxWidth, maxHeight);\n });\n\n // Set each drop to that size.\n this.getRoot().find('span.drop.group' + group).each(function(i, drop) {\n thisQ.setElementSize(drop, maxWidth, maxHeight);\n });\n };\n\n /**\n * Set a given DOM element to be a particular size.\n *\n * @param {HTMLElement} element\n * @param {int} width\n * @param {int} height\n */\n DragDropToTextQuestion.prototype.setElementSize = function(element, width, height) {\n $(element).width(width).height(height).css('lineHeight', height + 'px');\n };\n\n /**\n * Invisible 'drag homes' are output by the renderer. These have the same properties\n * as the drag items but are invisible. We clone these invisible elements to make the\n * actual drag items.\n */\n DragDropToTextQuestion.prototype.cloneDrags = function() {\n var thisQ = this;\n thisQ.getRoot().find('span.draghome').each(function(index, draghome) {\n var drag = $(draghome);\n var placeHolder = drag.clone();\n placeHolder.removeClass();\n placeHolder.addClass('draghome choice' +\n thisQ.getChoice(drag) + ' group' +\n thisQ.getGroup(drag) + ' dragplaceholder');\n drag.before(placeHolder);\n });\n };\n\n /**\n * Update the position of drags.\n */\n DragDropToTextQuestion.prototype.positionDrags = function() {\n var thisQ = this,\n root = this.getRoot();\n\n // First move all items back home.\n root.find('span.draghome').not('.dragplaceholder').each(function(i, dragNode) {\n var drag = $(dragNode),\n currentPlace = thisQ.getClassnameNumericSuffix(drag, 'inplace');\n drag.addClass('unplaced')\n .removeClass('placed');\n drag.removeAttr('tabindex');\n if (currentPlace !== null) {\n drag.removeClass('inplace' + currentPlace);\n }\n });\n\n // Then place the once that should be placed.\n root.find('input.placeinput').each(function(i, inputNode) {\n var input = $(inputNode),\n choice = input.val(),\n place = thisQ.getPlace(input);\n\n // Record the last known position of the drop.\n var drop = root.find('.drop.place' + place),\n dropPosition = drop.offset();\n drop.data('prev-top', dropPosition.top).data('prev-left', dropPosition.left);\n\n if (choice === '0') {\n // No item in this place.\n return;\n }\n\n // Get the unplaced drag.\n var unplacedDrag = thisQ.getUnplacedChoice(thisQ.getGroup(input), choice);\n // Get the clone of the drag.\n var hiddenDrag = thisQ.getDragClone(unplacedDrag);\n if (hiddenDrag.length) {\n if (unplacedDrag.hasClass('infinite')) {\n var noOfDrags = thisQ.noOfDropsInGroup(thisQ.getGroup(unplacedDrag));\n var cloneDrags = thisQ.getInfiniteDragClones(unplacedDrag, false);\n if (cloneDrags.length < noOfDrags) {\n var cloneDrag = unplacedDrag.clone();\n hiddenDrag.after(cloneDrag);\n questionManager.addEventHandlersToDrag(cloneDrag);\n } else {\n hiddenDrag.addClass('active');\n }\n } else {\n hiddenDrag.addClass('active');\n }\n }\n // Send the drag to drop.\n thisQ.sendDragToDrop(thisQ.getUnplacedChoice(thisQ.getGroup(input), choice), drop);\n });\n\n // Save the question answer.\n thisQ.questionAnswer = thisQ.getQuestionAnsweredValues();\n };\n\n /**\n * Get the question answered values.\n *\n * @return {Object} Contain key-value with key is the input id and value is the input value.\n */\n DragDropToTextQuestion.prototype.getQuestionAnsweredValues = function() {\n let result = {};\n this.getRoot().find('input.placeinput').each((i, inputNode) => {\n result[inputNode.id] = inputNode.value;\n });\n\n return result;\n };\n\n /**\n * Check if the question is being interacted or not.\n *\n * @return {boolean} Return true if the user has changed the question-answer.\n */\n DragDropToTextQuestion.prototype.isQuestionInteracted = function() {\n const oldAnswer = this.questionAnswer;\n const newAnswer = this.getQuestionAnsweredValues();\n let isInteracted = false;\n\n // First, check both answers have the same structure or not.\n if (JSON.stringify(newAnswer) !== JSON.stringify(oldAnswer)) {\n isInteracted = true;\n return isInteracted;\n }\n // Check the values.\n Object.keys(newAnswer).forEach(key => {\n if (newAnswer[key] !== oldAnswer[key]) {\n isInteracted = true;\n }\n });\n\n return isInteracted;\n };\n\n /**\n * Handles the start of dragging an item.\n *\n * @param {Event} e the touch start or mouse down event.\n */\n DragDropToTextQuestion.prototype.handleDragStart = function(e) {\n var thisQ = this,\n drag = $(e.target).closest('.draghome');\n\n var info = dragDrop.prepare(e);\n if (!info.start || drag.hasClass('beingdragged')) {\n return;\n }\n\n drag.addClass('beingdragged');\n var currentPlace = this.getClassnameNumericSuffix(drag, 'inplace');\n if (currentPlace !== null) {\n this.setInputValue(currentPlace, 0);\n drag.removeClass('inplace' + currentPlace);\n var hiddenDrop = thisQ.getDrop(drag, currentPlace);\n if (hiddenDrop.length) {\n hiddenDrop.addClass('active');\n drag.offset(hiddenDrop.offset());\n }\n } else {\n var hiddenDrag = thisQ.getDragClone(drag);\n if (hiddenDrag.length) {\n if (drag.hasClass('infinite')) {\n var noOfDrags = this.noOfDropsInGroup(this.getGroup(drag));\n var cloneDrags = this.getInfiniteDragClones(drag, false);\n if (cloneDrags.length < noOfDrags) {\n var cloneDrag = drag.clone();\n cloneDrag.removeClass('beingdragged');\n hiddenDrag.after(cloneDrag);\n questionManager.addEventHandlersToDrag(cloneDrag);\n drag.offset(cloneDrag.offset());\n } else {\n hiddenDrag.addClass('active');\n drag.offset(hiddenDrag.offset());\n }\n } else {\n hiddenDrag.addClass('active');\n drag.offset(hiddenDrag.offset());\n }\n }\n }\n\n dragDrop.start(e, drag, function(x, y, drag) {\n thisQ.dragMove(x, y, drag);\n }, function(x, y, drag) {\n thisQ.dragEnd(x, y, drag);\n });\n };\n\n /**\n * Called whenever the currently dragged items moves.\n *\n * @param {Number} pageX the x position.\n * @param {Number} pageY the y position.\n * @param {jQuery} drag the item being moved.\n */\n DragDropToTextQuestion.prototype.dragMove = function(pageX, pageY, drag) {\n var thisQ = this;\n this.getRoot().find('span.group' + this.getGroup(drag)).not('.beingdragged').each(function(i, dropNode) {\n var drop = $(dropNode);\n if (thisQ.isPointInDrop(pageX, pageY, drop)) {\n drop.addClass('valid-drag-over-drop');\n } else {\n drop.removeClass('valid-drag-over-drop');\n }\n });\n };\n\n /**\n * Called when user drops a drag item.\n *\n * @param {Number} pageX the x position.\n * @param {Number} pageY the y position.\n * @param {jQuery} drag the item being moved.\n */\n DragDropToTextQuestion.prototype.dragEnd = function(pageX, pageY, drag) {\n var thisQ = this,\n root = this.getRoot(),\n placed = false;\n root.find('span.group' + this.getGroup(drag)).not('.beingdragged').each(function(i, dropNode) {\n if (placed) {\n return false;\n }\n const dropZone = $(dropNode);\n if (!thisQ.isPointInDrop(pageX, pageY, dropZone)) {\n // Not this drop zone.\n return true;\n }\n let drop = null;\n if (dropZone.hasClass('placed')) {\n // This is an placed drag item in a drop.\n dropZone.removeClass('valid-drag-over-drop');\n // Get the correct drop.\n drop = thisQ.getDrop(drag, thisQ.getClassnameNumericSuffix(dropZone, 'inplace'));\n } else {\n // Empty drop.\n drop = dropZone;\n }\n // Now put this drag into the drop.\n drop.removeClass('valid-drag-over-drop');\n thisQ.sendDragToDrop(drag, drop);\n placed = true;\n return false; // Stop the each() here.\n });\n if (!placed) {\n this.sendDragHome(drag);\n }\n };\n\n /**\n * Animate a drag item into a given place (or back home).\n *\n * @param {jQuery|null} drag the item to place. If null, clear the place.\n * @param {jQuery} drop the place to put it.\n */\n DragDropToTextQuestion.prototype.sendDragToDrop = function(drag, drop) {\n // Send drag home if there is no place in drop.\n if (this.getPlace(drop) === null) {\n this.sendDragHome(drag);\n return;\n }\n\n // Is there already a drag in this drop? if so, evict it.\n var oldDrag = this.getCurrentDragInPlace(this.getPlace(drop));\n if (oldDrag.length !== 0) {\n var currentPlace = this.getClassnameNumericSuffix(oldDrag, 'inplace');\n // When infinite group and there is already a drag in a drop, reject the exact clone in the same drop.\n if (this.hasDropSameDrag(currentPlace, drop, oldDrag, drag)) {\n this.sendDragHome(drag);\n return;\n }\n var hiddenDrop = this.getDrop(oldDrag, currentPlace);\n hiddenDrop.addClass('active');\n oldDrag.addClass('beingdragged');\n oldDrag.offset(hiddenDrop.offset());\n this.sendDragHome(oldDrag);\n }\n\n if (drag.length === 0) {\n this.setInputValue(this.getPlace(drop), 0);\n if (drop.data('isfocus')) {\n drop.focus();\n }\n } else {\n // Prevent the drag item drop into two drop-zone.\n if (this.getClassnameNumericSuffix(drag, 'inplace')) {\n return;\n }\n\n this.setInputValue(this.getPlace(drop), this.getChoice(drag));\n drag.removeClass('unplaced')\n .addClass('placed inplace' + this.getPlace(drop));\n drag.attr('tabindex', 0);\n this.animateTo(drag, drop);\n }\n };\n\n /**\n * When infinite group and there is already a drag in a drop, reject the exact clone in the same drop.\n *\n * @param {int} currentPlace the position of the current drop.\n * @param {jQuery} drop the drop containing a drag.\n * @param {jQuery} oldDrag the drag already placed in drop.\n * @param {jQuery} drag the new drag which is exactly the same (clone) as oldDrag .\n * @returns {boolean}\n */\n DragDropToTextQuestion.prototype.hasDropSameDrag = function(currentPlace, drop, oldDrag, drag) {\n if (drag.hasClass('infinite')) {\n return drop.hasClass('place' + currentPlace) &&\n this.getGroup(drag) === this.getGroup(drop) &&\n this.getChoice(drag) === this.getChoice(oldDrag) &&\n this.getGroup(drag) === this.getGroup(oldDrag);\n }\n return false;\n };\n\n /**\n * Animate a drag back to its home.\n *\n * @param {jQuery} drag the item being moved.\n */\n DragDropToTextQuestion.prototype.sendDragHome = function(drag) {\n var currentPlace = this.getClassnameNumericSuffix(drag, 'inplace');\n if (currentPlace !== null) {\n drag.removeClass('inplace' + currentPlace);\n }\n drag.data('unplaced', true);\n\n this.animateTo(drag, this.getDragHome(this.getGroup(drag), this.getChoice(drag)));\n };\n\n /**\n * Handles keyboard events on drops.\n *\n * Drops are focusable. Once focused, right/down/space switches to the next choice, and\n * left/up switches to the previous. Escape clear.\n *\n * @param {KeyboardEvent} e\n */\n DragDropToTextQuestion.prototype.handleKeyPress = function(e) {\n var drop = $(e.target).closest('.drop');\n if (drop.length === 0) {\n var placedDrag = $(e.target);\n var currentPlace = this.getClassnameNumericSuffix(placedDrag, 'inplace');\n if (currentPlace !== null) {\n drop = this.getDrop(placedDrag, currentPlace);\n }\n }\n var currentDrag = this.getCurrentDragInPlace(this.getPlace(drop)),\n nextDrag = $();\n\n switch (e.keyCode) {\n case keys.space:\n case keys.arrowRight:\n case keys.arrowDown:\n nextDrag = this.getNextDrag(this.getGroup(drop), currentDrag);\n break;\n\n case keys.arrowLeft:\n case keys.arrowUp:\n nextDrag = this.getPreviousDrag(this.getGroup(drop), currentDrag);\n break;\n\n case keys.escape:\n break;\n\n default:\n questionManager.isKeyboardNavigation = false;\n return; // To avoid the preventDefault below.\n }\n\n if (nextDrag.length) {\n nextDrag.data('isfocus', true);\n nextDrag.addClass('beingdragged');\n var hiddenDrag = this.getDragClone(nextDrag);\n if (hiddenDrag.length) {\n if (nextDrag.hasClass('infinite')) {\n var noOfDrags = this.noOfDropsInGroup(this.getGroup(nextDrag));\n var cloneDrags = this.getInfiniteDragClones(nextDrag, false);\n if (cloneDrags.length < noOfDrags) {\n var cloneDrag = nextDrag.clone();\n cloneDrag.removeClass('beingdragged');\n cloneDrag.removeAttr('tabindex');\n hiddenDrag.after(cloneDrag);\n questionManager.addEventHandlersToDrag(cloneDrag);\n nextDrag.offset(cloneDrag.offset());\n } else {\n hiddenDrag.addClass('active');\n nextDrag.offset(hiddenDrag.offset());\n }\n } else {\n hiddenDrag.addClass('active');\n nextDrag.offset(hiddenDrag.offset());\n }\n }\n } else {\n drop.data('isfocus', true);\n }\n\n e.preventDefault();\n this.sendDragToDrop(nextDrag, drop);\n };\n\n /**\n * Choose the next drag in a group.\n *\n * @param {int} group which group.\n * @param {jQuery} drag current choice (empty jQuery if there isn't one).\n * @return {jQuery} the next drag in that group, or null if there wasn't one.\n */\n DragDropToTextQuestion.prototype.getNextDrag = function(group, drag) {\n var choice,\n numChoices = this.noOfChoicesInGroup(group);\n\n if (drag.length === 0) {\n choice = 1; // Was empty, so we want to select the first choice.\n } else {\n choice = this.getChoice(drag) + 1;\n }\n\n var next = this.getUnplacedChoice(group, choice);\n while (next.length === 0 && choice < numChoices) {\n choice++;\n next = this.getUnplacedChoice(group, choice);\n }\n\n return next;\n };\n\n /**\n * Choose the previous drag in a group.\n *\n * @param {int} group which group.\n * @param {jQuery} drag current choice (empty jQuery if there isn't one).\n * @return {jQuery} the next drag in that group, or null if there wasn't one.\n */\n DragDropToTextQuestion.prototype.getPreviousDrag = function(group, drag) {\n var choice;\n\n if (drag.length === 0) {\n choice = this.noOfChoicesInGroup(group);\n } else {\n choice = this.getChoice(drag) - 1;\n }\n\n var previous = this.getUnplacedChoice(group, choice);\n while (previous.length === 0 && choice > 1) {\n choice--;\n previous = this.getUnplacedChoice(group, choice);\n }\n\n // Does this choice exist?\n return previous;\n };\n\n /**\n * Animate an object to the given destination.\n *\n * @param {jQuery} drag the element to be animated.\n * @param {jQuery} target element marking the place to move it to.\n */\n DragDropToTextQuestion.prototype.animateTo = function(drag, target) {\n var currentPos = drag.offset(),\n targetPos = target.offset(),\n thisQ = this;\n\n M.util.js_pending('qtype_ddwtos-animate-' + thisQ.containerId);\n // Animate works in terms of CSS position, whereas locating an object\n // on the page works best with jQuery offset() function. So, to get\n // the right target position, we work out the required change in\n // offset() and then add that to the current CSS position.\n drag.animate(\n {\n left: parseInt(drag.css('left')) + targetPos.left - currentPos.left,\n top: parseInt(drag.css('top')) + targetPos.top - currentPos.top\n },\n {\n duration: 'fast',\n done: function() {\n $('body').trigger('qtype_ddwtos-dragmoved', [drag, target, thisQ]);\n M.util.js_complete('qtype_ddwtos-animate-' + thisQ.containerId);\n }\n }\n );\n };\n\n /**\n * Detect if a point is inside a given DOM node.\n *\n * @param {Number} pageX the x position.\n * @param {Number} pageY the y position.\n * @param {jQuery} drop the node to check (typically a drop).\n * @return {boolean} whether the point is inside the node.\n */\n DragDropToTextQuestion.prototype.isPointInDrop = function(pageX, pageY, drop) {\n var position = drop.offset();\n return pageX >= position.left && pageX < position.left + drop.width()\n && pageY >= position.top && pageY < position.top + drop.height();\n };\n\n /**\n * Set the value of the hidden input for a place, to record what is currently there.\n *\n * @param {int} place which place to set the input value for.\n * @param {int} choice the value to set.\n */\n DragDropToTextQuestion.prototype.setInputValue = function(place, choice) {\n this.getRoot().find('input.placeinput.place' + place).val(choice);\n };\n\n /**\n * Get the outer div for this question.\n *\n * @returns {jQuery} containing that div.\n */\n DragDropToTextQuestion.prototype.getRoot = function() {\n return $(document.getElementById(this.containerId));\n };\n\n /**\n * Get drag home for a given choice.\n *\n * @param {int} group the group.\n * @param {int} choice the choice number.\n * @returns {jQuery} containing that div.\n */\n DragDropToTextQuestion.prototype.getDragHome = function(group, choice) {\n if (!this.getRoot().find('.draghome.dragplaceholder.group' + group + '.choice' + choice).is(':visible')) {\n return this.getRoot().find('.draggrouphomes' + group +\n ' span.draghome.infinite' +\n '.choice' + choice +\n '.group' + group);\n }\n return this.getRoot().find('.draghome.dragplaceholder.group' + group + '.choice' + choice);\n };\n\n /**\n * Get an unplaced choice for a particular group.\n *\n * @param {int} group the group.\n * @param {int} choice the choice number.\n * @returns {jQuery} jQuery wrapping the unplaced choice. If there isn't one, the jQuery will be empty.\n */\n DragDropToTextQuestion.prototype.getUnplacedChoice = function(group, choice) {\n return this.getRoot().find('.draghome.group' + group + '.choice' + choice + '.unplaced').slice(0, 1);\n };\n\n /**\n * Get the drag that is currently in a given place.\n *\n * @param {int} place the place number.\n * @return {jQuery} the current drag (or an empty jQuery if none).\n */\n DragDropToTextQuestion.prototype.getCurrentDragInPlace = function(place) {\n return this.getRoot().find('span.draghome.inplace' + place);\n };\n\n /**\n * Return the number of blanks in a given group.\n *\n * @param {int} group the group number.\n * @returns {int} the number of drops.\n */\n DragDropToTextQuestion.prototype.noOfDropsInGroup = function(group) {\n return this.getRoot().find('.drop.group' + group).length;\n };\n\n /**\n * Return the number of choices in a given group.\n *\n * @param {int} group the group number.\n * @returns {int} the number of choices.\n */\n DragDropToTextQuestion.prototype.noOfChoicesInGroup = function(group) {\n return this.getRoot().find('.draghome.group' + group).length;\n };\n\n /**\n * Return the number at the end of the CSS class name with the given prefix.\n *\n * @param {jQuery} node\n * @param {String} prefix name prefix\n * @returns {Number|null} the suffix if found, else null.\n */\n DragDropToTextQuestion.prototype.getClassnameNumericSuffix = function(node, prefix) {\n var classes = node.attr('class');\n if (classes !== undefined && classes !== '') {\n var classesArr = classes.split(' ');\n for (var index = 0; index < classesArr.length; index++) {\n var patt1 = new RegExp('^' + prefix + '([0-9])+$');\n if (patt1.test(classesArr[index])) {\n var patt2 = new RegExp('([0-9])+$');\n var match = patt2.exec(classesArr[index]);\n return Number(match[0]);\n }\n }\n }\n return null;\n };\n\n /**\n * Get the choice number of a drag.\n *\n * @param {jQuery} drag the drag.\n * @returns {Number} the choice number.\n */\n DragDropToTextQuestion.prototype.getChoice = function(drag) {\n return this.getClassnameNumericSuffix(drag, 'choice');\n };\n\n /**\n * Given a DOM node that is significant to this question\n * (drag, drop, ...) get the group it belongs to.\n *\n * @param {jQuery} node a DOM node.\n * @returns {Number} the group it belongs to.\n */\n DragDropToTextQuestion.prototype.getGroup = function(node) {\n return this.getClassnameNumericSuffix(node, 'group');\n };\n\n /**\n * Get the place number of a drop, or its corresponding hidden input.\n *\n * @param {jQuery} node the DOM node.\n * @returns {Number} the place number.\n */\n DragDropToTextQuestion.prototype.getPlace = function(node) {\n return this.getClassnameNumericSuffix(node, 'place');\n };\n\n /**\n * Get drag clone for a given drag.\n *\n * @param {jQuery} drag the drag.\n * @returns {jQuery} the drag's clone.\n */\n DragDropToTextQuestion.prototype.getDragClone = function(drag) {\n return this.getRoot().find('.draggrouphomes' +\n this.getGroup(drag) +\n ' span.draghome' +\n '.choice' + this.getChoice(drag) +\n '.group' + this.getGroup(drag) +\n '.dragplaceholder');\n };\n\n /**\n * Get infinite drag clones for given drag.\n *\n * @param {jQuery} drag the drag.\n * @param {Boolean} inHome in the home area or not.\n * @returns {jQuery} the drag's clones.\n */\n DragDropToTextQuestion.prototype.getInfiniteDragClones = function(drag, inHome) {\n if (inHome) {\n return this.getRoot().find('.draggrouphomes' +\n this.getGroup(drag) +\n ' span.draghome' +\n '.choice' + this.getChoice(drag) +\n '.group' + this.getGroup(drag) +\n '.infinite').not('.dragplaceholder');\n }\n return this.getRoot().find('span.draghome' +\n '.choice' + this.getChoice(drag) +\n '.group' + this.getGroup(drag) +\n '.infinite').not('.dragplaceholder');\n };\n\n /**\n * Get drop for a given drag and place.\n *\n * @param {jQuery} drag the drag.\n * @param {Integer} currentPlace the current place of drag.\n * @returns {jQuery} the drop's clone.\n */\n DragDropToTextQuestion.prototype.getDrop = function(drag, currentPlace) {\n return this.getRoot().find('.drop.group' + this.getGroup(drag) + '.place' + currentPlace);\n };\n\n /**\n * Singleton that tracks all the DragDropToTextQuestions on this page, and deals\n * with event dispatching.\n *\n * @type {Object}\n */\n var questionManager = {\n /**\n * {boolean} used to ensure the event handlers are only initialised once per page.\n */\n eventHandlersInitialised: false,\n\n /**\n * {Object} ensures that the drag event handlers are only initialised once per question,\n * indexed by containerId (id on the .que div).\n */\n dragEventHandlersInitialised: {},\n\n /**\n * {boolean} is keyboard navigation or not.\n */\n isKeyboardNavigation: false,\n\n /**\n * {DragDropToTextQuestion[]} all the questions on this page, indexed by containerId (id on the .que div).\n */\n questions: {},\n\n /**\n * Initialise questions.\n *\n * @param {String} containerId id of the outer div for this question.\n * @param {boolean} readOnly whether the question is being displayed read-only.\n */\n init: function(containerId, readOnly) {\n questionManager.questions[containerId] = new DragDropToTextQuestion(containerId, readOnly);\n if (!questionManager.eventHandlersInitialised) {\n questionManager.setupEventHandlers();\n questionManager.eventHandlersInitialised = true;\n }\n if (!questionManager.dragEventHandlersInitialised.hasOwnProperty(containerId)) {\n questionManager.dragEventHandlersInitialised[containerId] = true;\n // We do not use the body event here to prevent the other event on Mobile device, such as scroll event.\n var questionContainer = document.getElementById(containerId);\n if (questionContainer.classList.contains('ddwtos') &&\n !questionContainer.classList.contains('qtype_ddwtos-readonly')) {\n // TODO: Convert all the jQuery selectors and events to native Javascript.\n questionManager.addEventHandlersToDrag($(questionContainer).find('span.draghome'));\n }\n }\n },\n\n /**\n * Set up the event handlers that make this question type work. (Done once per page.)\n */\n setupEventHandlers: function() {\n $('body')\n .on('keydown',\n '.que.ddwtos:not(.qtype_ddwtos-readonly) span.drop',\n questionManager.handleKeyPress)\n .on('keydown',\n '.que.ddwtos:not(.qtype_ddwtos-readonly) span.draghome.placed:not(.beingdragged)',\n questionManager.handleKeyPress)\n .on('qtype_ddwtos-dragmoved', questionManager.handleDragMoved);\n },\n\n /**\n * Binding the drag/touch event again for newly created element.\n *\n * @param {jQuery} element Element to bind the event\n */\n addEventHandlersToDrag: function(element) {\n // Unbind all the mousedown and touchstart events to prevent double binding.\n element.unbind('mousedown touchstart');\n element.on('mousedown touchstart', questionManager.handleDragStart);\n },\n\n /**\n * Handle mouse down / touch start on drags.\n * @param {Event} e the DOM event.\n */\n handleDragStart: function(e) {\n e.preventDefault();\n var question = questionManager.getQuestionForEvent(e);\n if (question) {\n question.handleDragStart(e);\n }\n },\n\n /**\n * Handle key down / press on drops.\n * @param {KeyboardEvent} e\n */\n handleKeyPress: function(e) {\n if (questionManager.isKeyboardNavigation) {\n return;\n }\n questionManager.isKeyboardNavigation = true;\n var question = questionManager.getQuestionForEvent(e);\n if (question) {\n question.handleKeyPress(e);\n }\n },\n\n /**\n * Given an event, work out which question it affects.\n *\n * @param {Event} e the event.\n * @returns {DragDropToTextQuestion|undefined} The question, or undefined.\n */\n getQuestionForEvent: function(e) {\n var containerId = $(e.currentTarget).closest('.que.ddwtos').attr('id');\n return questionManager.questions[containerId];\n },\n\n /**\n * Handle when drag moved.\n *\n * @param {Event} e the event.\n * @param {jQuery} drag the drag\n * @param {jQuery} target the target\n * @param {DragDropToTextQuestion} thisQ the question.\n */\n handleDragMoved: function(e, drag, target, thisQ) {\n drag.removeClass('beingdragged');\n drag.css('top', '').css('left', '');\n target.after(drag);\n target.removeClass('active');\n if (typeof drag.data('unplaced') !== 'undefined' && drag.data('unplaced') === true) {\n drag.removeClass('placed').addClass('unplaced');\n drag.removeAttr('tabindex');\n drag.removeData('unplaced');\n if (drag.hasClass('infinite') && thisQ.getInfiniteDragClones(drag, true).length > 1) {\n thisQ.getInfiniteDragClones(drag, true).first().remove();\n }\n }\n if (typeof drag.data('isfocus') !== 'undefined' && drag.data('isfocus') === true) {\n drag.focus();\n drag.removeData('isfocus');\n }\n if (typeof target.data('isfocus') !== 'undefined' && target.data('isfocus') === true) {\n target.removeData('isfocus');\n }\n if (questionManager.isKeyboardNavigation) {\n questionManager.isKeyboardNavigation = false;\n }\n if (thisQ.isQuestionInteracted()) {\n // The user has interacted with the draggable items. We need to mark the form as dirty.\n questionManager.handleFormDirty();\n // Save the new answered value.\n thisQ.questionAnswer = thisQ.getQuestionAnsweredValues();\n }\n },\n\n /**\n * Handle when the form is dirty.\n */\n handleFormDirty: function() {\n const responseForm = document.getElementById('responseform');\n FormChangeChecker.markFormAsDirty(responseForm);\n }\n };\n\n /**\n * @alias module:qtype_ddwtos/ddwtos\n */\n return {\n /**\n * Initialise one drag-drop into text question.\n *\n * @param {String} containerId id of the outer div for this question.\n * @param {boolean} readOnly whether the question is being displayed read-only.\n */\n init: questionManager.init\n };\n});\n"],"names":["define","$","dragDrop","keys","FormChangeChecker","DragDropToTextQuestion","containerId","readOnly","questionAnswer","getRoot","addClass","resizeAllDragsAndDrops","cloneDrags","positionDrags","prototype","thisQ","this","find","each","i","node","resizeAllDragsAndDropsInGroup","getClassnameNumericSuffix","group","dragHomes","maxWidth","maxHeight","drag","Math","max","ceil","offsetWidth","offsetHeight","setElementSize","drop","element","width","height","css","index","draghome","placeHolder","clone","removeClass","getChoice","getGroup","before","root","not","dragNode","currentPlace","removeAttr","inputNode","input","choice","val","place","getPlace","dropPosition","offset","data","top","left","unplacedDrag","getUnplacedChoice","hiddenDrag","getDragClone","length","hasClass","noOfDrags","noOfDropsInGroup","getInfiniteDragClones","cloneDrag","after","questionManager","addEventHandlersToDrag","sendDragToDrop","getQuestionAnsweredValues","result","id","value","isQuestionInteracted","oldAnswer","newAnswer","isInteracted","JSON","stringify","Object","forEach","key","handleDragStart","e","target","closest","prepare","start","setInputValue","hiddenDrop","getDrop","x","y","dragMove","dragEnd","pageX","pageY","dropNode","isPointInDrop","placed","dropZone","sendDragHome","oldDrag","getCurrentDragInPlace","hasDropSameDrag","focus","attr","animateTo","getDragHome","handleKeyPress","placedDrag","currentDrag","nextDrag","keyCode","space","arrowRight","arrowDown","getNextDrag","arrowLeft","arrowUp","getPreviousDrag","escape","isKeyboardNavigation","preventDefault","numChoices","noOfChoicesInGroup","next","previous","currentPos","targetPos","M","util","js_pending","animate","parseInt","duration","done","trigger","js_complete","position","document","getElementById","is","slice","prefix","classes","undefined","classesArr","split","RegExp","test","match","exec","Number","inHome","eventHandlersInitialised","dragEventHandlersInitialised","questions","init","setupEventHandlers","hasOwnProperty","questionContainer","classList","contains","on","handleDragMoved","unbind","question","getQuestionForEvent","currentTarget","removeData","first","remove","handleFormDirty","responseForm","markFormAsDirty"],"mappings":"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"} \ No newline at end of file diff --git a/question/type/ddwtos/amd/src/ddwtos.js b/question/type/ddwtos/amd/src/ddwtos.js index 506bc8990a3..d02bc003c25 100644 --- a/question/type/ddwtos/amd/src/ddwtos.js +++ b/question/type/ddwtos/amd/src/ddwtos.js @@ -373,6 +373,11 @@ define([ var oldDrag = this.getCurrentDragInPlace(this.getPlace(drop)); if (oldDrag.length !== 0) { var currentPlace = this.getClassnameNumericSuffix(oldDrag, 'inplace'); + // When infinite group and there is already a drag in a drop, reject the exact clone in the same drop. + if (this.hasDropSameDrag(currentPlace, drop, oldDrag, drag)) { + this.sendDragHome(drag); + return; + } var hiddenDrop = this.getDrop(oldDrag, currentPlace); hiddenDrop.addClass('active'); oldDrag.addClass('beingdragged'); @@ -399,6 +404,25 @@ define([ } }; + /** + * When infinite group and there is already a drag in a drop, reject the exact clone in the same drop. + * + * @param {int} currentPlace the position of the current drop. + * @param {jQuery} drop the drop containing a drag. + * @param {jQuery} oldDrag the drag already placed in drop. + * @param {jQuery} drag the new drag which is exactly the same (clone) as oldDrag . + * @returns {boolean} + */ + DragDropToTextQuestion.prototype.hasDropSameDrag = function(currentPlace, drop, oldDrag, drag) { + if (drag.hasClass('infinite')) { + return drop.hasClass('place' + currentPlace) && + this.getGroup(drag) === this.getGroup(drop) && + this.getChoice(drag) === this.getChoice(oldDrag) && + this.getGroup(drag) === this.getGroup(oldDrag); + } + return false; + }; + /** * Animate a drag back to its home. *