"),root.find(".dropzone.place"+i).width(maxWidth-2).height(maxHeight-2))}},DragDropOntoImageQuestion.prototype.cloneDrags=function(){var thisQ=this;thisQ.getRoot().find(".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)}))},DragDropOntoImageQuestion.prototype.cloneDragsForOneChoice=function(dragHome){if(dragHome.hasClass("infinite"))for(var noOfDrags=this.noOfDropsInGroup(this.getGroup(dragHome)),i=0;i0&&"0"===choice)){var place=thisQ.getPlace(input),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},DragDropOntoImageQuestion.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)},DragDropOntoImageQuestion.prototype.handleDragStart=function(e){var thisQ=this,drag=$(e.target).closest(".draghome"),newIndex=this.calculateZIndex()+2;if(dragDrop.prepare(e).start&&!drag.hasClass("beingdragged")){drag.addClass("beingdragged").css("transform","").css("z-index",newIndex);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(thisQ.getGroup(drag));if(this.getInfiniteDragClones(drag,!1).length1;)choice--,previous=this.getUnplacedChoice(group,choice);return previous},DragDropOntoImageQuestion.prototype.animateTo=function(drag,target){var currentPos=drag.offset(),targetPos=target.offset(),thisQ=this;M.util.js_pending("qtype_ddimageortext-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_ddimageortext-dragmoved",[drag,target,thisQ]),M.util.js_complete("qtype_ddimageortext-animate-"+thisQ.containerId)}})},DragDropOntoImageQuestion.prototype.isPointInDrop=function(pageX,pageY,drop){var position=drop.offset();return drop.hasClass("draghome")?pageX>=position.left&&pageX=position.top&&pageY=position.left&&pageX=position.top&&pageYzIndex&&(zIndex=itemZIndex)})),zIndex},DragDropOntoImageQuestion.prototype.isDragSameAsDrop=function(drag,drop){return this.getChoice(drag)===this.getChoice(drop)&&this.getGroup(drag)===this.getGroup(drop)};var questionManager={eventHandlersInitialised:!1,dragEventHandlersInitialised:{},isPrinting:!1,isKeyboardNavigation:!1,questions:{},init:function(containerId,readOnly,places){if(questionManager.questions[containerId]=new DragDropOntoImageQuestion(containerId,readOnly,places),questionManager.eventHandlersInitialised||(questionManager.setupEventHandlers(),questionManager.eventHandlersInitialised=!0),!questionManager.dragEventHandlersInitialised.hasOwnProperty(containerId)){questionManager.dragEventHandlersInitialised[containerId]=!0;var questionContainer=document.getElementById(containerId);questionContainer.classList.contains("ddimageortext")&&!questionContainer.classList.contains("qtype_ddimageortext-readonly")&&questionManager.addEventHandlersToDrag($(questionContainer).find(".draghome"))}},setupEventHandlers:function(){$("body").on("keydown",".que.ddimageortext:not(.qtype_ddimageortext-readonly) .dropzones .dropzone",questionManager.handleKeyPress).on("keydown",".que.ddimageortext:not(.qtype_ddimageortext-readonly) .draghome.placed:not(.beingdragged)",questionManager.handleKeyPress).on("qtype_ddimageortext-dragmoved",questionManager.handleDragMoved),$(window).on("resize",(function(){questionManager.handleWindowResize(!1)})),window.addEventListener("beforeprint",(function(){questionManager.isPrinting=!0,questionManager.handleWindowResize(questionManager.isPrinting)})),window.addEventListener("afterprint",(function(){questionManager.isPrinting=!1,questionManager.handleWindowResize(questionManager.isPrinting)})),setTimeout((function(){questionManager.fixLayoutIfThingsMoved()}),100)},addEventHandlersToDrag:function(element){element.unbind("mousedown touchstart"),element.on("mousedown touchstart",questionManager.handleDragStart)},handleDragStart:function(e){e.preventDefault();var question=questionManager.getQuestionForEvent(e);question&&question.handleDragStart(e)},handleKeyPress:function(e){if(!questionManager.isKeyboardNavigation){questionManager.isKeyboardNavigation=!0;var question=questionManager.getQuestionForEvent(e);question&&question.handleKeyPress(e)}},handleWindowResize:function(isPrinting){for(var containerId in questionManager.questions)questionManager.questions.hasOwnProperty(containerId)&&(questionManager.questions[containerId].isPrinting=isPrinting,questionManager.questions[containerId].handleResize())},fixLayoutIfThingsMoved:function(){this.handleWindowResize(questionManager.isPrinting),setTimeout((function(){questionManager.fixLayoutIfThingsMoved(questionManager.isPrinting)}),100)},handleDragMoved:function(e,drag,target,thisQ){drag.removeClass("beingdragged").css("z-index",""),drag.css("top",target.position().top).css("left",target.position().left),target.after(drag),target.removeClass("active"),void 0!==drag.data("unplaced")&&!0===drag.data("unplaced")?(drag.removeClass("placed").addClass("unplaced"),drag.removeAttr("tabindex"),drag.removeData("unplaced"),drag.css("top","").css("left","").css("transform",""),drag.hasClass("infinite")&&thisQ.getInfiniteDragClones(drag,!0).length>1&&thisQ.getInfiniteDragClones(drag,!0).first().remove()):(drag.data("originX",target.data("originX")).data("originY",target.data("originY")),thisQ.handleElementScale(drag,"left top")),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())},getQuestionForEvent:function(e){var containerId=$(e.currentTarget).closest(".que.ddimageortext").attr("id");return questionManager.questions[containerId]},handleFormDirty:function(){const responseForm=document.getElementById("responseform");FormChangeChecker.markFormAsDirty(responseForm)}};return{init:questionManager.init}}));
//# sourceMappingURL=question.min.js.map
\ No newline at end of file
diff --git a/question/type/ddimageortext/amd/build/question.min.js.map b/question/type/ddimageortext/amd/build/question.min.js.map
index f745d581b73..a0bf39e5fbb 100644
--- a/question/type/ddimageortext/amd/build/question.min.js.map
+++ b/question/type/ddimageortext/amd/build/question.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"question.min.js","sources":["../src/question.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 allow dragging options to slots (using mouse down or touch) or tab through slots using keyboard.\n *\n * @module qtype_ddimageortext/question\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/dragdrop', 'core/key_codes'], function($, dragDrop, keys) {\n\n \"use strict\";\n\n /**\n * Initialise one drag-drop onto image 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 * @param {Array} places Information about the drop places.\n * @constructor\n */\n function DragDropOntoImageQuestion(containerId, readOnly, places) {\n this.containerId = containerId;\n M.util.js_pending('qtype_ddimageortext-init-' + this.containerId);\n this.places = places;\n this.allImagesLoaded = false;\n this.imageLoadingTimeoutId = null;\n this.isPrinting = false;\n if (readOnly) {\n this.getRoot().addClass('qtype_ddimageortext-readonly');\n }\n\n var thisQ = this;\n this.getNotYetLoadedImages().one('load', function() {\n thisQ.waitForAllImagesToBeLoaded();\n });\n this.waitForAllImagesToBeLoaded();\n }\n\n /**\n * Waits until all images are loaded before calling setupQuestion().\n *\n * This function is called from the onLoad of each image, and also polls with\n * a time-out, because image on-loads are allegedly unreliable.\n */\n DragDropOntoImageQuestion.prototype.waitForAllImagesToBeLoaded = function() {\n var thisQ = this;\n\n // This method may get called multiple times (via image on-loads or timeouts.\n // If we are already done, don't do it again.\n if (this.allImagesLoaded) {\n return;\n }\n\n // Clear any current timeout, if set.\n if (this.imageLoadingTimeoutId !== null) {\n clearTimeout(this.imageLoadingTimeoutId);\n }\n\n // If we have not yet loaded all images, set a timeout to\n // call ourselves again, since apparently images on-load\n // events are flakey.\n if (this.getNotYetLoadedImages().length > 0) {\n this.imageLoadingTimeoutId = setTimeout(function() {\n thisQ.waitForAllImagesToBeLoaded();\n }, 100);\n return;\n }\n\n // We now have all images. Carry on, but only after giving the layout a chance to settle down.\n this.allImagesLoaded = true;\n thisQ.setupQuestion();\n };\n\n /**\n * Get any of the images in the drag-drop area that are not yet fully loaded.\n *\n * @returns {jQuery} those images.\n */\n DragDropOntoImageQuestion.prototype.getNotYetLoadedImages = function() {\n var thisQ = this;\n return this.getRoot().find('.ddarea img').not(function(i, imgNode) {\n return thisQ.imageIsLoaded(imgNode);\n });\n };\n\n /**\n * Check if an image has loaded without errors.\n *\n * @param {HTMLImageElement} imgElement an image.\n * @returns {boolean} true if this image has loaded without errors.\n */\n DragDropOntoImageQuestion.prototype.imageIsLoaded = function(imgElement) {\n return imgElement.complete && imgElement.naturalHeight !== 0;\n };\n\n /**\n * Set up the question, once all images have been loaded.\n */\n DragDropOntoImageQuestion.prototype.setupQuestion = function() {\n this.resizeAllDragsAndDrops();\n this.cloneDrags();\n this.positionDragsAndDrops();\n M.util.js_complete('qtype_ddimageortext-init-' + this.containerId);\n };\n\n /**\n * In each group, resize all the items to be the same size.\n */\n DragDropOntoImageQuestion.prototype.resizeAllDragsAndDrops = function() {\n var thisQ = this;\n this.getRoot().find('.draghomes > div').each(function(i, node) {\n thisQ.resizeAllDragsAndDropsInGroup(\n thisQ.getClassnameNumericSuffix($(node), 'dragitemgroup'));\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 DragDropOntoImageQuestion.prototype.resizeAllDragsAndDropsInGroup = function(group) {\n var root = this.getRoot(),\n dragHomes = root.find('.dragitemgroup' + group + ' .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(drag.offsetHeight));\n });\n\n // The size we will want to set is a bit bigger than this.\n maxWidth += 10;\n maxHeight += 10;\n\n // Set each drag home to that size.\n dragHomes.each(function(i, drag) {\n var left = Math.round((maxWidth - drag.offsetWidth) / 2),\n top = Math.floor((maxHeight - drag.offsetHeight) / 2);\n // Set top and left padding so the item is centred.\n $(drag).css({\n 'padding-left': left + 'px',\n 'padding-right': (maxWidth - drag.offsetWidth - left) + 'px',\n 'padding-top': top + 'px',\n 'padding-bottom': (maxHeight - drag.offsetHeight - top) + 'px'\n });\n });\n\n // Create the drops and make them the right size.\n for (var i in this.places) {\n if (!this.places.hasOwnProperty((i))) {\n continue;\n }\n var place = this.places[i],\n label = place.text;\n if (parseInt(place.group) !== group) {\n continue;\n }\n if (label === '') {\n label = M.util.get_string('blank', 'qtype_ddimageortext');\n }\n root.find('.dropzones').append('
' +\n '' + label + '
');\n root.find('.dropzone.place' + i).width(maxWidth - 2).height(maxHeight - 2);\n }\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 DragDropOntoImageQuestion.prototype.cloneDrags = function() {\n var thisQ = this;\n thisQ.getRoot().find('.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 * Clone drag item for one choice.\n *\n * @param {jQuery} dragHome the drag home to clone.\n */\n DragDropOntoImageQuestion.prototype.cloneDragsForOneChoice = function(dragHome) {\n if (dragHome.hasClass('infinite')) {\n var noOfDrags = this.noOfDropsInGroup(this.getGroup(dragHome));\n for (var i = 0; i < noOfDrags; i++) {\n this.cloneDrag(dragHome);\n }\n } else {\n this.cloneDrag(dragHome);\n }\n };\n\n /**\n * Clone drag item.\n *\n * @param {jQuery} dragHome\n */\n DragDropOntoImageQuestion.prototype.cloneDrag = function(dragHome) {\n var drag = dragHome.clone();\n drag.removeClass('draghome')\n .addClass('drag unplaced moodle-has-zindex')\n .offset(dragHome.offset());\n this.getRoot().find('.dragitems').append(drag);\n };\n\n /**\n * Update the position of drags.\n */\n DragDropOntoImageQuestion.prototype.positionDragsAndDrops = function() {\n var thisQ = this,\n root = this.getRoot(),\n bgRatio = this.bgRatio();\n\n // Move the drops into position.\n root.find('.ddarea .dropzone').each(function(i, dropNode) {\n var drop = $(dropNode),\n place = thisQ.places[thisQ.getPlace(drop)];\n // The xy values come from PHP as strings, so we need parseInt to stop JS doing string concatenation.\n drop.css('left', parseInt(place.xy[0]) * bgRatio)\n .css('top', parseInt(place.xy[1]) * bgRatio);\n drop.data('originX', parseInt(place.xy[0]))\n .data('originY', parseInt(place.xy[1]));\n thisQ.handleElementScale(drop, 'left top');\n });\n\n // First move all items back home.\n root.find('.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 ones that should be placed.\n root.find('input.placeinput').each(function(i, inputNode) {\n var input = $(inputNode),\n choice = input.val();\n if (choice.length === 0 || (choice.length > 0 && choice === '0')) {\n // No item in this place.\n return;\n }\n\n var place = thisQ.getPlace(input);\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 cloneDrag.removeClass('beingdragged');\n cloneDrag.removeAttr('tabindex');\n hiddenDrag.after(cloneDrag);\n // Sometimes, for the question that has a lot of input groups and unlimited draggable items,\n // this 'clone' process takes longer than usual, so the questionManager.init() method\n // will not add the eventHandler for this cloned drag.\n // We need to make sure to add the eventHandler for the cloned drag too.\n questionManager.addEventHandlersToDrag(cloneDrag);\n } else {\n hiddenDrag.addClass('active');\n }\n } else {\n hiddenDrag.addClass('active');\n }\n }\n\n // Send the drag to drop.\n var drop = root.find('.dropzone.place' + place);\n thisQ.sendDragToDrop(unplacedDrag, drop);\n });\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 DragDropOntoImageQuestion.prototype.handleDragStart = function(e) {\n var thisQ = this,\n drag = $(e.target).closest('.draghome'),\n currentIndex = this.calculateZIndex(),\n newIndex = currentIndex + 2;\n\n var info = dragDrop.prepare(e);\n if (!info.start || drag.hasClass('beingdragged')) {\n return;\n }\n\n drag.addClass('beingdragged').css('transform', '').css('z-index', newIndex);\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(thisQ.getGroup(drag));\n var cloneDrags = this.getInfiniteDragClones(drag, false);\n if (cloneDrags.length < noOfDrags) {\n var cloneDrag = drag.clone();\n cloneDrag.removeClass('beingdragged');\n cloneDrag.removeAttr('tabindex');\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 DragDropOntoImageQuestion.prototype.dragMove = function(pageX, pageY, drag) {\n var thisQ = this,\n highlighted = false;\n this.getRoot().find('.dropzone.group' + this.getGroup(drag)).each(function(i, dropNode) {\n var drop = $(dropNode);\n if (thisQ.isPointInDrop(pageX, pageY, drop) && !highlighted) {\n highlighted = true;\n drop.addClass('valid-drag-over-drop');\n } else {\n drop.removeClass('valid-drag-over-drop');\n }\n });\n this.getRoot().find('.draghome.placed.group' + this.getGroup(drag)).not('.beingdragged').each(function(i, dropNode) {\n var drop = $(dropNode);\n if (thisQ.isPointInDrop(pageX, pageY, drop) && !highlighted && !thisQ.isDragSameAsDrop(drag, drop)) {\n highlighted = true;\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 DragDropOntoImageQuestion.prototype.dragEnd = function(pageX, pageY, drag) {\n var thisQ = this,\n root = this.getRoot(),\n placed = false;\n\n // Looking for drag that was dropped on a dropzone.\n root.find('.dropzone.group' + this.getGroup(drag)).each(function(i, dropNode) {\n var drop = $(dropNode);\n if (!thisQ.isPointInDrop(pageX, pageY, drop)) {\n // Not this drop.\n return true;\n }\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\n if (!placed) {\n // Looking for drag that was dropped on a placed drag.\n root.find('.draghome.placed.group' + this.getGroup(drag)).not('.beingdragged').each(function(i, placedNode) {\n var placedDrag = $(placedNode);\n if (!thisQ.isPointInDrop(pageX, pageY, placedDrag) || thisQ.isDragSameAsDrop(drag, placedDrag)) {\n // Not this placed drag.\n return true;\n }\n\n // Now put this drag into the drop.\n placedDrag.removeClass('valid-drag-over-drop');\n var currentPlace = thisQ.getClassnameNumericSuffix(placedDrag, 'inplace');\n var drop = thisQ.getDrop(drag, currentPlace);\n thisQ.sendDragToDrop(drag, drop);\n placed = true;\n return false; // Stop the each() here.\n });\n }\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 DragDropOntoImageQuestion.prototype.sendDragToDrop = function(drag, drop) {\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 oldDrag.addClass('beingdragged');\n oldDrag.offset(oldDrag.offset());\n var currentPlace = this.getClassnameNumericSuffix(oldDrag, 'inplace');\n var hiddenDrop = this.getDrop(oldDrag, currentPlace);\n hiddenDrop.addClass('active');\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 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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.prototype.handleKeyPress = function(e) {\n var drop = $(e.target).closest('.dropzone');\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 questionManager.isKeyboardNavigation = false;\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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.prototype.animateTo = function(drag, target) {\n var currentPos = drag.offset(),\n targetPos = target.offset(),\n thisQ = this;\n\n M.util.js_pending('qtype_ddimageortext-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_ddimageortext-dragmoved', [drag, target, thisQ]);\n M.util.js_complete('qtype_ddimageortext-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 DragDropOntoImageQuestion.prototype.isPointInDrop = function(pageX, pageY, drop) {\n var position = drop.offset();\n if (drop.hasClass('draghome')) {\n return pageX >= position.left && pageX < position.left + drop.outerWidth()\n && pageY >= position.top && pageY < position.top + drop.outerHeight();\n }\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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.prototype.getRoot = function() {\n return $(document.getElementById(this.containerId));\n };\n\n /**\n * Get the img that is the background image.\n * @returns {jQuery} containing that img.\n */\n DragDropOntoImageQuestion.prototype.bgImage = function() {\n return this.getRoot().find('img.dropbackground');\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 DragDropOntoImageQuestion.prototype.getDragHome = function(group, choice) {\n if (!this.getRoot().find('.draghome.dragplaceholder.group' + group + '.choice' + choice).is(':visible')) {\n return this.getRoot().find('.dragitemgroup' + group +\n ' .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 DragDropOntoImageQuestion.prototype.getUnplacedChoice = function(group, choice) {\n return this.getRoot().find('.ddarea .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 DragDropOntoImageQuestion.prototype.getCurrentDragInPlace = function(place) {\n return this.getRoot().find('.ddarea .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 DragDropOntoImageQuestion.prototype.noOfDropsInGroup = function(group) {\n return this.getRoot().find('.dropzone.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 DragDropOntoImageQuestion.prototype.noOfChoicesInGroup = function(group) {\n return this.getRoot().find('.dragitemgroup' + group + ' .draghome').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 DragDropOntoImageQuestion.prototype.getClassnameNumericSuffix = function(node, prefix) {\n var classes = node.attr('class');\n if (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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.prototype.getDragClone = function(drag) {\n return this.getRoot().find('.dragitemgroup' +\n this.getGroup(drag) +\n ' .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 DragDropOntoImageQuestion.prototype.getInfiniteDragClones = function(drag, inHome) {\n if (inHome) {\n return this.getRoot().find('.dragitemgroup' +\n this.getGroup(drag) +\n ' .draghome' +\n '.choice' + this.getChoice(drag) +\n '.group' + this.getGroup(drag) +\n '.infinite').not('.dragplaceholder');\n }\n return this.getRoot().find('.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 DragDropOntoImageQuestion.prototype.getDrop = function(drag, currentPlace) {\n return this.getRoot().find('.dropzone.group' + this.getGroup(drag) + '.place' + currentPlace);\n };\n\n /**\n * Handle when the window is resized.\n */\n DragDropOntoImageQuestion.prototype.handleResize = function() {\n var thisQ = this,\n bgRatio = this.bgRatio();\n if (this.isPrinting) {\n bgRatio = 1;\n }\n\n this.getRoot().find('.ddarea .dropzone').each(function(i, dropNode) {\n $(dropNode)\n .css('left', parseInt($(dropNode).data('originX')) * parseFloat(bgRatio))\n .css('top', parseInt($(dropNode).data('originY')) * parseFloat(bgRatio));\n thisQ.handleElementScale(dropNode, 'left top');\n });\n\n this.getRoot().find('div.droparea .draghome').not('.beingdragged').each(function(key, drag) {\n $(drag)\n .css('left', parseFloat($(drag).data('originX')) * parseFloat(bgRatio))\n .css('top', parseFloat($(drag).data('originY')) * parseFloat(bgRatio));\n thisQ.handleElementScale(drag, 'left top');\n });\n };\n\n /**\n * Return the background ratio.\n *\n * @returns {number} Background ratio.\n */\n DragDropOntoImageQuestion.prototype.bgRatio = function() {\n var bgImg = this.bgImage();\n var bgImgNaturalWidth = bgImg.get(0).naturalWidth;\n var bgImgClientWidth = bgImg.width();\n\n return bgImgClientWidth / bgImgNaturalWidth;\n };\n\n /**\n * Scale the drag if needed.\n *\n * @param {jQuery} element the item to place.\n * @param {String} type scaling type\n */\n DragDropOntoImageQuestion.prototype.handleElementScale = function(element, type) {\n var bgRatio = parseFloat(this.bgRatio());\n if (this.isPrinting) {\n bgRatio = 1;\n }\n $(element).css({\n '-webkit-transform': 'scale(' + bgRatio + ')',\n '-moz-transform': 'scale(' + bgRatio + ')',\n '-ms-transform': 'scale(' + bgRatio + ')',\n '-o-transform': 'scale(' + bgRatio + ')',\n 'transform': 'scale(' + bgRatio + ')',\n 'transform-origin': type\n });\n };\n\n /**\n * Calculate z-index value.\n *\n * @returns {number} z-index value\n */\n DragDropOntoImageQuestion.prototype.calculateZIndex = function() {\n var zIndex = 0;\n this.getRoot().find('.ddarea .dropzone, div.droparea .draghome').each(function(i, dropNode) {\n dropNode = $(dropNode);\n // Note that webkit browsers won't return the z-index value from the CSS stylesheet\n // if the element doesn't have a position specified. Instead it'll return \"auto\".\n var itemZIndex = dropNode.css('z-index') ? parseInt(dropNode.css('z-index')) : 0;\n\n if (itemZIndex > zIndex) {\n zIndex = itemZIndex;\n }\n });\n\n return zIndex;\n };\n\n /**\n * Check that the drag is drop to it's clone.\n *\n * @param {jQuery} drag The drag.\n * @param {jQuery} drop The drop.\n * @returns {boolean}\n */\n DragDropOntoImageQuestion.prototype.isDragSameAsDrop = function(drag, drop) {\n return this.getChoice(drag) === this.getChoice(drop) && this.getGroup(drag) === this.getGroup(drop);\n };\n\n /**\n * Singleton object that handles all the DragDropOntoImageQuestions\n * on the page, and deals with event dispatching.\n * @type {Object}\n */\n var questionManager = {\n\n /**\n * {boolean} ensures that 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 printing or not.\n */\n isPrinting: false,\n\n /**\n * {boolean} is keyboard navigation or not.\n */\n isKeyboardNavigation: false,\n\n /**\n * {Object} all the questions on this page, indexed by containerId (id on the .que div).\n */\n questions: {}, // An object containing all the information about each question on the page.\n\n /**\n * Initialise one question.\n *\n * @method\n * @param {String} containerId the id of the div.que that contains this question.\n * @param {boolean} readOnly whether the question is read-only.\n * @param {Array} places data.\n */\n init: function(containerId, readOnly, places) {\n questionManager.questions[containerId] =\n new DragDropOntoImageQuestion(containerId, readOnly, places);\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('ddimageortext') &&\n !questionContainer.classList.contains('qtype_ddimageortext-readonly')) {\n // TODO: Convert all the jQuery selectors and events to native Javascript.\n questionManager.addEventHandlersToDrag($(questionContainer).find('.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.ddimageortext:not(.qtype_ddimageortext-readonly) .dropzones .dropzone',\n questionManager.handleKeyPress)\n .on('keydown',\n '.que.ddimageortext:not(.qtype_ddimageortext-readonly) .draghome.placed:not(.beingdragged)',\n questionManager.handleKeyPress)\n .on('qtype_ddimageortext-dragmoved', questionManager.handleDragMoved);\n $(window).on('resize', function() {\n questionManager.handleWindowResize(false);\n });\n window.addEventListener('beforeprint', function() {\n questionManager.isPrinting = true;\n questionManager.handleWindowResize(questionManager.isPrinting);\n });\n window.addEventListener('afterprint', function() {\n questionManager.isPrinting = false;\n questionManager.handleWindowResize(questionManager.isPrinting);\n });\n setTimeout(function() {\n questionManager.fixLayoutIfThingsMoved();\n }, 100);\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 events 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 events on drags.\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 * Handle when the window is resized.\n * @param {boolean} isPrinting\n */\n handleWindowResize: function(isPrinting) {\n for (var containerId in questionManager.questions) {\n if (questionManager.questions.hasOwnProperty(containerId)) {\n questionManager.questions[containerId].isPrinting = isPrinting;\n questionManager.questions[containerId].handleResize();\n }\n }\n },\n\n /**\n * Sometimes, despite our best efforts, things change in a way that cannot\n * be specifically caught (e.g. dock expanding or collapsing in Boost).\n * Therefore, we need to periodically check everything is in the right position.\n */\n fixLayoutIfThingsMoved: function() {\n this.handleWindowResize(questionManager.isPrinting);\n // We use setTimeout after finishing work, rather than setInterval,\n // in case positioning things is slow. We want 100 ms gap\n // between executions, not what setInterval does.\n setTimeout(function() {\n questionManager.fixLayoutIfThingsMoved(questionManager.isPrinting);\n }, 100);\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 {DragDropOntoImageQuestion} thisQ the question.\n */\n handleDragMoved: function(e, drag, target, thisQ) {\n drag.removeClass('beingdragged').css('z-index', '');\n drag.css('top', target.position().top).css('left', target.position().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 drag.css('top', '')\n .css('left', '')\n .css('transform', '');\n if (drag.hasClass('infinite') && thisQ.getInfiniteDragClones(drag, true).length > 1) {\n thisQ.getInfiniteDragClones(drag, true).first().remove();\n }\n } else {\n drag.data('originX', target.data('originX')).data('originY', target.data('originY'));\n thisQ.handleElementScale(drag, 'left top');\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 },\n\n /**\n * Given an event, work out which question it effects.\n * @param {Event} e the event.\n * @returns {DragDropOntoImageQuestion|undefined} The question, or undefined.\n */\n getQuestionForEvent: function(e) {\n var containerId = $(e.currentTarget).closest('.que.ddimageortext').attr('id');\n return questionManager.questions[containerId];\n }\n };\n\n /**\n * @alias module:qtype_ddimageortext/question\n */\n return {\n init: questionManager.init\n };\n});\n"],"names":["define","$","dragDrop","keys","DragDropOntoImageQuestion","containerId","readOnly","places","M","util","js_pending","this","allImagesLoaded","imageLoadingTimeoutId","isPrinting","getRoot","addClass","thisQ","getNotYetLoadedImages","one","waitForAllImagesToBeLoaded","prototype","clearTimeout","length","setTimeout","setupQuestion","find","not","i","imgNode","imageIsLoaded","imgElement","complete","naturalHeight","resizeAllDragsAndDrops","cloneDrags","positionDragsAndDrops","js_complete","each","node","resizeAllDragsAndDropsInGroup","getClassnameNumericSuffix","group","root","dragHomes","maxWidth","maxHeight","drag","Math","max","ceil","offsetWidth","offsetHeight","left","round","top","floor","css","hasOwnProperty","place","label","text","parseInt","get_string","append","width","height","index","dragHome","placeHolder","clone","removeClass","getChoice","getGroup","before","cloneDragsForOneChoice","hasClass","noOfDrags","noOfDropsInGroup","cloneDrag","offset","bgRatio","dropNode","drop","getPlace","xy","data","handleElementScale","dragNode","currentPlace","removeAttr","inputNode","input","choice","val","unplacedDrag","getUnplacedChoice","hiddenDrag","getDragClone","getInfiniteDragClones","after","questionManager","addEventHandlersToDrag","sendDragToDrop","handleDragStart","e","target","closest","newIndex","calculateZIndex","prepare","start","setInputValue","hiddenDrop","getDrop","x","y","dragMove","dragEnd","pageX","pageY","highlighted","isPointInDrop","isDragSameAsDrop","placed","placedNode","placedDrag","sendDragHome","oldDrag","getCurrentDragInPlace","focus","attr","animateTo","getDragHome","handleKeyPress","currentDrag","nextDrag","keyCode","space","arrowRight","arrowDown","getNextDrag","arrowLeft","arrowUp","getPreviousDrag","escape","isKeyboardNavigation","preventDefault","numChoices","noOfChoicesInGroup","next","previous","currentPos","targetPos","animate","duration","done","trigger","position","outerWidth","outerHeight","document","getElementById","bgImage","is","slice","prefix","classes","classesArr","split","RegExp","test","match","exec","Number","inHome","handleResize","parseFloat","key","bgImg","bgImgNaturalWidth","get","naturalWidth","element","type","zIndex","itemZIndex","eventHandlersInitialised","dragEventHandlersInitialised","questions","init","setupEventHandlers","questionContainer","classList","contains","on","handleDragMoved","window","handleWindowResize","addEventListener","fixLayoutIfThingsMoved","unbind","question","getQuestionForEvent","removeData","first","remove","currentTarget"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"question.min.js","sources":["../src/question.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 allow dragging options to slots (using mouse down or touch) or tab through slots using keyboard.\n *\n * @module qtype_ddimageortext/question\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\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 * Initialise one drag-drop onto image 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 * @param {Array} places Information about the drop places.\n * @constructor\n */\n function DragDropOntoImageQuestion(containerId, readOnly, places) {\n this.containerId = containerId;\n this.questionAnswer = {};\n M.util.js_pending('qtype_ddimageortext-init-' + this.containerId);\n this.places = places;\n this.allImagesLoaded = false;\n this.imageLoadingTimeoutId = null;\n this.isPrinting = false;\n if (readOnly) {\n this.getRoot().addClass('qtype_ddimageortext-readonly');\n }\n\n var thisQ = this;\n this.getNotYetLoadedImages().one('load', function() {\n thisQ.waitForAllImagesToBeLoaded();\n });\n this.waitForAllImagesToBeLoaded();\n }\n\n /**\n * Waits until all images are loaded before calling setupQuestion().\n *\n * This function is called from the onLoad of each image, and also polls with\n * a time-out, because image on-loads are allegedly unreliable.\n */\n DragDropOntoImageQuestion.prototype.waitForAllImagesToBeLoaded = function() {\n var thisQ = this;\n\n // This method may get called multiple times (via image on-loads or timeouts.\n // If we are already done, don't do it again.\n if (this.allImagesLoaded) {\n return;\n }\n\n // Clear any current timeout, if set.\n if (this.imageLoadingTimeoutId !== null) {\n clearTimeout(this.imageLoadingTimeoutId);\n }\n\n // If we have not yet loaded all images, set a timeout to\n // call ourselves again, since apparently images on-load\n // events are flakey.\n if (this.getNotYetLoadedImages().length > 0) {\n this.imageLoadingTimeoutId = setTimeout(function() {\n thisQ.waitForAllImagesToBeLoaded();\n }, 100);\n return;\n }\n\n // We now have all images. Carry on, but only after giving the layout a chance to settle down.\n this.allImagesLoaded = true;\n thisQ.setupQuestion();\n };\n\n /**\n * Get any of the images in the drag-drop area that are not yet fully loaded.\n *\n * @returns {jQuery} those images.\n */\n DragDropOntoImageQuestion.prototype.getNotYetLoadedImages = function() {\n var thisQ = this;\n return this.getRoot().find('.ddarea img').not(function(i, imgNode) {\n return thisQ.imageIsLoaded(imgNode);\n });\n };\n\n /**\n * Check if an image has loaded without errors.\n *\n * @param {HTMLImageElement} imgElement an image.\n * @returns {boolean} true if this image has loaded without errors.\n */\n DragDropOntoImageQuestion.prototype.imageIsLoaded = function(imgElement) {\n return imgElement.complete && imgElement.naturalHeight !== 0;\n };\n\n /**\n * Set up the question, once all images have been loaded.\n */\n DragDropOntoImageQuestion.prototype.setupQuestion = function() {\n this.resizeAllDragsAndDrops();\n this.cloneDrags();\n this.positionDragsAndDrops();\n M.util.js_complete('qtype_ddimageortext-init-' + this.containerId);\n };\n\n /**\n * In each group, resize all the items to be the same size.\n */\n DragDropOntoImageQuestion.prototype.resizeAllDragsAndDrops = function() {\n var thisQ = this;\n this.getRoot().find('.draghomes > div').each(function(i, node) {\n thisQ.resizeAllDragsAndDropsInGroup(\n thisQ.getClassnameNumericSuffix($(node), 'dragitemgroup'));\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 DragDropOntoImageQuestion.prototype.resizeAllDragsAndDropsInGroup = function(group) {\n var root = this.getRoot(),\n dragHomes = root.find('.dragitemgroup' + group + ' .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(drag.offsetHeight));\n });\n\n // The size we will want to set is a bit bigger than this.\n maxWidth += 10;\n maxHeight += 10;\n\n // Set each drag home to that size.\n dragHomes.each(function(i, drag) {\n var left = Math.round((maxWidth - drag.offsetWidth) / 2),\n top = Math.floor((maxHeight - drag.offsetHeight) / 2);\n // Set top and left padding so the item is centred.\n $(drag).css({\n 'padding-left': left + 'px',\n 'padding-right': (maxWidth - drag.offsetWidth - left) + 'px',\n 'padding-top': top + 'px',\n 'padding-bottom': (maxHeight - drag.offsetHeight - top) + 'px'\n });\n });\n\n // Create the drops and make them the right size.\n for (var i in this.places) {\n if (!this.places.hasOwnProperty((i))) {\n continue;\n }\n var place = this.places[i],\n label = place.text;\n if (parseInt(place.group) !== group) {\n continue;\n }\n if (label === '') {\n label = M.util.get_string('blank', 'qtype_ddimageortext');\n }\n root.find('.dropzones').append('
' +\n '' + label + '
');\n root.find('.dropzone.place' + i).width(maxWidth - 2).height(maxHeight - 2);\n }\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 DragDropOntoImageQuestion.prototype.cloneDrags = function() {\n var thisQ = this;\n thisQ.getRoot().find('.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 * Clone drag item for one choice.\n *\n * @param {jQuery} dragHome the drag home to clone.\n */\n DragDropOntoImageQuestion.prototype.cloneDragsForOneChoice = function(dragHome) {\n if (dragHome.hasClass('infinite')) {\n var noOfDrags = this.noOfDropsInGroup(this.getGroup(dragHome));\n for (var i = 0; i < noOfDrags; i++) {\n this.cloneDrag(dragHome);\n }\n } else {\n this.cloneDrag(dragHome);\n }\n };\n\n /**\n * Clone drag item.\n *\n * @param {jQuery} dragHome\n */\n DragDropOntoImageQuestion.prototype.cloneDrag = function(dragHome) {\n var drag = dragHome.clone();\n drag.removeClass('draghome')\n .addClass('drag unplaced moodle-has-zindex')\n .offset(dragHome.offset());\n this.getRoot().find('.dragitems').append(drag);\n };\n\n /**\n * Update the position of drags.\n */\n DragDropOntoImageQuestion.prototype.positionDragsAndDrops = function() {\n var thisQ = this,\n root = this.getRoot(),\n bgRatio = this.bgRatio();\n\n // Move the drops into position.\n root.find('.ddarea .dropzone').each(function(i, dropNode) {\n var drop = $(dropNode),\n place = thisQ.places[thisQ.getPlace(drop)];\n // The xy values come from PHP as strings, so we need parseInt to stop JS doing string concatenation.\n drop.css('left', parseInt(place.xy[0]) * bgRatio)\n .css('top', parseInt(place.xy[1]) * bgRatio);\n drop.data('originX', parseInt(place.xy[0]))\n .data('originY', parseInt(place.xy[1]));\n thisQ.handleElementScale(drop, 'left top');\n });\n\n // First move all items back home.\n root.find('.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 ones that should be placed.\n root.find('input.placeinput').each(function(i, inputNode) {\n var input = $(inputNode),\n choice = input.val();\n if (choice.length === 0 || (choice.length > 0 && choice === '0')) {\n // No item in this place.\n return;\n }\n\n var place = thisQ.getPlace(input);\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 cloneDrag.removeClass('beingdragged');\n cloneDrag.removeAttr('tabindex');\n hiddenDrag.after(cloneDrag);\n // Sometimes, for the question that has a lot of input groups and unlimited draggable items,\n // this 'clone' process takes longer than usual, so the questionManager.init() method\n // will not add the eventHandler for this cloned drag.\n // We need to make sure to add the eventHandler for the cloned drag too.\n questionManager.addEventHandlersToDrag(cloneDrag);\n } else {\n hiddenDrag.addClass('active');\n }\n } else {\n hiddenDrag.addClass('active');\n }\n }\n\n // Send the drag to drop.\n var drop = root.find('.dropzone.place' + place);\n thisQ.sendDragToDrop(unplacedDrag, 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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.prototype.handleDragStart = function(e) {\n var thisQ = this,\n drag = $(e.target).closest('.draghome'),\n currentIndex = this.calculateZIndex(),\n newIndex = currentIndex + 2;\n\n var info = dragDrop.prepare(e);\n if (!info.start || drag.hasClass('beingdragged')) {\n return;\n }\n\n drag.addClass('beingdragged').css('transform', '').css('z-index', newIndex);\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(thisQ.getGroup(drag));\n var cloneDrags = this.getInfiniteDragClones(drag, false);\n if (cloneDrags.length < noOfDrags) {\n var cloneDrag = drag.clone();\n cloneDrag.removeClass('beingdragged');\n cloneDrag.removeAttr('tabindex');\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 DragDropOntoImageQuestion.prototype.dragMove = function(pageX, pageY, drag) {\n var thisQ = this,\n highlighted = false;\n this.getRoot().find('.dropzone.group' + this.getGroup(drag)).each(function(i, dropNode) {\n var drop = $(dropNode);\n if (thisQ.isPointInDrop(pageX, pageY, drop) && !highlighted) {\n highlighted = true;\n drop.addClass('valid-drag-over-drop');\n } else {\n drop.removeClass('valid-drag-over-drop');\n }\n });\n this.getRoot().find('.draghome.placed.group' + this.getGroup(drag)).not('.beingdragged').each(function(i, dropNode) {\n var drop = $(dropNode);\n if (thisQ.isPointInDrop(pageX, pageY, drop) && !highlighted && !thisQ.isDragSameAsDrop(drag, drop)) {\n highlighted = true;\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 DragDropOntoImageQuestion.prototype.dragEnd = function(pageX, pageY, drag) {\n var thisQ = this,\n root = this.getRoot(),\n placed = false;\n\n // Looking for drag that was dropped on a dropzone.\n root.find('.dropzone.group' + this.getGroup(drag)).each(function(i, dropNode) {\n var drop = $(dropNode);\n if (!thisQ.isPointInDrop(pageX, pageY, drop)) {\n // Not this drop.\n return true;\n }\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\n if (!placed) {\n // Looking for drag that was dropped on a placed drag.\n root.find('.draghome.placed.group' + this.getGroup(drag)).not('.beingdragged').each(function(i, placedNode) {\n var placedDrag = $(placedNode);\n if (!thisQ.isPointInDrop(pageX, pageY, placedDrag) || thisQ.isDragSameAsDrop(drag, placedDrag)) {\n // Not this placed drag.\n return true;\n }\n\n // Now put this drag into the drop.\n placedDrag.removeClass('valid-drag-over-drop');\n var currentPlace = thisQ.getClassnameNumericSuffix(placedDrag, 'inplace');\n var drop = thisQ.getDrop(drag, currentPlace);\n thisQ.sendDragToDrop(drag, drop);\n placed = true;\n return false; // Stop the each() here.\n });\n }\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 DragDropOntoImageQuestion.prototype.sendDragToDrop = function(drag, drop) {\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 oldDrag.addClass('beingdragged');\n oldDrag.offset(oldDrag.offset());\n var currentPlace = this.getClassnameNumericSuffix(oldDrag, 'inplace');\n var hiddenDrop = this.getDrop(oldDrag, currentPlace);\n hiddenDrop.addClass('active');\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 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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.prototype.handleKeyPress = function(e) {\n var drop = $(e.target).closest('.dropzone');\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 questionManager.isKeyboardNavigation = false;\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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.prototype.animateTo = function(drag, target) {\n var currentPos = drag.offset(),\n targetPos = target.offset(),\n thisQ = this;\n\n M.util.js_pending('qtype_ddimageortext-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_ddimageortext-dragmoved', [drag, target, thisQ]);\n M.util.js_complete('qtype_ddimageortext-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 DragDropOntoImageQuestion.prototype.isPointInDrop = function(pageX, pageY, drop) {\n var position = drop.offset();\n if (drop.hasClass('draghome')) {\n return pageX >= position.left && pageX < position.left + drop.outerWidth()\n && pageY >= position.top && pageY < position.top + drop.outerHeight();\n }\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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.prototype.getRoot = function() {\n return $(document.getElementById(this.containerId));\n };\n\n /**\n * Get the img that is the background image.\n * @returns {jQuery} containing that img.\n */\n DragDropOntoImageQuestion.prototype.bgImage = function() {\n return this.getRoot().find('img.dropbackground');\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 DragDropOntoImageQuestion.prototype.getDragHome = function(group, choice) {\n if (!this.getRoot().find('.draghome.dragplaceholder.group' + group + '.choice' + choice).is(':visible')) {\n return this.getRoot().find('.dragitemgroup' + group +\n ' .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 DragDropOntoImageQuestion.prototype.getUnplacedChoice = function(group, choice) {\n return this.getRoot().find('.ddarea .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 DragDropOntoImageQuestion.prototype.getCurrentDragInPlace = function(place) {\n return this.getRoot().find('.ddarea .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 DragDropOntoImageQuestion.prototype.noOfDropsInGroup = function(group) {\n return this.getRoot().find('.dropzone.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 DragDropOntoImageQuestion.prototype.noOfChoicesInGroup = function(group) {\n return this.getRoot().find('.dragitemgroup' + group + ' .draghome').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 DragDropOntoImageQuestion.prototype.getClassnameNumericSuffix = function(node, prefix) {\n var classes = node.attr('class');\n if (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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.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 DragDropOntoImageQuestion.prototype.getDragClone = function(drag) {\n return this.getRoot().find('.dragitemgroup' +\n this.getGroup(drag) +\n ' .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 DragDropOntoImageQuestion.prototype.getInfiniteDragClones = function(drag, inHome) {\n if (inHome) {\n return this.getRoot().find('.dragitemgroup' +\n this.getGroup(drag) +\n ' .draghome' +\n '.choice' + this.getChoice(drag) +\n '.group' + this.getGroup(drag) +\n '.infinite').not('.dragplaceholder');\n }\n return this.getRoot().find('.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 DragDropOntoImageQuestion.prototype.getDrop = function(drag, currentPlace) {\n return this.getRoot().find('.dropzone.group' + this.getGroup(drag) + '.place' + currentPlace);\n };\n\n /**\n * Handle when the window is resized.\n */\n DragDropOntoImageQuestion.prototype.handleResize = function() {\n var thisQ = this,\n bgRatio = this.bgRatio();\n if (this.isPrinting) {\n bgRatio = 1;\n }\n\n this.getRoot().find('.ddarea .dropzone').each(function(i, dropNode) {\n $(dropNode)\n .css('left', parseInt($(dropNode).data('originX')) * parseFloat(bgRatio))\n .css('top', parseInt($(dropNode).data('originY')) * parseFloat(bgRatio));\n thisQ.handleElementScale(dropNode, 'left top');\n });\n\n this.getRoot().find('div.droparea .draghome').not('.beingdragged').each(function(key, drag) {\n $(drag)\n .css('left', parseFloat($(drag).data('originX')) * parseFloat(bgRatio))\n .css('top', parseFloat($(drag).data('originY')) * parseFloat(bgRatio));\n thisQ.handleElementScale(drag, 'left top');\n });\n };\n\n /**\n * Return the background ratio.\n *\n * @returns {number} Background ratio.\n */\n DragDropOntoImageQuestion.prototype.bgRatio = function() {\n var bgImg = this.bgImage();\n var bgImgNaturalWidth = bgImg.get(0).naturalWidth;\n var bgImgClientWidth = bgImg.width();\n\n return bgImgClientWidth / bgImgNaturalWidth;\n };\n\n /**\n * Scale the drag if needed.\n *\n * @param {jQuery} element the item to place.\n * @param {String} type scaling type\n */\n DragDropOntoImageQuestion.prototype.handleElementScale = function(element, type) {\n var bgRatio = parseFloat(this.bgRatio());\n if (this.isPrinting) {\n bgRatio = 1;\n }\n $(element).css({\n '-webkit-transform': 'scale(' + bgRatio + ')',\n '-moz-transform': 'scale(' + bgRatio + ')',\n '-ms-transform': 'scale(' + bgRatio + ')',\n '-o-transform': 'scale(' + bgRatio + ')',\n 'transform': 'scale(' + bgRatio + ')',\n 'transform-origin': type\n });\n };\n\n /**\n * Calculate z-index value.\n *\n * @returns {number} z-index value\n */\n DragDropOntoImageQuestion.prototype.calculateZIndex = function() {\n var zIndex = 0;\n this.getRoot().find('.ddarea .dropzone, div.droparea .draghome').each(function(i, dropNode) {\n dropNode = $(dropNode);\n // Note that webkit browsers won't return the z-index value from the CSS stylesheet\n // if the element doesn't have a position specified. Instead it'll return \"auto\".\n var itemZIndex = dropNode.css('z-index') ? parseInt(dropNode.css('z-index')) : 0;\n\n if (itemZIndex > zIndex) {\n zIndex = itemZIndex;\n }\n });\n\n return zIndex;\n };\n\n /**\n * Check that the drag is drop to it's clone.\n *\n * @param {jQuery} drag The drag.\n * @param {jQuery} drop The drop.\n * @returns {boolean}\n */\n DragDropOntoImageQuestion.prototype.isDragSameAsDrop = function(drag, drop) {\n return this.getChoice(drag) === this.getChoice(drop) && this.getGroup(drag) === this.getGroup(drop);\n };\n\n /**\n * Singleton object that handles all the DragDropOntoImageQuestions\n * on the page, and deals with event dispatching.\n * @type {Object}\n */\n var questionManager = {\n\n /**\n * {boolean} ensures that 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 printing or not.\n */\n isPrinting: false,\n\n /**\n * {boolean} is keyboard navigation or not.\n */\n isKeyboardNavigation: false,\n\n /**\n * {Object} all the questions on this page, indexed by containerId (id on the .que div).\n */\n questions: {}, // An object containing all the information about each question on the page.\n\n /**\n * Initialise one question.\n *\n * @method\n * @param {String} containerId the id of the div.que that contains this question.\n * @param {boolean} readOnly whether the question is read-only.\n * @param {Array} places data.\n */\n init: function(containerId, readOnly, places) {\n questionManager.questions[containerId] =\n new DragDropOntoImageQuestion(containerId, readOnly, places);\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('ddimageortext') &&\n !questionContainer.classList.contains('qtype_ddimageortext-readonly')) {\n // TODO: Convert all the jQuery selectors and events to native Javascript.\n questionManager.addEventHandlersToDrag($(questionContainer).find('.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.ddimageortext:not(.qtype_ddimageortext-readonly) .dropzones .dropzone',\n questionManager.handleKeyPress)\n .on('keydown',\n '.que.ddimageortext:not(.qtype_ddimageortext-readonly) .draghome.placed:not(.beingdragged)',\n questionManager.handleKeyPress)\n .on('qtype_ddimageortext-dragmoved', questionManager.handleDragMoved);\n $(window).on('resize', function() {\n questionManager.handleWindowResize(false);\n });\n window.addEventListener('beforeprint', function() {\n questionManager.isPrinting = true;\n questionManager.handleWindowResize(questionManager.isPrinting);\n });\n window.addEventListener('afterprint', function() {\n questionManager.isPrinting = false;\n questionManager.handleWindowResize(questionManager.isPrinting);\n });\n setTimeout(function() {\n questionManager.fixLayoutIfThingsMoved();\n }, 100);\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 events 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 events on drags.\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 * Handle when the window is resized.\n * @param {boolean} isPrinting\n */\n handleWindowResize: function(isPrinting) {\n for (var containerId in questionManager.questions) {\n if (questionManager.questions.hasOwnProperty(containerId)) {\n questionManager.questions[containerId].isPrinting = isPrinting;\n questionManager.questions[containerId].handleResize();\n }\n }\n },\n\n /**\n * Sometimes, despite our best efforts, things change in a way that cannot\n * be specifically caught (e.g. dock expanding or collapsing in Boost).\n * Therefore, we need to periodically check everything is in the right position.\n */\n fixLayoutIfThingsMoved: function() {\n this.handleWindowResize(questionManager.isPrinting);\n // We use setTimeout after finishing work, rather than setInterval,\n // in case positioning things is slow. We want 100 ms gap\n // between executions, not what setInterval does.\n setTimeout(function() {\n questionManager.fixLayoutIfThingsMoved(questionManager.isPrinting);\n }, 100);\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 {DragDropOntoImageQuestion} thisQ the question.\n */\n handleDragMoved: function(e, drag, target, thisQ) {\n drag.removeClass('beingdragged').css('z-index', '');\n drag.css('top', target.position().top).css('left', target.position().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 drag.css('top', '')\n .css('left', '')\n .css('transform', '');\n if (drag.hasClass('infinite') && thisQ.getInfiniteDragClones(drag, true).length > 1) {\n thisQ.getInfiniteDragClones(drag, true).first().remove();\n }\n } else {\n drag.data('originX', target.data('originX')).data('originY', target.data('originY'));\n thisQ.handleElementScale(drag, 'left top');\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 * Given an event, work out which question it effects.\n * @param {Event} e the event.\n * @returns {DragDropOntoImageQuestion|undefined} The question, or undefined.\n */\n getQuestionForEvent: function(e) {\n var containerId = $(e.currentTarget).closest('.que.ddimageortext').attr('id');\n return questionManager.questions[containerId];\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_ddimageortext/question\n */\n return {\n init: questionManager.init\n };\n});\n"],"names":["define","$","dragDrop","keys","FormChangeChecker","DragDropOntoImageQuestion","containerId","readOnly","places","questionAnswer","M","util","js_pending","this","allImagesLoaded","imageLoadingTimeoutId","isPrinting","getRoot","addClass","thisQ","getNotYetLoadedImages","one","waitForAllImagesToBeLoaded","prototype","clearTimeout","length","setTimeout","setupQuestion","find","not","i","imgNode","imageIsLoaded","imgElement","complete","naturalHeight","resizeAllDragsAndDrops","cloneDrags","positionDragsAndDrops","js_complete","each","node","resizeAllDragsAndDropsInGroup","getClassnameNumericSuffix","group","root","dragHomes","maxWidth","maxHeight","drag","Math","max","ceil","offsetWidth","offsetHeight","left","round","top","floor","css","hasOwnProperty","place","label","text","parseInt","get_string","append","width","height","index","dragHome","placeHolder","clone","removeClass","getChoice","getGroup","before","cloneDragsForOneChoice","hasClass","noOfDrags","noOfDropsInGroup","cloneDrag","offset","bgRatio","dropNode","drop","getPlace","xy","data","handleElementScale","dragNode","currentPlace","removeAttr","inputNode","input","choice","val","unplacedDrag","getUnplacedChoice","hiddenDrag","getDragClone","getInfiniteDragClones","after","questionManager","addEventHandlersToDrag","sendDragToDrop","getQuestionAnsweredValues","result","id","value","isQuestionInteracted","oldAnswer","newAnswer","isInteracted","JSON","stringify","Object","forEach","key","handleDragStart","e","target","closest","newIndex","calculateZIndex","prepare","start","setInputValue","hiddenDrop","getDrop","x","y","dragMove","dragEnd","pageX","pageY","highlighted","isPointInDrop","isDragSameAsDrop","placed","placedNode","placedDrag","sendDragHome","oldDrag","getCurrentDragInPlace","focus","attr","animateTo","getDragHome","handleKeyPress","currentDrag","nextDrag","keyCode","space","arrowRight","arrowDown","getNextDrag","arrowLeft","arrowUp","getPreviousDrag","escape","isKeyboardNavigation","preventDefault","numChoices","noOfChoicesInGroup","next","previous","currentPos","targetPos","animate","duration","done","trigger","position","outerWidth","outerHeight","document","getElementById","bgImage","is","slice","prefix","classes","classesArr","split","RegExp","test","match","exec","Number","inHome","handleResize","parseFloat","bgImg","bgImgNaturalWidth","get","naturalWidth","element","type","zIndex","itemZIndex","eventHandlersInitialised","dragEventHandlersInitialised","questions","init","setupEventHandlers","questionContainer","classList","contains","on","handleDragMoved","window","handleWindowResize","addEventListener","fixLayoutIfThingsMoved","unbind","question","getQuestionForEvent","removeData","first","remove","handleFormDirty","currentTarget","responseForm","markFormAsDirty"],"mappings":";;;;;;;AAsBAA,sCAAO,CACH,SACA,gBACA,iBACA,4BACD,SACCC,EACAC,SACAC,KACAC,4BAaSC,0BAA0BC,YAAaC,SAAUC,aACjDF,YAAcA,iBACdG,eAAiB,GACtBC,EAAEC,KAAKC,WAAW,4BAA8BC,KAAKP,kBAChDE,OAASA,YACTM,iBAAkB,OAClBC,sBAAwB,UACxBC,YAAa,EACdT,eACKU,UAAUC,SAAS,oCAGxBC,MAAQN,UACPO,wBAAwBC,IAAI,QAAQ,WACrCF,MAAMG,qCAELA,6BASTjB,0BAA0BkB,UAAUD,2BAA6B,eACzDH,MAAQN,KAIRA,KAAKC,kBAK0B,OAA/BD,KAAKE,uBACLS,aAAaX,KAAKE,uBAMlBF,KAAKO,wBAAwBK,OAAS,OACjCV,sBAAwBW,YAAW,WACpCP,MAAMG,+BACP,WAKFR,iBAAkB,EACvBK,MAAMQ,mBAQVtB,0BAA0BkB,UAAUH,sBAAwB,eACpDD,MAAQN,YACLA,KAAKI,UAAUW,KAAK,eAAeC,KAAI,SAASC,EAAGC,gBAC/CZ,MAAMa,cAAcD,aAUnC1B,0BAA0BkB,UAAUS,cAAgB,SAASC,mBAClDA,WAAWC,UAAyC,IAA7BD,WAAWE,eAM7C9B,0BAA0BkB,UAAUI,cAAgB,gBAC3CS,8BACAC,kBACAC,wBACL5B,EAAEC,KAAK4B,YAAY,4BAA8B1B,KAAKP,cAM1DD,0BAA0BkB,UAAUa,uBAAyB,eACrDjB,MAAQN,UACPI,UAAUW,KAAK,oBAAoBY,MAAK,SAASV,EAAGW,MACrDtB,MAAMuB,8BACEvB,MAAMwB,0BAA0B1C,EAAEwC,MAAO,sBASzDpC,0BAA0BkB,UAAUmB,8BAAgC,SAASE,WACrEC,KAAOhC,KAAKI,UACZ6B,UAAYD,KAAKjB,KAAK,iBAAmBgB,MAAQ,cACjDG,SAAW,EACXC,UAAY,MA0BX,IAAIlB,KAvBTgB,UAAUN,MAAK,SAASV,EAAGmB,MACvBF,SAAWG,KAAKC,IAAIJ,SAAUG,KAAKE,KAAKH,KAAKI,cAC7CL,UAAYE,KAAKC,IAAIH,UAAWE,KAAKE,KAAKH,KAAKK,kBAInDP,UAAY,GACZC,WAAa,GAGbF,UAAUN,MAAK,SAASV,EAAGmB,UACnBM,KAAOL,KAAKM,OAAOT,SAAWE,KAAKI,aAAe,GAClDI,IAAMP,KAAKQ,OAAOV,UAAYC,KAAKK,cAAgB,GAEvDrD,EAAEgD,MAAMU,IAAI,gBACQJ,KAAO,qBACLR,SAAWE,KAAKI,YAAcE,KAAQ,mBACzCE,IAAM,sBACFT,UAAYC,KAAKK,aAAeG,IAAO,UAKpD5C,KAAKL,UACVK,KAAKL,OAAOoD,eAAgB9B,QAG7B+B,MAAQhD,KAAKL,OAAOsB,GACpBgC,MAAQD,MAAME,KACdC,SAASH,MAAMjB,SAAWA,QAGhB,KAAVkB,QACAA,MAAQpD,EAAEC,KAAKsD,WAAW,QAAS,wBAEvCpB,KAAKjB,KAAK,cAAcsC,OAAO,oCAAsCL,MAAMjB,MAC3D,SAAWd,EADI,2CAEOgC,MAAQ,uBAC9CjB,KAAKjB,KAAK,kBAAoBE,GAAGqC,MAAMpB,SAAW,GAAGqB,OAAOpB,UAAY,MAShF3C,0BAA0BkB,UAAUc,WAAa,eACzClB,MAAQN,KACZM,MAAMF,UAAUW,KAAK,aAAaY,MAAK,SAAS6B,MAAOC,cAC/CrB,KAAOhD,EAAEqE,UACTC,YAActB,KAAKuB,QACvBD,YAAYE,cACZF,YAAYrD,SAAS,kBACjBC,MAAMuD,UAAUzB,MAAQ,SACxB9B,MAAMwD,SAAS1B,MAAQ,oBAC3BA,KAAK2B,OAAOL,iBASpBlE,0BAA0BkB,UAAUsD,uBAAyB,SAASP,aAC9DA,SAASQ,SAAS,oBACdC,UAAYlE,KAAKmE,iBAAiBnE,KAAK8D,SAASL,WAC3CxC,EAAI,EAAGA,EAAIiD,UAAWjD,SACtBmD,UAAUX,oBAGdW,UAAUX,WASvBjE,0BAA0BkB,UAAU0D,UAAY,SAASX,cACjDrB,KAAOqB,SAASE,QACpBvB,KAAKwB,YAAY,YACZvD,SAAS,mCACTgE,OAAOZ,SAASY,eAChBjE,UAAUW,KAAK,cAAcsC,OAAOjB,OAM7C5C,0BAA0BkB,UAAUe,sBAAwB,eACpDnB,MAAQN,KACRgC,KAAOhC,KAAKI,UACZkE,QAAUtE,KAAKsE,UAGnBtC,KAAKjB,KAAK,qBAAqBY,MAAK,SAASV,EAAGsD,cACxCC,KAAOpF,EAAEmF,UACTvB,MAAQ1C,MAAMX,OAAOW,MAAMmE,SAASD,OAExCA,KAAK1B,IAAI,OAAQK,SAASH,MAAM0B,GAAG,IAAMJ,SACpCxB,IAAI,MAAOK,SAASH,MAAM0B,GAAG,IAAMJ,SACxCE,KAAKG,KAAK,UAAWxB,SAASH,MAAM0B,GAAG,KAClCC,KAAK,UAAWxB,SAASH,MAAM0B,GAAG,KACvCpE,MAAMsE,mBAAmBJ,KAAM,eAInCxC,KAAKjB,KAAK,aAAaC,IAAI,oBAAoBW,MAAK,SAASV,EAAG4D,cACxDzC,KAAOhD,EAAEyF,UACTC,aAAexE,MAAMwB,0BAA0BM,KAAM,WACzDA,KAAK/B,SAAS,YACTuD,YAAY,UACjBxB,KAAK2C,WAAW,YACK,OAAjBD,cACA1C,KAAKwB,YAAY,UAAYkB,iBAKrC9C,KAAKjB,KAAK,oBAAoBY,MAAK,SAASV,EAAG+D,eACvCC,MAAQ7F,EAAE4F,WACVE,OAASD,MAAME,WACG,IAAlBD,OAAOtE,QAAiBsE,OAAOtE,OAAS,GAAgB,MAAXsE,aAK7ClC,MAAQ1C,MAAMmE,SAASQ,OAEvBG,aAAe9E,MAAM+E,kBAAkB/E,MAAMwD,SAASmB,OAAQC,QAE9DI,WAAahF,MAAMiF,aAAaH,iBAChCE,WAAW1E,UACPwE,aAAanB,SAAS,YAAa,KAC/BC,UAAY5D,MAAM6D,iBAAiB7D,MAAMwD,SAASsB,kBACrC9E,MAAMkF,sBAAsBJ,cAAc,GAC5CxE,OAASsD,UAAW,KAC3BE,UAAYgB,aAAazB,QAC7BS,UAAUR,YAAY,gBACtBQ,UAAUW,WAAW,YACrBO,WAAWG,MAAMrB,WAKjBsB,gBAAgBC,uBAAuBvB,gBAEvCkB,WAAWjF,SAAS,eAGxBiF,WAAWjF,SAAS,cAKxBmE,KAAOxC,KAAKjB,KAAK,kBAAoBiC,OACzC1C,MAAMsF,eAAeR,aAAcZ,UAIvClE,MAAMV,eAAiBU,MAAMuF,6BAQjCrG,0BAA0BkB,UAAUmF,0BAA4B,eACxDC,OAAS,eACR1F,UAAUW,KAAK,oBAAoBY,MAAK,CAACV,EAAG+D,aAC7Cc,OAAOd,UAAUe,IAAMf,UAAUgB,SAG9BF,QAQXtG,0BAA0BkB,UAAUuF,qBAAuB,iBACjDC,UAAYlG,KAAKJ,eACjBuG,UAAYnG,KAAK6F,gCACnBO,cAAe,SAGfC,KAAKC,UAAUH,aAAeE,KAAKC,UAAUJ,YAC7CE,cAAe,EACRA,eAGXG,OAAOjH,KAAK6G,WAAWK,SAAQC,MACvBN,UAAUM,OAASP,UAAUO,OAC7BL,cAAe,MAIhBA,eAQX5G,0BAA0BkB,UAAUgG,gBAAkB,SAASC,OACvDrG,MAAQN,KACRoC,KAAOhD,EAAEuH,EAAEC,QAAQC,QAAQ,aAE3BC,SADe9G,KAAK+G,kBACM,KAEnB1H,SAAS2H,QAAQL,GAClBM,QAAS7E,KAAK6B,SAAS,iBAIjC7B,KAAK/B,SAAS,gBAAgByC,IAAI,YAAa,IAAIA,IAAI,UAAWgE,cAC9DhC,aAAe9E,KAAK8B,0BAA0BM,KAAM,cACnC,OAAjB0C,aAAuB,MAClBoC,cAAcpC,aAAc,GACjC1C,KAAKwB,YAAY,UAAYkB,kBACzBqC,WAAa7G,MAAM8G,QAAQhF,KAAM0C,cACjCqC,WAAWvG,SACXuG,WAAW9G,SAAS,UACpB+B,KAAKiC,OAAO8C,WAAW9C,eAExB,KACCiB,WAAahF,MAAMiF,aAAanD,SAChCkD,WAAW1E,UACPwB,KAAK6B,SAAS,YAAa,KACvBC,UAAYlE,KAAKmE,iBAAiB7D,MAAMwD,SAAS1B,UACpCpC,KAAKwF,sBAAsBpD,MAAM,GACnCxB,OAASsD,UAAW,KAC3BE,UAAYhC,KAAKuB,QACrBS,UAAUR,YAAY,gBACtBQ,UAAUW,WAAW,YACrBO,WAAWG,MAAMrB,WACjBsB,gBAAgBC,uBAAuBvB,WACvChC,KAAKiC,OAAOD,UAAUC,eAEtBiB,WAAWjF,SAAS,UACpB+B,KAAKiC,OAAOiB,WAAWjB,eAG3BiB,WAAWjF,SAAS,UACpB+B,KAAKiC,OAAOiB,WAAWjB,UAKnChF,SAAS4H,MAAMN,EAAGvE,MAAM,SAASiF,EAAGC,EAAGlF,MACnC9B,MAAMiH,SAASF,EAAGC,EAAGlF,SACtB,SAASiF,EAAGC,EAAGlF,MACd9B,MAAMkH,QAAQH,EAAGC,EAAGlF,WAW5B5C,0BAA0BkB,UAAU6G,SAAW,SAASE,MAAOC,MAAOtF,UAC9D9B,MAAQN,KACR2H,aAAc,OACbvH,UAAUW,KAAK,kBAAoBf,KAAK8D,SAAS1B,OAAOT,MAAK,SAASV,EAAGsD,cACtEC,KAAOpF,EAAEmF,UACTjE,MAAMsH,cAAcH,MAAOC,MAAOlD,QAAUmD,aAC5CA,aAAc,EACdnD,KAAKnE,SAAS,yBAEdmE,KAAKZ,YAAY,gCAGpBxD,UAAUW,KAAK,yBAA2Bf,KAAK8D,SAAS1B,OAAOpB,IAAI,iBAAiBW,MAAK,SAASV,EAAGsD,cAClGC,KAAOpF,EAAEmF,WACTjE,MAAMsH,cAAcH,MAAOC,MAAOlD,OAAUmD,aAAgBrH,MAAMuH,iBAAiBzF,KAAMoC,MAIzFA,KAAKZ,YAAY,yBAHjB+D,aAAc,EACdnD,KAAKnE,SAAS,6BAc1Bb,0BAA0BkB,UAAU8G,QAAU,SAASC,MAAOC,MAAOtF,UAC7D9B,MAAQN,KACRgC,KAAOhC,KAAKI,UACZ0H,QAAS,EAGb9F,KAAKjB,KAAK,kBAAoBf,KAAK8D,SAAS1B,OAAOT,MAAK,SAASV,EAAGsD,cAC5DC,KAAOpF,EAAEmF,iBACRjE,MAAMsH,cAAcH,MAAOC,MAAOlD,QAMvCA,KAAKZ,YAAY,wBACjBtD,MAAMsF,eAAexD,KAAMoC,MAC3BsD,QAAS,GACF,MAGNA,QAED9F,KAAKjB,KAAK,yBAA2Bf,KAAK8D,SAAS1B,OAAOpB,IAAI,iBAAiBW,MAAK,SAASV,EAAG8G,gBACxFC,WAAa5I,EAAE2I,gBACdzH,MAAMsH,cAAcH,MAAOC,MAAOM,aAAe1H,MAAMuH,iBAAiBzF,KAAM4F,mBAExE,EAIXA,WAAWpE,YAAY,4BACnBkB,aAAexE,MAAMwB,0BAA0BkG,WAAY,WAC3DxD,KAAOlE,MAAM8G,QAAQhF,KAAM0C,qBAC/BxE,MAAMsF,eAAexD,KAAMoC,MAC3BsD,QAAS,GACF,KAIVA,aACIG,aAAa7F,OAU1B5C,0BAA0BkB,UAAUkF,eAAiB,SAASxD,KAAMoC,UAE5D0D,QAAUlI,KAAKmI,sBAAsBnI,KAAKyE,SAASD,UAChC,IAAnB0D,QAAQtH,OAAc,CACtBsH,QAAQ7H,SAAS,gBACjB6H,QAAQ7D,OAAO6D,QAAQ7D,cACnBS,aAAe9E,KAAK8B,0BAA0BoG,QAAS,WAC1ClI,KAAKoH,QAAQc,QAASpD,cAC5BzE,SAAS,eACf4H,aAAaC,SAGF,IAAhB9F,KAAKxB,aACAsG,cAAclH,KAAKyE,SAASD,MAAO,GACpCA,KAAKG,KAAK,YACVH,KAAK4D,eAGJlB,cAAclH,KAAKyE,SAASD,MAAOxE,KAAK6D,UAAUzB,OACvDA,KAAKwB,YAAY,YACZvD,SAAS,iBAAmBL,KAAKyE,SAASD,OAC/CpC,KAAKiG,KAAK,WAAY,QACjBC,UAAUlG,KAAMoC,QAS7BhF,0BAA0BkB,UAAUuH,aAAe,SAAS7F,UACpD0C,aAAe9E,KAAK8B,0BAA0BM,KAAM,WACnC,OAAjB0C,cACA1C,KAAKwB,YAAY,UAAYkB,cAEjC1C,KAAKuC,KAAK,YAAY,QAEjB2D,UAAUlG,KAAMpC,KAAKuI,YAAYvI,KAAK8D,SAAS1B,MAAOpC,KAAK6D,UAAUzB,SAW9E5C,0BAA0BkB,UAAU8H,eAAiB,SAAS7B,OACtDnC,KAAOpF,EAAEuH,EAAEC,QAAQC,QAAQ,gBACX,IAAhBrC,KAAK5D,OAAc,KACfoH,WAAa5I,EAAEuH,EAAEC,QACjB9B,aAAe9E,KAAK8B,0BAA0BkG,WAAY,WACzC,OAAjBlD,eACAN,KAAOxE,KAAKoH,QAAQY,WAAYlD,mBAGpC2D,YAAczI,KAAKmI,sBAAsBnI,KAAKyE,SAASD,OACvDkE,SAAWtJ,WAEPuH,EAAEgC,cACDrJ,KAAKsJ,WACLtJ,KAAKuJ,gBACLvJ,KAAKwJ,UACNJ,SAAW1I,KAAK+I,YAAY/I,KAAK8D,SAASU,MAAOiE,wBAGhDnJ,KAAK0J,eACL1J,KAAK2J,QACNP,SAAW1I,KAAKkJ,gBAAgBlJ,KAAK8D,SAASU,MAAOiE,wBAGpDnJ,KAAK6J,OACNzD,gBAAgB0D,sBAAuB,4BAIvC1D,gBAAgB0D,sBAAuB,MAI3CV,SAAS9H,OAAQ,CACjB8H,SAAS/D,KAAK,WAAW,GACzB+D,SAASrI,SAAS,oBACdiF,WAAatF,KAAKuF,aAAamD,aAC/BpD,WAAW1E,UACP8H,SAASzE,SAAS,YAAa,KAC3BC,UAAYlE,KAAKmE,iBAAiBnE,KAAK8D,SAAS4E,cACnC1I,KAAKwF,sBAAsBkD,UAAU,GACvC9H,OAASsD,UAAW,KAC3BE,UAAYsE,SAAS/E,QACzBS,UAAUR,YAAY,gBACtBQ,UAAUW,WAAW,YACrBO,WAAWG,MAAMrB,WACjBsB,gBAAgBC,uBAAuBvB,WACvCsE,SAASrE,OAAOD,UAAUC,eAE1BiB,WAAWjF,SAAS,UACpBqI,SAASrE,OAAOiB,WAAWjB,eAG/BiB,WAAWjF,SAAS,UACpBqI,SAASrE,OAAOiB,WAAWjB,eAInCG,KAAKG,KAAK,WAAW,GAGzBgC,EAAE0C,sBACGzD,eAAe8C,SAAUlE,OAUlChF,0BAA0BkB,UAAUqI,YAAc,SAAShH,MAAOK,UAC1D8C,OACAoE,WAAatJ,KAAKuJ,mBAAmBxH,OAGrCmD,OADgB,IAAhB9C,KAAKxB,OACI,EAEAZ,KAAK6D,UAAUzB,MAAQ,UAGhCoH,KAAOxJ,KAAKqF,kBAAkBtD,MAAOmD,QAClB,IAAhBsE,KAAK5I,QAAgBsE,OAASoE,YACjCpE,SACAsE,KAAOxJ,KAAKqF,kBAAkBtD,MAAOmD,eAGlCsE,MAUXhK,0BAA0BkB,UAAUwI,gBAAkB,SAASnH,MAAOK,UAC9D8C,OAGAA,OADgB,IAAhB9C,KAAKxB,OACIZ,KAAKuJ,mBAAmBxH,OAExB/B,KAAK6D,UAAUzB,MAAQ,UAGhCqH,SAAWzJ,KAAKqF,kBAAkBtD,MAAOmD,QAClB,IAApBuE,SAAS7I,QAAgBsE,OAAS,GACrCA,SACAuE,SAAWzJ,KAAKqF,kBAAkBtD,MAAOmD,eAItCuE,UASXjK,0BAA0BkB,UAAU4H,UAAY,SAASlG,KAAMwE,YACvD8C,WAAatH,KAAKiC,SAClBsF,UAAY/C,OAAOvC,SACnB/D,MAAQN,KAEZH,EAAEC,KAAKC,WAAW,+BAAiCO,MAAMb,aAKzD2C,KAAKwH,QACD,CACIlH,KAAMS,SAASf,KAAKU,IAAI,SAAW6G,UAAUjH,KAAOgH,WAAWhH,KAC/DE,IAAKO,SAASf,KAAKU,IAAI,QAAU6G,UAAU/G,IAAM8G,WAAW9G,KAEhE,CACIiH,SAAU,OACVC,KAAM,WACF1K,EAAE,QAAQ2K,QAAQ,gCAAiC,CAAC3H,KAAMwE,OAAQtG,QAClET,EAAEC,KAAK4B,YAAY,+BAAiCpB,MAAMb,iBAc1ED,0BAA0BkB,UAAUkH,cAAgB,SAASH,MAAOC,MAAOlD,UACnEwF,SAAWxF,KAAKH,gBAChBG,KAAKP,SAAS,YACPwD,OAASuC,SAAStH,MAAQ+E,MAAQuC,SAAStH,KAAO8B,KAAKyF,cACvDvC,OAASsC,SAASpH,KAAO8E,MAAQsC,SAASpH,IAAM4B,KAAK0F,cAEzDzC,OAASuC,SAAStH,MAAQ+E,MAAQuC,SAAStH,KAAO8B,KAAKlB,SACvDoE,OAASsC,SAASpH,KAAO8E,MAAQsC,SAASpH,IAAM4B,KAAKjB,UAShE/D,0BAA0BkB,UAAUwG,cAAgB,SAASlE,MAAOkC,aAC3D9E,UAAUW,KAAK,yBAA2BiC,OAAOmC,IAAID,SAQ9D1F,0BAA0BkB,UAAUN,QAAU,kBACnChB,EAAE+K,SAASC,eAAepK,KAAKP,eAO1CD,0BAA0BkB,UAAU2J,QAAU,kBACnCrK,KAAKI,UAAUW,KAAK,uBAU/BvB,0BAA0BkB,UAAU6H,YAAc,SAASxG,MAAOmD,eACzDlF,KAAKI,UAAUW,KAAK,kCAAoCgB,MAAQ,UAAYmD,QAAQoF,GAAG,YAMrFtK,KAAKI,UAAUW,KAAK,kCAAoCgB,MAAQ,UAAYmD,QALxElF,KAAKI,UAAUW,KAAK,iBAAmBgB,MAAnB,6BAEXmD,OACZ,SAAWnD,QAYvBvC,0BAA0BkB,UAAU2E,kBAAoB,SAAStD,MAAOmD,eAC7DlF,KAAKI,UAAUW,KAAK,0BAA4BgB,MAAQ,UAAYmD,OAAS,aAAaqF,MAAM,EAAG,IAS9G/K,0BAA0BkB,UAAUyH,sBAAwB,SAASnF,cAC1DhD,KAAKI,UAAUW,KAAK,4BAA8BiC,QAS7DxD,0BAA0BkB,UAAUyD,iBAAmB,SAASpC,cACrD/B,KAAKI,UAAUW,KAAK,kBAAoBgB,OAAOnB,QAS1DpB,0BAA0BkB,UAAU6I,mBAAqB,SAASxH,cACvD/B,KAAKI,UAAUW,KAAK,iBAAmBgB,MAAQ,cAAcnB,QAUxEpB,0BAA0BkB,UAAUoB,0BAA4B,SAASF,KAAM4I,YACvEC,QAAU7I,KAAKyG,KAAK,YACR,KAAZoC,gBACIC,WAAaD,QAAQE,MAAM,KACtBnH,MAAQ,EAAGA,MAAQkH,WAAW9J,OAAQ4C,QAAS,IACxC,IAAIoH,OAAO,IAAMJ,OAAS,aAC5BK,KAAKH,WAAWlH,QAAS,KAE3BsH,MADQ,IAAIF,OAAO,aACLG,KAAKL,WAAWlH,eAC3BwH,OAAOF,MAAM,YAIzB,MASXtL,0BAA0BkB,UAAUmD,UAAY,SAASzB,aAC9CpC,KAAK8B,0BAA0BM,KAAM,WAUhD5C,0BAA0BkB,UAAUoD,SAAW,SAASlC,aAC7C5B,KAAK8B,0BAA0BF,KAAM,UAShDpC,0BAA0BkB,UAAU+D,SAAW,SAAS7C,aAC7C5B,KAAK8B,0BAA0BF,KAAM,UAShDpC,0BAA0BkB,UAAU6E,aAAe,SAASnD,aACjDpC,KAAKI,UAAUW,KAAK,iBACvBf,KAAK8D,SAAS1B,MADS,oBAGXpC,KAAK6D,UAAUzB,MAC3B,SAAWpC,KAAK8D,SAAS1B,MACzB,qBAUR5C,0BAA0BkB,UAAU8E,sBAAwB,SAASpD,KAAM6I,eACnEA,OACOjL,KAAKI,UAAUW,KAAK,iBACvBf,KAAK8D,SAAS1B,MADS,oBAGXpC,KAAK6D,UAAUzB,MAC3B,SAAWpC,KAAK8D,SAAS1B,MACzB,aAAapB,IAAI,oBAElBhB,KAAKI,UAAUW,KAAK,mBACXf,KAAK6D,UAAUzB,MAC3B,SAAWpC,KAAK8D,SAAS1B,MACzB,aAAapB,IAAI,qBAUzBxB,0BAA0BkB,UAAU0G,QAAU,SAAShF,KAAM0C,qBAClD9E,KAAKI,UAAUW,KAAK,kBAAoBf,KAAK8D,SAAS1B,MAAQ,SAAW0C,eAMpFtF,0BAA0BkB,UAAUwK,aAAe,eAC3C5K,MAAQN,KACRsE,QAAUtE,KAAKsE,UACftE,KAAKG,aACLmE,QAAU,QAGTlE,UAAUW,KAAK,qBAAqBY,MAAK,SAASV,EAAGsD,UACtDnF,EAAEmF,UACGzB,IAAI,OAAQK,SAAS/D,EAAEmF,UAAUI,KAAK,YAAcwG,WAAW7G,UAC/DxB,IAAI,MAAOK,SAAS/D,EAAEmF,UAAUI,KAAK,YAAcwG,WAAW7G,UACnEhE,MAAMsE,mBAAmBL,SAAU,oBAGlCnE,UAAUW,KAAK,0BAA0BC,IAAI,iBAAiBW,MAAK,SAAS8E,IAAKrE,MAClFhD,EAAEgD,MACGU,IAAI,OAAQqI,WAAW/L,EAAEgD,MAAMuC,KAAK,YAAcwG,WAAW7G,UAC7DxB,IAAI,MAAOqI,WAAW/L,EAAEgD,MAAMuC,KAAK,YAAcwG,WAAW7G,UACjEhE,MAAMsE,mBAAmBxC,KAAM,gBASvC5C,0BAA0BkB,UAAU4D,QAAU,eACtC8G,MAAQpL,KAAKqK,UACbgB,kBAAoBD,MAAME,IAAI,GAAGC,oBACdH,MAAM9H,QAEH+H,mBAS9B7L,0BAA0BkB,UAAUkE,mBAAqB,SAAS4G,QAASC,UACnEnH,QAAU6G,WAAWnL,KAAKsE,WAC1BtE,KAAKG,aACLmE,QAAU,GAEdlF,EAAEoM,SAAS1I,IAAI,qBACU,SAAWwB,QAAU,qBACxB,SAAWA,QAAU,oBACtB,SAAWA,QAAU,mBACtB,SAAWA,QAAU,cACxB,SAAWA,QAAU,uBACdmH,QAS5BjM,0BAA0BkB,UAAUqG,gBAAkB,eAC9C2E,OAAS,cACRtL,UAAUW,KAAK,6CAA6CY,MAAK,SAASV,EAAGsD,cAI1EoH,YAHJpH,SAAWnF,EAAEmF,WAGazB,IAAI,WAAaK,SAASoB,SAASzB,IAAI,YAAc,EAE3E6I,WAAaD,SACbA,OAASC,eAIVD,QAUXlM,0BAA0BkB,UAAUmH,iBAAmB,SAASzF,KAAMoC,aAC3DxE,KAAK6D,UAAUzB,QAAUpC,KAAK6D,UAAUW,OAASxE,KAAK8D,SAAS1B,QAAUpC,KAAK8D,SAASU,WAQ9FkB,gBAAkB,CAKlBkG,0BAA0B,EAM1BC,6BAA8B,GAK9B1L,YAAY,EAKZiJ,sBAAsB,EAKtB0C,UAAW,GAUXC,KAAM,SAAStM,YAAaC,SAAUC,WAClC+F,gBAAgBoG,UAAUrM,aACtB,IAAID,0BAA0BC,YAAaC,SAAUC,QACpD+F,gBAAgBkG,2BACjBlG,gBAAgBsG,qBAChBtG,gBAAgBkG,0BAA2B,IAE1ClG,gBAAgBmG,6BAA6B9I,eAAetD,aAAc,CAC3EiG,gBAAgBmG,6BAA6BpM,cAAe,MAExDwM,kBAAoB9B,SAASC,eAAe3K,aAC5CwM,kBAAkBC,UAAUC,SAAS,mBACpCF,kBAAkBC,UAAUC,SAAS,iCAEtCzG,gBAAgBC,uBAAuBvG,EAAE6M,mBAAmBlL,KAAK,gBAQ7EiL,mBAAoB,WAChB5M,EAAE,QACGgN,GAAG,UACA,6EACA1G,gBAAgB8C,gBACnB4D,GAAG,UACA,4FACA1G,gBAAgB8C,gBACnB4D,GAAG,gCAAiC1G,gBAAgB2G,iBACzDjN,EAAEkN,QAAQF,GAAG,UAAU,WACnB1G,gBAAgB6G,oBAAmB,MAEvCD,OAAOE,iBAAiB,eAAe,WACnC9G,gBAAgBvF,YAAa,EAC7BuF,gBAAgB6G,mBAAmB7G,gBAAgBvF,eAEvDmM,OAAOE,iBAAiB,cAAc,WAClC9G,gBAAgBvF,YAAa,EAC7BuF,gBAAgB6G,mBAAmB7G,gBAAgBvF,eAEvDU,YAAW,WACP6E,gBAAgB+G,2BACjB,MAQP9G,uBAAwB,SAAS6F,SAE7BA,QAAQkB,OAAO,wBACflB,QAAQY,GAAG,uBAAwB1G,gBAAgBgB,kBAOvDA,gBAAiB,SAASC,GACtBA,EAAE0C,qBACEsD,SAAWjH,gBAAgBkH,oBAAoBjG,GAC/CgG,UACAA,SAASjG,gBAAgBC,IAQjC6B,eAAgB,SAAS7B,OACjBjB,gBAAgB0D,sBAGpB1D,gBAAgB0D,sBAAuB,MACnCuD,SAAWjH,gBAAgBkH,oBAAoBjG,GAC/CgG,UACAA,SAASnE,eAAe7B,KAQhC4F,mBAAoB,SAASpM,gBACpB,IAAIV,eAAeiG,gBAAgBoG,UAChCpG,gBAAgBoG,UAAU/I,eAAetD,eACzCiG,gBAAgBoG,UAAUrM,aAAaU,WAAaA,WACpDuF,gBAAgBoG,UAAUrM,aAAayL,iBAUnDuB,uBAAwB,gBACfF,mBAAmB7G,gBAAgBvF,YAIxCU,YAAW,WACP6E,gBAAgB+G,uBAAuB/G,gBAAgBvF,cACxD,MAWPkM,gBAAiB,SAAS1F,EAAGvE,KAAMwE,OAAQtG,OACvC8B,KAAKwB,YAAY,gBAAgBd,IAAI,UAAW,IAChDV,KAAKU,IAAI,MAAO8D,OAAOoD,WAAWpH,KAAKE,IAAI,OAAQ8D,OAAOoD,WAAWtH,MACrEkE,OAAOnB,MAAMrD,MACbwE,OAAOhD,YAAY,eACkB,IAA1BxB,KAAKuC,KAAK,cAAyD,IAA1BvC,KAAKuC,KAAK,aAC1DvC,KAAKwB,YAAY,UAAUvD,SAAS,YACpC+B,KAAK2C,WAAW,YAChB3C,KAAKyK,WAAW,YAChBzK,KAAKU,IAAI,MAAO,IACXA,IAAI,OAAQ,IACZA,IAAI,YAAa,IAClBV,KAAK6B,SAAS,aAAe3D,MAAMkF,sBAAsBpD,MAAM,GAAMxB,OAAS,GAC9EN,MAAMkF,sBAAsBpD,MAAM,GAAM0K,QAAQC,WAGpD3K,KAAKuC,KAAK,UAAWiC,OAAOjC,KAAK,YAAYA,KAAK,UAAWiC,OAAOjC,KAAK,YACzErE,MAAMsE,mBAAmBxC,KAAM,kBAEC,IAAzBA,KAAKuC,KAAK,aAAuD,IAAzBvC,KAAKuC,KAAK,aACzDvC,KAAKgG,QACLhG,KAAKyK,WAAW,iBAEkB,IAA3BjG,OAAOjC,KAAK,aAAyD,IAA3BiC,OAAOjC,KAAK,YAC7DiC,OAAOiG,WAAW,WAElBnH,gBAAgB0D,uBAChB1D,gBAAgB0D,sBAAuB,GAEvC9I,MAAM2F,yBAENP,gBAAgBsH,kBAEhB1M,MAAMV,eAAiBU,MAAMuF,8BASrC+G,oBAAqB,SAASjG,OACtBlH,YAAcL,EAAEuH,EAAEsG,eAAepG,QAAQ,sBAAsBwB,KAAK,aACjE3C,gBAAgBoG,UAAUrM,cAMrCuN,gBAAiB,iBACPE,aAAe/C,SAASC,eAAe,gBAC7C7K,kBAAkB4N,gBAAgBD,sBAOnC,CACHnB,KAAMrG,gBAAgBqG"}
\ No newline at end of file
diff --git a/question/type/ddimageortext/amd/src/question.js b/question/type/ddimageortext/amd/src/question.js
index b95cd05d53e..bfc1cecf395 100644
--- a/question/type/ddimageortext/amd/src/question.js
+++ b/question/type/ddimageortext/amd/src/question.js
@@ -20,7 +20,17 @@
* @copyright 2018 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-define(['jquery', 'core/dragdrop', 'core/key_codes'], function($, dragDrop, keys) {
+define([
+ 'jquery',
+ 'core/dragdrop',
+ 'core/key_codes',
+ 'core_form/changechecker'
+], function(
+ $,
+ dragDrop,
+ keys,
+ FormChangeChecker
+) {
"use strict";
@@ -34,6 +44,7 @@ define(['jquery', 'core/dragdrop', 'core/key_codes'], function($, dragDrop, keys
*/
function DragDropOntoImageQuestion(containerId, readOnly, places) {
this.containerId = containerId;
+ this.questionAnswer = {};
M.util.js_pending('qtype_ddimageortext-init-' + this.containerId);
this.places = places;
this.allImagesLoaded = false;
@@ -301,6 +312,48 @@ define(['jquery', 'core/dragdrop', 'core/key_codes'], function($, dragDrop, keys
var drop = root.find('.dropzone.place' + place);
thisQ.sendDragToDrop(unplacedDrag, drop);
});
+
+ // Save the question answer.
+ thisQ.questionAnswer = thisQ.getQuestionAnsweredValues();
+ };
+
+ /**
+ * Get the question answered values.
+ *
+ * @return {Object} Contain key-value with key is the input id and value is the input value.
+ */
+ DragDropOntoImageQuestion.prototype.getQuestionAnsweredValues = function() {
+ let result = {};
+ this.getRoot().find('input.placeinput').each((i, inputNode) => {
+ result[inputNode.id] = inputNode.value;
+ });
+
+ return result;
+ };
+
+ /**
+ * Check if the question is being interacted or not.
+ *
+ * @return {boolean} Return true if the user has changed the question-answer.
+ */
+ DragDropOntoImageQuestion.prototype.isQuestionInteracted = function() {
+ const oldAnswer = this.questionAnswer;
+ const newAnswer = this.getQuestionAnsweredValues();
+ let isInteracted = false;
+
+ // First, check both answers have the same structure or not.
+ if (JSON.stringify(newAnswer) !== JSON.stringify(oldAnswer)) {
+ isInteracted = true;
+ return isInteracted;
+ }
+ // Check the values.
+ Object.keys(newAnswer).forEach(key => {
+ if (newAnswer[key] !== oldAnswer[key]) {
+ isInteracted = true;
+ }
+ });
+
+ return isInteracted;
};
/**
@@ -1131,6 +1184,12 @@ define(['jquery', 'core/dragdrop', 'core/key_codes'], function($, dragDrop, keys
if (questionManager.isKeyboardNavigation) {
questionManager.isKeyboardNavigation = false;
}
+ if (thisQ.isQuestionInteracted()) {
+ // The user has interacted with the draggable items. We need to mark the form as dirty.
+ questionManager.handleFormDirty();
+ // Save the new answered value.
+ thisQ.questionAnswer = thisQ.getQuestionAnsweredValues();
+ }
},
/**
@@ -1141,6 +1200,14 @@ define(['jquery', 'core/dragdrop', 'core/key_codes'], function($, dragDrop, keys
getQuestionForEvent: function(e) {
var containerId = $(e.currentTarget).closest('.que.ddimageortext').attr('id');
return questionManager.questions[containerId];
+ },
+
+ /**
+ * Handle when the form is dirty.
+ */
+ handleFormDirty: function() {
+ const responseForm = document.getElementById('responseform');
+ FormChangeChecker.markFormAsDirty(responseForm);
}
};
diff --git a/question/type/ddmarker/amd/build/question.min.js b/question/type/ddmarker/amd/build/question.min.js
index a6925e3fc87..fbdc11f8eb5 100644
--- a/question/type/ddmarker/amd/build/question.min.js
+++ b/question/type/ddmarker/amd/build/question.min.js
@@ -5,6 +5,6 @@
* @copyright 2018 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-define("qtype_ddmarker/question",["jquery","core/dragdrop","qtype_ddmarker/shapes","core/key_codes"],(function($,dragDrop,Shapes,keys){function DragDropMarkersQuestion(containerId,readOnly,visibleDropZones){this.containerId=containerId,this.visibleDropZones=visibleDropZones,this.shapes=[],this.shapeSVGs=[],this.isPrinting=!1,readOnly&&this.getRoot().addClass("qtype_ddmarker-readonly"),this.cloneDrags(),this.repositionDrags(),this.drawDropzones()}DragDropMarkersQuestion.prototype.drawDropzones=function(){if(this.visibleDropZones.length>0){var bgImage=this.bgImage();this.getRoot().find("div.dropzones").html('');for(var svg=this.getRoot().find("svg.dropzones"),nextColourIndex=0,dropZoneNo=0;dropZoneNo'+dropZone.markertext+"");var markerspan=this.getRoot().find("div.ddarea div.markertexts span.markertext"+dropZoneNo);if(markerspan.length){var handles=shape.getHandlePositions(),positionLeft=handles.moveHandle.x-markerspan.outerWidth()/2-4,positionTop=handles.moveHandle.y-markerspan.outerHeight()/2;markerspan.css("left",positionLeft).css("top",positionTop),markerspan.data("originX",markerspan.position().left/bgRatio).data("originY",markerspan.position().top/bgRatio),this.handleElementScale(markerspan,"center")}}var shapeSVG=shape.makeSvg(svg[0]);shapeSVG.setAttribute("class","dropzone "+colourClass),this.shapes[this.shapes.length]=shape,this.shapeSVGs[this.shapeSVGs.length]=shapeSVG}},DragDropMarkersQuestion.prototype.repositionDrags=function(){var root=this.getRoot(),thisQ=this;root.find("div.draghomes .marker").not(".dragplaceholder").each((function(key,item){$(item).addClass("unneeded")})),root.find("input.choices").each((function(key,input){var choiceNo=thisQ.getChoiceNoFromElement(input),coords=thisQ.getCoords(input);if(coords.length){var drag=thisQ.getRoot().find(".draghomes span.marker.choice"+choiceNo).not(".dragplaceholder");drag.remove();for(var i=0;i=bgPosition.left&&point.x=bgPosition.top&&point.y2&&void 0!==arguments[2]&&arguments[2];var dropArea=this.dropArea(),bgRatio=this.bgRatio();drag.removeClass("beingdragged").removeClass("unneeded");var dragXY=this.convertToBgImgXY(new Shapes.Point(drag.data("pagex"),drag.data("pagey")));isScaling?(drag.data("originX",dragXY.x/bgRatio).data("originY",dragXY.y/bgRatio),drag.css("left",dragXY.x).css("top",dragXY.y)):(drag.data("originX",dragXY.x).data("originY",dragXY.y),drag.css("left",dragXY.x*bgRatio).css("top",dragXY.y*bgRatio)),initialLoad||drag.data("scaleRatio",bgRatio),dropArea.append(drag),this.handleElementScale(drag,"left top")},DragDropMarkersQuestion.prototype.cloneDragIfNeeded=function(drag){var inputNode=this.getInput(drag),noOfDrags=Number(this.getClassnameNumericSuffix(inputNode,"noofdrags")),displayedDragsInDropArea=this.getRoot().find("div.droparea .marker.choice"+this.getChoiceNoFromElement(drag)+this.getDragNoClass(drag,!0)).length,displayedDragsInDragHomes=this.getRoot().find("div.draghomes .marker.choice"+this.getChoiceNoFromElement(drag)+this.getDragNoClass(drag,!0)).not(".dragplaceholder").length;if((this.isInfiniteDrag(drag)||!this.isInfiniteDrag(drag)&&displayedDragsInDropArea1;)dragsInHome.first().remove(),displayedDrags--},DragDropMarkersQuestion.prototype.getInput=function(drag){var choiceNo=this.getChoiceNoFromElement(drag);return this.getRoot().find("input.choices.choice"+choiceNo)},DragDropMarkersQuestion.prototype.bgRatio=function(){var bgImg=this.bgImage(),bgImgNaturalWidth=bgImg.get(0).naturalWidth;return bgImg.width()/bgImgNaturalWidth},DragDropMarkersQuestion.prototype.handleElementScale=function(element,type){var bgRatio=parseFloat(this.bgRatio());this.isPrinting&&(bgRatio=1),$(element).css({"-webkit-transform":"scale("+bgRatio+")","-moz-transform":"scale("+bgRatio+")","-ms-transform":"scale("+bgRatio+")","-o-transform":"scale("+bgRatio+")",transform:"scale("+bgRatio+")","transform-origin":type})},DragDropMarkersQuestion.prototype.isInfiniteDrag=function(drag){return drag.hasClass("infinite")};var questionManager={eventHandlersInitialised:!1,markerEventHandlersInitialised:{},isPrinting:!1,isKeyboardNavigation:!1,questions:{},init:function(containerId,readOnly,visibleDropZones){if(questionManager.questions[containerId]=new DragDropMarkersQuestion(containerId,readOnly,visibleDropZones),questionManager.eventHandlersInitialised||(questionManager.setupEventHandlers(),questionManager.eventHandlersInitialised=!0),!questionManager.markerEventHandlersInitialised.hasOwnProperty(containerId)){questionManager.markerEventHandlersInitialised[containerId]=!0;var questionContainer=document.getElementById(containerId);questionContainer.classList.contains("ddmarker")&&!questionContainer.classList.contains("qtype_ddmarker-readonly")&&(questionManager.addEventHandlersToMarker($(questionContainer).find("div.draghomes .marker")),questionManager.addEventHandlersToMarker($(questionContainer).find("div.droparea .marker")))}},setupEventHandlers:function(){$(window).on("resize",(function(){questionManager.handleWindowResize(!1)})),window.addEventListener("beforeprint",(function(){questionManager.isPrinting=!0,questionManager.handleWindowResize(questionManager.isPrinting)})),window.addEventListener("afterprint",(function(){questionManager.isPrinting=!1,questionManager.handleWindowResize(questionManager.isPrinting)})),setTimeout((function(){questionManager.fixLayoutIfThingsMoved()}),100)},addEventHandlersToMarker:function(element){element.on("mousedown touchstart",questionManager.handleDragStart).on("keydown keypress",questionManager.handleKeyPress).focusin((function(e){questionManager.handleKeyboardFocus(e,!0)})).focusout((function(e){questionManager.handleKeyboardFocus(e,!1)}))},handleDragStart:function(e){e.preventDefault();var question=questionManager.getQuestionForEvent(e);question&&question.handleDragStart(e)},handleKeyPress:function(e){var question=questionManager.getQuestionForEvent(e);question&&question.handleKeyPress(e)},handleWindowResize:function(isPrinting){for(var containerId in questionManager.questions)questionManager.questions.hasOwnProperty(containerId)&&(questionManager.questions[containerId].isPrinting=isPrinting,questionManager.questions[containerId].handleResize())},handleKeyboardFocus:function(e,isNavigating){questionManager.isKeyboardNavigation=isNavigating},fixLayoutIfThingsMoved:function(){questionManager.isKeyboardNavigation||this.handleWindowResize(questionManager.isPrinting),setTimeout((function(){questionManager.fixLayoutIfThingsMoved(questionManager.isPrinting)}),100)},getQuestionForEvent:function(e){var containerId=$(e.currentTarget).closest(".que.ddmarker").attr("id");return questionManager.questions[containerId]}};return{init:questionManager.init}}));
+define("qtype_ddmarker/question",["jquery","core/dragdrop","qtype_ddmarker/shapes","core/key_codes","core_form/changechecker"],(function($,dragDrop,Shapes,keys,FormChangeChecker){function DragDropMarkersQuestion(containerId,readOnly,visibleDropZones){this.containerId=containerId,this.visibleDropZones=visibleDropZones,this.shapes=[],this.shapeSVGs=[],this.isPrinting=!1,this.questionAnswer={},readOnly&&this.getRoot().addClass("qtype_ddmarker-readonly"),this.cloneDrags(),this.repositionDrags(),this.drawDropzones()}DragDropMarkersQuestion.prototype.drawDropzones=function(){if(this.visibleDropZones.length>0){var bgImage=this.bgImage();this.getRoot().find("div.dropzones").html('');for(var svg=this.getRoot().find("svg.dropzones"),nextColourIndex=0,dropZoneNo=0;dropZoneNo'+dropZone.markertext+"");var markerspan=this.getRoot().find("div.ddarea div.markertexts span.markertext"+dropZoneNo);if(markerspan.length){var handles=shape.getHandlePositions(),positionLeft=handles.moveHandle.x-markerspan.outerWidth()/2-4,positionTop=handles.moveHandle.y-markerspan.outerHeight()/2;markerspan.css("left",positionLeft).css("top",positionTop),markerspan.data("originX",markerspan.position().left/bgRatio).data("originY",markerspan.position().top/bgRatio),this.handleElementScale(markerspan,"center")}}var shapeSVG=shape.makeSvg(svg[0]);shapeSVG.setAttribute("class","dropzone "+colourClass),this.shapes[this.shapes.length]=shape,this.shapeSVGs[this.shapeSVGs.length]=shapeSVG}},DragDropMarkersQuestion.prototype.repositionDrags=function(){var root=this.getRoot(),thisQ=this;root.find("div.draghomes .marker").not(".dragplaceholder").each((function(key,item){$(item).addClass("unneeded")})),root.find("input.choices").each((function(key,input){var choiceNo=thisQ.getChoiceNoFromElement(input),coords=thisQ.getCoords(input);if(coords.length){var drag=thisQ.getRoot().find(".draghomes span.marker.choice"+choiceNo).not(".dragplaceholder");drag.remove();for(var i=0;i{result[inputNode.id]=inputNode.value})),result},DragDropMarkersQuestion.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)},DragDropMarkersQuestion.prototype.getCoords=function(inputNode){var coords=[],val=$(inputNode).val();if(""!==val)for(var coordsStrings=val.split(";"),i=0;i=bgPosition.left&&point.x=bgPosition.top&&point.y2&&void 0!==arguments[2]&&arguments[2];var dropArea=this.dropArea(),bgRatio=this.bgRatio();drag.removeClass("beingdragged").removeClass("unneeded");var dragXY=this.convertToBgImgXY(new Shapes.Point(drag.data("pagex"),drag.data("pagey")));isScaling?(drag.data("originX",dragXY.x/bgRatio).data("originY",dragXY.y/bgRatio),drag.css("left",dragXY.x).css("top",dragXY.y)):(drag.data("originX",dragXY.x).data("originY",dragXY.y),drag.css("left",dragXY.x*bgRatio).css("top",dragXY.y*bgRatio)),initialLoad||drag.data("scaleRatio",bgRatio),dropArea.append(drag),this.handleElementScale(drag,"left top")},DragDropMarkersQuestion.prototype.cloneDragIfNeeded=function(drag){var inputNode=this.getInput(drag),noOfDrags=Number(this.getClassnameNumericSuffix(inputNode,"noofdrags")),displayedDragsInDropArea=this.getRoot().find("div.droparea .marker.choice"+this.getChoiceNoFromElement(drag)+this.getDragNoClass(drag,!0)).length,displayedDragsInDragHomes=this.getRoot().find("div.draghomes .marker.choice"+this.getChoiceNoFromElement(drag)+this.getDragNoClass(drag,!0)).not(".dragplaceholder").length;if((this.isInfiniteDrag(drag)||!this.isInfiniteDrag(drag)&&displayedDragsInDropArea1;)dragsInHome.first().remove(),displayedDrags--},DragDropMarkersQuestion.prototype.getInput=function(drag){var choiceNo=this.getChoiceNoFromElement(drag);return this.getRoot().find("input.choices.choice"+choiceNo)},DragDropMarkersQuestion.prototype.bgRatio=function(){var bgImg=this.bgImage(),bgImgNaturalWidth=bgImg.get(0).naturalWidth;return bgImg.width()/bgImgNaturalWidth},DragDropMarkersQuestion.prototype.handleElementScale=function(element,type){var bgRatio=parseFloat(this.bgRatio());this.isPrinting&&(bgRatio=1),$(element).css({"-webkit-transform":"scale("+bgRatio+")","-moz-transform":"scale("+bgRatio+")","-ms-transform":"scale("+bgRatio+")","-o-transform":"scale("+bgRatio+")",transform:"scale("+bgRatio+")","transform-origin":type})},DragDropMarkersQuestion.prototype.isInfiniteDrag=function(drag){return drag.hasClass("infinite")};var questionManager={eventHandlersInitialised:!1,markerEventHandlersInitialised:{},isPrinting:!1,isKeyboardNavigation:!1,questions:{},init:function(containerId,readOnly,visibleDropZones){if(questionManager.questions[containerId]=new DragDropMarkersQuestion(containerId,readOnly,visibleDropZones),questionManager.eventHandlersInitialised||(questionManager.setupEventHandlers(),questionManager.eventHandlersInitialised=!0),!questionManager.markerEventHandlersInitialised.hasOwnProperty(containerId)){questionManager.markerEventHandlersInitialised[containerId]=!0;var questionContainer=document.getElementById(containerId);questionContainer.classList.contains("ddmarker")&&!questionContainer.classList.contains("qtype_ddmarker-readonly")&&(questionManager.addEventHandlersToMarker($(questionContainer).find("div.draghomes .marker")),questionManager.addEventHandlersToMarker($(questionContainer).find("div.droparea .marker")))}},setupEventHandlers:function(){$(window).on("resize",(function(){questionManager.handleWindowResize(!1)})),window.addEventListener("beforeprint",(function(){questionManager.isPrinting=!0,questionManager.handleWindowResize(questionManager.isPrinting)})),window.addEventListener("afterprint",(function(){questionManager.isPrinting=!1,questionManager.handleWindowResize(questionManager.isPrinting)})),setTimeout((function(){questionManager.fixLayoutIfThingsMoved()}),100)},addEventHandlersToMarker:function(element){element.on("mousedown touchstart",questionManager.handleDragStart).on("keydown keypress",questionManager.handleKeyPress).focusin((function(e){questionManager.handleKeyboardFocus(e,!0)})).focusout((function(e){questionManager.handleKeyboardFocus(e,!1)}))},handleDragStart:function(e){e.preventDefault();var question=questionManager.getQuestionForEvent(e);question&&question.handleDragStart(e)},handleKeyPress:function(e){var question=questionManager.getQuestionForEvent(e);question&&question.handleKeyPress(e)},handleWindowResize:function(isPrinting){for(var containerId in questionManager.questions)questionManager.questions.hasOwnProperty(containerId)&&(questionManager.questions[containerId].isPrinting=isPrinting,questionManager.questions[containerId].handleResize())},handleKeyboardFocus:function(e,isNavigating){questionManager.isKeyboardNavigation=isNavigating},fixLayoutIfThingsMoved:function(){questionManager.isKeyboardNavigation||this.handleWindowResize(questionManager.isPrinting),setTimeout((function(){questionManager.fixLayoutIfThingsMoved(questionManager.isPrinting)}),100)},getQuestionForEvent:function(e){var containerId=$(e.currentTarget).closest(".que.ddmarker").attr("id");return questionManager.questions[containerId]},handleFormDirty:function(){const responseForm=document.getElementById("responseform");FormChangeChecker.markFormAsDirty(responseForm)}};return{init:questionManager.init}}));
//# sourceMappingURL=question.min.js.map
\ No newline at end of file
diff --git a/question/type/ddmarker/amd/build/question.min.js.map b/question/type/ddmarker/amd/build/question.min.js.map
index 48e32357e7a..1afcca1c366 100644
--- a/question/type/ddmarker/amd/build/question.min.js.map
+++ b/question/type/ddmarker/amd/build/question.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"question.min.js","sources":["../src/question.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 * Question class for drag and drop marker question type, used to support the question and preview pages.\n *\n * @module qtype_ddmarker/question\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], function($, dragDrop, Shapes, keys) {\n\n \"use strict\";\n\n /**\n * Object to handle one drag-drop markers 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 * @param {Object[]} visibleDropZones the geometry of any drop-zones to show.\n * Objects have fields shape, coords and markertext.\n * @constructor\n */\n function DragDropMarkersQuestion(containerId, readOnly, visibleDropZones) {\n var thisQ = this;\n this.containerId = containerId;\n this.visibleDropZones = visibleDropZones;\n this.shapes = [];\n this.shapeSVGs = [];\n this.isPrinting = false;\n if (readOnly) {\n this.getRoot().addClass('qtype_ddmarker-readonly');\n }\n thisQ.cloneDrags();\n thisQ.repositionDrags();\n thisQ.drawDropzones();\n }\n\n /**\n * Draws the svg shapes of any drop zones that should be visible for feedback purposes.\n */\n DragDropMarkersQuestion.prototype.drawDropzones = function() {\n if (this.visibleDropZones.length > 0) {\n var bgImage = this.bgImage();\n\n this.getRoot().find('div.dropzones').html('');\n var svg = this.getRoot().find('svg.dropzones');\n\n var nextColourIndex = 0;\n for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) {\n var colourClass = 'color' + nextColourIndex;\n nextColourIndex = (nextColourIndex + 1) % 8;\n this.addDropzone(svg, dropZoneNo, colourClass);\n }\n }\n };\n\n /**\n * Adds a dropzone shape with colour, coords and link provided to the array of shapes.\n *\n * @param {jQuery} svg the SVG image to which to add this drop zone.\n * @param {int} dropZoneNo which drop-zone to add.\n * @param {string} colourClass class name\n */\n DragDropMarkersQuestion.prototype.addDropzone = function(svg, dropZoneNo, colourClass) {\n var dropZone = this.visibleDropZones[dropZoneNo],\n shape = Shapes.make(dropZone.shape, ''),\n existingmarkertext,\n bgRatio = this.bgRatio();\n if (!shape.parse(dropZone.coords, bgRatio)) {\n return;\n }\n\n existingmarkertext = this.getRoot().find('div.markertexts span.markertext' + dropZoneNo);\n if (existingmarkertext.length) {\n if (dropZone.markertext !== '') {\n existingmarkertext.html(dropZone.markertext);\n } else {\n existingmarkertext.remove();\n }\n } else if (dropZone.markertext !== '') {\n var classnames = 'markertext markertext' + dropZoneNo;\n this.getRoot().find('div.markertexts').append('' +\n dropZone.markertext + '');\n var markerspan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo);\n if (markerspan.length) {\n var handles = shape.getHandlePositions();\n var positionLeft = handles.moveHandle.x - (markerspan.outerWidth() / 2) - 4;\n var positionTop = handles.moveHandle.y - (markerspan.outerHeight() / 2);\n markerspan\n .css('left', positionLeft)\n .css('top', positionTop);\n markerspan\n .data('originX', markerspan.position().left / bgRatio)\n .data('originY', markerspan.position().top / bgRatio);\n this.handleElementScale(markerspan, 'center');\n }\n }\n\n var shapeSVG = shape.makeSvg(svg[0]);\n shapeSVG.setAttribute('class', 'dropzone ' + colourClass);\n\n this.shapes[this.shapes.length] = shape;\n this.shapeSVGs[this.shapeSVGs.length] = shapeSVG;\n };\n\n /**\n * Draws the drag items on the page (and drop zones if required).\n * The idea is to re-draw all the drags and drops whenever there is a change\n * like a widow resize or an item dropped in place.\n */\n DragDropMarkersQuestion.prototype.repositionDrags = function() {\n var root = this.getRoot(),\n thisQ = this;\n\n root.find('div.draghomes .marker').not('.dragplaceholder').each(function(key, item) {\n $(item).addClass('unneeded');\n });\n\n root.find('input.choices').each(function(key, input) {\n var choiceNo = thisQ.getChoiceNoFromElement(input),\n coords = thisQ.getCoords(input);\n if (coords.length) {\n var drag = thisQ.getRoot().find('.draghomes' + ' span.marker' + '.choice' + choiceNo).not('.dragplaceholder');\n drag.remove();\n for (var i = 0; i < coords.length; i++) {\n var dragInDrop = drag.clone();\n dragInDrop.data('pagex', coords[i].x).data('pagey', coords[i].y);\n // We always save the coordinates in the 1:1 ratio.\n // So we need to set the scale ratio to 1 for the initial load.\n dragInDrop.data('scaleRatio', 1);\n thisQ.sendDragToDrop(dragInDrop, false, true);\n }\n thisQ.getDragClone(drag).addClass('active');\n thisQ.cloneDragIfNeeded(drag);\n }\n });\n };\n\n /**\n * Determine what drag items need to be shown and\n * return coords of all drag items except any that are currently being dragged\n * based on contents of hidden inputs and whether drags are 'infinite' or how many\n * drags should be shown.\n *\n * @param {jQuery} inputNode\n * @returns {Point[]} coordinates of however many copies of the drag item should be shown.\n */\n DragDropMarkersQuestion.prototype.getCoords = function(inputNode) {\n var coords = [],\n val = $(inputNode).val();\n if (val !== '') {\n var coordsStrings = val.split(';');\n for (var i = 0; i < coordsStrings.length; i++) {\n coords[i] = this.convertToWindowXY(Shapes.Point.parse(coordsStrings[i]));\n }\n }\n return coords;\n };\n\n /**\n * Converts the relative x and y position coordinates into\n * absolute x and y position coordinates.\n *\n * @param {Point} point relative to the background image.\n * @returns {Point} point relative to the page.\n */\n DragDropMarkersQuestion.prototype.convertToWindowXY = function(point) {\n var bgImage = this.bgImage();\n // The +1 seems rather odd, but seems to give the best results in\n // the three main browsers at a range of zoom levels.\n // (Its due to the 1px border around the image, that shifts the\n // image pixels by 1 down and to the left.)\n return point.offset(bgImage.offset().left + 1, bgImage.offset().top + 1);\n };\n\n /**\n * Utility function converting window coordinates to relative to the\n * background image coordinates.\n *\n * @param {Point} point relative to the page.\n * @returns {Point} point relative to the background image.\n */\n DragDropMarkersQuestion.prototype.convertToBgImgXY = function(point) {\n var bgImage = this.bgImage();\n return point.offset(-bgImage.offset().left - 1, -bgImage.offset().top - 1);\n };\n\n /**\n * Is the point within the background image?\n *\n * @param {Point} point relative to the BG image.\n * @return {boolean} true it they are.\n */\n DragDropMarkersQuestion.prototype.coordsInBgImg = function(point) {\n var bgImage = this.bgImage();\n var bgPosition = bgImage.offset();\n\n return point.x >= bgPosition.left && point.x < bgPosition.left + bgImage.width()\n && point.y >= bgPosition.top && point.y < bgPosition.top + bgImage.height();\n };\n\n /**\n * Get the outer div for this question.\n * @returns {jQuery} containing that div.\n */\n DragDropMarkersQuestion.prototype.getRoot = function() {\n return $(document.getElementById(this.containerId));\n };\n\n /**\n * Get the img that is the background image.\n * @returns {jQuery} containing that img.\n */\n DragDropMarkersQuestion.prototype.bgImage = function() {\n return this.getRoot().find('img.dropbackground');\n };\n\n DragDropMarkersQuestion.prototype.handleDragStart = function(e) {\n var thisQ = this,\n dragged = $(e.target).closest('.marker');\n\n var info = dragDrop.prepare(e);\n if (!info.start) {\n return;\n }\n\n dragged.addClass('beingdragged').css('transform', '');\n\n var placed = !dragged.hasClass('unneeded');\n if (!placed) {\n var hiddenDrag = thisQ.getDragClone(dragged);\n if (hiddenDrag.length) {\n hiddenDrag.addClass('active');\n dragged.offset(hiddenDrag.offset());\n }\n }\n\n dragDrop.start(e, dragged, function() {\n void (1);\n }, function(x, y, dragged) {\n thisQ.dragEnd(dragged);\n });\n };\n\n /**\n * Functionality at the end of a drag drop.\n * @param {jQuery} dragged the marker that was dragged.\n */\n DragDropMarkersQuestion.prototype.dragEnd = function(dragged) {\n var placed = false,\n choiceNo = this.getChoiceNoFromElement(dragged),\n bgRatio = this.bgRatio(),\n dragXY;\n\n dragged.data('pagex', dragged.offset().left).data('pagey', dragged.offset().top);\n dragXY = new Shapes.Point(dragged.data('pagex'), dragged.data('pagey'));\n if (this.coordsInBgImg(dragXY)) {\n this.sendDragToDrop(dragged, true);\n placed = true;\n\n // It seems that the dragdrop sometimes leaves the drag\n // one pixel out of position. Put it in exactly the right place.\n var bgImgXY = this.convertToBgImgXY(dragXY);\n bgImgXY = new Shapes.Point(bgImgXY.x / bgRatio, bgImgXY.y / bgRatio);\n dragged.data('originX', bgImgXY.x).data('originY', bgImgXY.y);\n }\n\n if (!placed) {\n this.sendDragHome(dragged);\n this.removeDragIfNeeded(dragged);\n } else {\n this.cloneDragIfNeeded(dragged);\n }\n\n this.saveCoordsForChoice(choiceNo);\n };\n\n /**\n * Save the coordinates for a dropped item in the form field.\n * @param {Number} choiceNo which copy of the choice this was.\n */\n DragDropMarkersQuestion.prototype.saveCoordsForChoice = function(choiceNo) {\n var coords = [],\n items = this.getRoot().find('div.droparea span.marker.choice' + choiceNo),\n thiQ = this,\n bgRatio = this.bgRatio();\n\n if (items.length) {\n items.each(function() {\n var drag = $(this);\n if (!drag.hasClass('beingdragged')) {\n if (drag.data('scaleRatio') !== bgRatio) {\n // The scale ratio for the draggable item was changed. We need to update that.\n drag.data('pagex', drag.offset().left).data('pagey', drag.offset().top);\n }\n var dragXY = new Shapes.Point(drag.data('pagex'), drag.data('pagey'));\n if (thiQ.coordsInBgImg(dragXY)) {\n var bgImgXY = thiQ.convertToBgImgXY(dragXY);\n bgImgXY = new Shapes.Point(bgImgXY.x / bgRatio, bgImgXY.y / bgRatio);\n coords[coords.length] = bgImgXY;\n }\n }\n });\n }\n\n this.getRoot().find('input.choice' + choiceNo).val(coords.join(';'));\n };\n\n /**\n * Handle key down / press events on markers.\n * @param {KeyboardEvent} e\n */\n DragDropMarkersQuestion.prototype.handleKeyPress = function(e) {\n var drag = $(e.target).closest('.marker'),\n point = new Shapes.Point(drag.offset().left, drag.offset().top),\n choiceNo = this.getChoiceNoFromElement(drag);\n\n switch (e.keyCode) {\n case keys.arrowLeft:\n case 65: // A.\n point.x -= 1;\n break;\n case keys.arrowRight:\n case 68: // D.\n point.x += 1;\n break;\n case keys.arrowDown:\n case 83: // S.\n point.y += 1;\n break;\n case keys.arrowUp:\n case 87: // W.\n point.y -= 1;\n break;\n case keys.space:\n case keys.escape:\n point = null;\n break;\n default:\n return; // Ingore other keys.\n }\n e.preventDefault();\n\n if (point !== null) {\n point = this.constrainToBgImg(point);\n drag.offset({'left': point.x, 'top': point.y});\n drag.data('pagex', drag.offset().left).data('pagey', drag.offset().top);\n var dragXY = this.convertToBgImgXY(new Shapes.Point(drag.data('pagex'), drag.data('pagey')));\n drag.data('originX', dragXY.x / this.bgRatio()).data('originY', dragXY.y / this.bgRatio());\n if (this.coordsInBgImg(new Shapes.Point(drag.offset().left, drag.offset().top))) {\n if (drag.hasClass('unneeded')) {\n this.sendDragToDrop(drag, true);\n var hiddenDrag = this.getDragClone(drag);\n if (hiddenDrag.length) {\n hiddenDrag.addClass('active');\n }\n this.cloneDragIfNeeded(drag);\n }\n }\n } else {\n drag.css('left', '').css('top', '');\n drag.data('pagex', drag.offset().left).data('pagey', drag.offset().top);\n this.sendDragHome(drag);\n this.removeDragIfNeeded(drag);\n }\n drag.focus();\n this.saveCoordsForChoice(choiceNo);\n };\n\n /**\n * Makes sure the dragged item always exists within the background image area.\n *\n * @param {Point} windowxy\n * @returns {Point} coordinates\n */\n DragDropMarkersQuestion.prototype.constrainToBgImg = function(windowxy) {\n var bgImg = this.bgImage(),\n bgImgXY = this.convertToBgImgXY(windowxy);\n bgImgXY.x = Math.max(0, bgImgXY.x);\n bgImgXY.y = Math.max(0, bgImgXY.y);\n bgImgXY.x = Math.min(bgImg.width(), bgImgXY.x);\n bgImgXY.y = Math.min(bgImg.height(), bgImgXY.y);\n return this.convertToWindowXY(bgImgXY);\n };\n\n /**\n * Returns the choice number for a node.\n *\n * @param {Element|jQuery} node\n * @returns {Number}\n */\n DragDropMarkersQuestion.prototype.getChoiceNoFromElement = function(node) {\n return Number(this.getClassnameNumericSuffix(node, 'choice'));\n };\n\n /**\n * Returns the numeric part of a class with the given prefix.\n *\n * @param {Element|jQuery} node\n * @param {String} prefix\n * @returns {Number|null}\n */\n DragDropMarkersQuestion.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 * Handle when the window is resized.\n */\n DragDropMarkersQuestion.prototype.handleResize = function() {\n var thisQ = this,\n bgRatio = this.bgRatio();\n if (this.isPrinting) {\n bgRatio = 1;\n }\n\n this.getRoot().find('div.droparea .marker').not('.beingdragged').each(function(key, drag) {\n $(drag)\n .css('left', parseFloat($(drag).data('originX')) * parseFloat(bgRatio))\n .css('top', parseFloat($(drag).data('originY')) * parseFloat(bgRatio));\n thisQ.handleElementScale(drag, 'left top');\n });\n\n this.getRoot().find('div.droparea svg.dropzones')\n .width(this.bgImage().width())\n .height(this.bgImage().height());\n\n for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) {\n var dropZone = thisQ.visibleDropZones[dropZoneNo];\n var originCoords = dropZone.coords;\n var shape = thisQ.shapes[dropZoneNo];\n var shapeSVG = thisQ.shapeSVGs[dropZoneNo];\n shape.parse(originCoords, bgRatio);\n shape.updateSvg(shapeSVG);\n\n var handles = shape.getHandlePositions();\n var markerSpan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo);\n markerSpan\n .css('left', handles.moveHandle.x - (markerSpan.outerWidth() / 2) - 4)\n .css('top', handles.moveHandle.y - (markerSpan.outerHeight() / 2));\n thisQ.handleElementScale(markerSpan, 'center');\n }\n };\n\n /**\n * Clone the drag.\n */\n DragDropMarkersQuestion.prototype.cloneDrags = function() {\n var thisQ = this;\n this.getRoot().find('div.draghomes span.marker').each(function(index, draghome) {\n var drag = $(draghome);\n var placeHolder = drag.clone();\n placeHolder.removeClass();\n placeHolder.addClass('marker');\n placeHolder.addClass('choice' + thisQ.getChoiceNoFromElement(drag));\n placeHolder.addClass(thisQ.getDragNoClass(drag, false));\n placeHolder.addClass('dragplaceholder');\n drag.before(placeHolder);\n });\n };\n\n /**\n * Get the drag number of a drag.\n *\n * @param {jQuery} drag the drag.\n * @returns {Number} the drag number.\n */\n DragDropMarkersQuestion.prototype.getDragNo = function(drag) {\n return this.getClassnameNumericSuffix(drag, 'dragno');\n };\n\n /**\n * Get the drag number prefix of a drag.\n *\n * @param {jQuery} drag the drag.\n * @param {Boolean} includeSelector include the CSS selector prefix or not.\n * @return {String} Class name\n */\n DragDropMarkersQuestion.prototype.getDragNoClass = function(drag, includeSelector) {\n var className = 'dragno' + this.getDragNo(drag);\n if (this.isInfiniteDrag(drag)) {\n className = 'infinite';\n }\n\n if (includeSelector) {\n return '.' + className;\n }\n\n return className;\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 DragDropMarkersQuestion.prototype.getDragClone = function(drag) {\n return this.getRoot().find('.draghomes' + ' span.marker' +\n '.choice' + this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true) + '.dragplaceholder');\n };\n\n /**\n * Get the drop area element.\n * @returns {jQuery} droparea element.\n */\n DragDropMarkersQuestion.prototype.dropArea = function() {\n return this.getRoot().find('div.droparea');\n };\n\n /**\n * Animate a drag back to its home.\n *\n * @param {jQuery} drag the item being moved.\n */\n DragDropMarkersQuestion.prototype.sendDragHome = function(drag) {\n drag.removeClass('beingdragged')\n .addClass('unneeded')\n .css('top', '')\n .css('left', '')\n .css('transform', '');\n var placeHolder = this.getDragClone(drag);\n placeHolder.after(drag);\n placeHolder.removeClass('active');\n };\n\n /**\n * Animate a drag item into a given place.\n *\n * @param {jQuery} drag the item to place.\n * @param {boolean} isScaling Scaling or not.\n * @param {boolean} initialLoad Whether it is the initial load or not.\n */\n DragDropMarkersQuestion.prototype.sendDragToDrop = function(drag, isScaling, initialLoad = false) {\n var dropArea = this.dropArea(),\n bgRatio = this.bgRatio();\n drag.removeClass('beingdragged').removeClass('unneeded');\n var dragXY = this.convertToBgImgXY(new Shapes.Point(drag.data('pagex'), drag.data('pagey')));\n if (isScaling) {\n drag.data('originX', dragXY.x / bgRatio).data('originY', dragXY.y / bgRatio);\n drag.css('left', dragXY.x).css('top', dragXY.y);\n } else {\n drag.data('originX', dragXY.x).data('originY', dragXY.y);\n drag.css('left', dragXY.x * bgRatio).css('top', dragXY.y * bgRatio);\n }\n // We need to save the original scale ratio for each draggable item.\n if (!initialLoad) {\n // Only set the scale ratio for a current being-dragged item, not for the initial loading.\n drag.data('scaleRatio', bgRatio);\n }\n dropArea.append(drag);\n this.handleElementScale(drag, 'left top');\n };\n\n /**\n * Clone the drag at the draghome area if needed.\n *\n * @param {jQuery} drag the item to place.\n */\n DragDropMarkersQuestion.prototype.cloneDragIfNeeded = function(drag) {\n var inputNode = this.getInput(drag),\n noOfDrags = Number(this.getClassnameNumericSuffix(inputNode, 'noofdrags')),\n displayedDragsInDropArea = this.getRoot().find('div.droparea .marker.choice' +\n this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true)).length,\n displayedDragsInDragHomes = this.getRoot().find('div.draghomes .marker.choice' +\n this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true)).not('.dragplaceholder').length;\n\n if ((this.isInfiniteDrag(drag) ||\n !this.isInfiniteDrag(drag) && displayedDragsInDropArea < noOfDrags) && displayedDragsInDragHomes === 0) {\n var dragClone = drag.clone();\n dragClone.addClass('unneeded')\n .css('top', '')\n .css('left', '')\n .css('transform', '');\n this.getDragClone(drag)\n .removeClass('active')\n .after(dragClone);\n questionManager.addEventHandlersToMarker(dragClone);\n }\n };\n\n /**\n * Remove the clone drag at the draghome area if needed.\n *\n * @param {jQuery} drag the item to place.\n */\n DragDropMarkersQuestion.prototype.removeDragIfNeeded = function(drag) {\n var dragsInHome = this.getRoot().find('div.draghomes .marker.choice' +\n this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true)).not('.dragplaceholder');\n var displayedDrags = dragsInHome.length;\n while (displayedDrags > 1) {\n dragsInHome.first().remove();\n displayedDrags--;\n }\n };\n\n /**\n * Get the input belong to drag.\n *\n * @param {jQuery} drag the item to place.\n * @returns {jQuery} input element.\n */\n DragDropMarkersQuestion.prototype.getInput = function(drag) {\n var choiceNo = this.getChoiceNoFromElement(drag);\n return this.getRoot().find('input.choices.choice' + choiceNo);\n };\n\n /**\n * Return the background ratio.\n *\n * @returns {number} Background ratio.\n */\n DragDropMarkersQuestion.prototype.bgRatio = function() {\n var bgImg = this.bgImage();\n var bgImgNaturalWidth = bgImg.get(0).naturalWidth;\n var bgImgClientWidth = bgImg.width();\n\n return bgImgClientWidth / bgImgNaturalWidth;\n };\n\n /**\n * Scale the drag if needed.\n *\n * @param {jQuery} element the item to place.\n * @param {String} type scaling type\n */\n DragDropMarkersQuestion.prototype.handleElementScale = function(element, type) {\n var bgRatio = parseFloat(this.bgRatio());\n if (this.isPrinting) {\n bgRatio = 1;\n }\n $(element).css({\n '-webkit-transform': 'scale(' + bgRatio + ')',\n '-moz-transform': 'scale(' + bgRatio + ')',\n '-ms-transform': 'scale(' + bgRatio + ')',\n '-o-transform': 'scale(' + bgRatio + ')',\n 'transform': 'scale(' + bgRatio + ')',\n 'transform-origin': type\n });\n };\n\n /**\n * Check if the given drag is in infinite mode or not.\n *\n * @param {jQuery} drag The drag item need to check.\n */\n DragDropMarkersQuestion.prototype.isInfiniteDrag = function(drag) {\n return drag.hasClass('infinite');\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 /**\n * {boolean} ensures that the event handlers are only initialised once per page.\n */\n eventHandlersInitialised: false,\n\n /**\n * {Object} ensures that the marker event handlers are only initialised once per question,\n * indexed by containerId (id on the .que div).\n */\n markerEventHandlersInitialised: {},\n\n /**\n * {boolean} is printing or not.\n */\n isPrinting: false,\n\n /**\n * {boolean} is keyboard navigation.\n */\n isKeyboardNavigation: false,\n\n /**\n * {Object} all the questions on this page, indexed by containerId (id on the .que div).\n */\n questions: {}, // An object containing all the information about each question on the page.\n\n /**\n * Initialise one question.\n *\n * @param {String} containerId the id of the div.que that contains this question.\n * @param {boolean} readOnly whether the question is read-only.\n * @param {Object[]} visibleDropZones data on any drop zones to draw as part of the feedback.\n */\n init: function(containerId, readOnly, visibleDropZones) {\n questionManager.questions[containerId] =\n new DragDropMarkersQuestion(containerId, readOnly, visibleDropZones);\n if (!questionManager.eventHandlersInitialised) {\n questionManager.setupEventHandlers();\n questionManager.eventHandlersInitialised = true;\n }\n if (!questionManager.markerEventHandlersInitialised.hasOwnProperty(containerId)) {\n questionManager.markerEventHandlersInitialised[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('ddmarker') &&\n !questionContainer.classList.contains('qtype_ddmarker-readonly')) {\n // TODO: Convert all the jQuery selectors and events to native Javascript.\n questionManager.addEventHandlersToMarker($(questionContainer).find('div.draghomes .marker'));\n questionManager.addEventHandlersToMarker($(questionContainer).find('div.droparea .marker'));\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 $(window).on('resize', function() {\n questionManager.handleWindowResize(false);\n });\n window.addEventListener('beforeprint', function() {\n questionManager.isPrinting = true;\n questionManager.handleWindowResize(questionManager.isPrinting);\n });\n window.addEventListener('afterprint', function() {\n questionManager.isPrinting = false;\n questionManager.handleWindowResize(questionManager.isPrinting);\n });\n setTimeout(function() {\n questionManager.fixLayoutIfThingsMoved();\n }, 100);\n },\n\n /**\n * Binding the event again for newly created element.\n *\n * @param {jQuery} element Element to bind the event\n */\n addEventHandlersToMarker: function(element) {\n element\n .on('mousedown touchstart', questionManager.handleDragStart)\n .on('keydown keypress', questionManager.handleKeyPress)\n .focusin(function(e) {\n questionManager.handleKeyboardFocus(e, true);\n })\n .focusout(function(e) {\n questionManager.handleKeyboardFocus(e, false);\n });\n },\n\n /**\n * Handle mouse down / touch start events on markers.\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 events on markers.\n * @param {Event} e\n */\n handleKeyPress: function(e) {\n var question = questionManager.getQuestionForEvent(e);\n if (question) {\n question.handleKeyPress(e);\n }\n },\n\n /**\n * Handle when the window is resized.\n * @param {boolean} isPrinting\n */\n handleWindowResize: function(isPrinting) {\n for (var containerId in questionManager.questions) {\n if (questionManager.questions.hasOwnProperty(containerId)) {\n questionManager.questions[containerId].isPrinting = isPrinting;\n questionManager.questions[containerId].handleResize();\n }\n }\n },\n\n /**\n * Handle focus lost events on markers.\n * @param {Event} e\n * @param {boolean} isNavigating\n */\n handleKeyboardFocus: function(e, isNavigating) {\n questionManager.isKeyboardNavigation = isNavigating;\n },\n\n /**\n * Sometimes, despite our best efforts, things change in a way that cannot\n * be specifically caught (e.g. dock expanding or collapsing in Boost).\n * Therefore, we need to periodically check everything is in the right position.\n */\n fixLayoutIfThingsMoved: function() {\n if (!questionManager.isKeyboardNavigation) {\n this.handleWindowResize(questionManager.isPrinting);\n }\n // We use setTimeout after finishing work, rather than setInterval,\n // in case positioning things is slow. We want 100 ms gap\n // between executions, not what setInterval does.\n setTimeout(function() {\n questionManager.fixLayoutIfThingsMoved(questionManager.isPrinting);\n }, 100);\n },\n\n /**\n * Given an event, work out which question it effects.\n * @param {Event} e the event.\n * @returns {DragDropMarkersQuestion|undefined} The question, or undefined.\n */\n getQuestionForEvent: function(e) {\n var containerId = $(e.currentTarget).closest('.que.ddmarker').attr('id');\n return questionManager.questions[containerId];\n }\n };\n\n /**\n * @alias module:qtype_ddmarker/question\n */\n return {\n /**\n * Initialise one drag-drop markers question.\n *\n * @param {String} containerId id of the outer div for this question.\n * @param {String} bgImgUrl the URL of the background image.\n * @param {boolean} readOnly whether the question is being displayed read-only.\n * @param {String[]} visibleDropZones the geometry of any drop-zones to show.\n */\n init: questionManager.init\n };\n});\n"],"names":["define","$","dragDrop","Shapes","keys","DragDropMarkersQuestion","containerId","readOnly","visibleDropZones","shapes","shapeSVGs","isPrinting","getRoot","addClass","this","cloneDrags","repositionDrags","drawDropzones","prototype","length","bgImage","find","html","outerWidth","outerHeight","svg","nextColourIndex","dropZoneNo","colourClass","addDropzone","existingmarkertext","dropZone","shape","make","bgRatio","parse","coords","markertext","remove","classnames","append","markerspan","handles","getHandlePositions","positionLeft","moveHandle","x","positionTop","y","css","data","position","left","top","handleElementScale","shapeSVG","makeSvg","setAttribute","root","thisQ","not","each","key","item","input","choiceNo","getChoiceNoFromElement","getCoords","drag","i","dragInDrop","clone","sendDragToDrop","getDragClone","cloneDragIfNeeded","inputNode","val","coordsStrings","split","convertToWindowXY","Point","point","offset","convertToBgImgXY","coordsInBgImg","bgPosition","width","height","document","getElementById","handleDragStart","e","dragged","target","closest","prepare","start","hasClass","hiddenDrag","dragEnd","dragXY","placed","bgImgXY","sendDragHome","removeDragIfNeeded","saveCoordsForChoice","items","thiQ","join","handleKeyPress","keyCode","arrowLeft","arrowRight","arrowDown","arrowUp","space","escape","preventDefault","constrainToBgImg","focus","windowxy","bgImg","Math","max","min","node","Number","getClassnameNumericSuffix","prefix","classes","attr","undefined","classesarr","index","RegExp","test","match","exec","handleResize","parseFloat","originCoords","updateSvg","markerSpan","draghome","placeHolder","removeClass","getDragNoClass","before","getDragNo","includeSelector","className","isInfiniteDrag","dropArea","after","isScaling","initialLoad","getInput","noOfDrags","displayedDragsInDropArea","displayedDragsInDragHomes","dragClone","questionManager","addEventHandlersToMarker","dragsInHome","displayedDrags","first","bgImgNaturalWidth","get","naturalWidth","element","type","eventHandlersInitialised","markerEventHandlersInitialised","isKeyboardNavigation","questions","init","setupEventHandlers","hasOwnProperty","questionContainer","classList","contains","window","on","handleWindowResize","addEventListener","setTimeout","fixLayoutIfThingsMoved","focusin","handleKeyboardFocus","focusout","question","getQuestionForEvent","isNavigating","currentTarget"],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"question.min.js","sources":["../src/question.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 * Question class for drag and drop marker question type, used to support the question and preview pages.\n *\n * @module qtype_ddmarker/question\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine([\n 'jquery',\n 'core/dragdrop',\n 'qtype_ddmarker/shapes',\n 'core/key_codes',\n 'core_form/changechecker'\n], function(\n $,\n dragDrop,\n Shapes,\n keys,\n FormChangeChecker\n) {\n\n \"use strict\";\n\n /**\n * Object to handle one drag-drop markers 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 * @param {Object[]} visibleDropZones the geometry of any drop-zones to show.\n * Objects have fields shape, coords and markertext.\n * @constructor\n */\n function DragDropMarkersQuestion(containerId, readOnly, visibleDropZones) {\n var thisQ = this;\n this.containerId = containerId;\n this.visibleDropZones = visibleDropZones;\n this.shapes = [];\n this.shapeSVGs = [];\n this.isPrinting = false;\n this.questionAnswer = {};\n if (readOnly) {\n this.getRoot().addClass('qtype_ddmarker-readonly');\n }\n thisQ.cloneDrags();\n thisQ.repositionDrags();\n thisQ.drawDropzones();\n }\n\n /**\n * Draws the svg shapes of any drop zones that should be visible for feedback purposes.\n */\n DragDropMarkersQuestion.prototype.drawDropzones = function() {\n if (this.visibleDropZones.length > 0) {\n var bgImage = this.bgImage();\n\n this.getRoot().find('div.dropzones').html('');\n var svg = this.getRoot().find('svg.dropzones');\n\n var nextColourIndex = 0;\n for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) {\n var colourClass = 'color' + nextColourIndex;\n nextColourIndex = (nextColourIndex + 1) % 8;\n this.addDropzone(svg, dropZoneNo, colourClass);\n }\n }\n };\n\n /**\n * Adds a dropzone shape with colour, coords and link provided to the array of shapes.\n *\n * @param {jQuery} svg the SVG image to which to add this drop zone.\n * @param {int} dropZoneNo which drop-zone to add.\n * @param {string} colourClass class name\n */\n DragDropMarkersQuestion.prototype.addDropzone = function(svg, dropZoneNo, colourClass) {\n var dropZone = this.visibleDropZones[dropZoneNo],\n shape = Shapes.make(dropZone.shape, ''),\n existingmarkertext,\n bgRatio = this.bgRatio();\n if (!shape.parse(dropZone.coords, bgRatio)) {\n return;\n }\n\n existingmarkertext = this.getRoot().find('div.markertexts span.markertext' + dropZoneNo);\n if (existingmarkertext.length) {\n if (dropZone.markertext !== '') {\n existingmarkertext.html(dropZone.markertext);\n } else {\n existingmarkertext.remove();\n }\n } else if (dropZone.markertext !== '') {\n var classnames = 'markertext markertext' + dropZoneNo;\n this.getRoot().find('div.markertexts').append('' +\n dropZone.markertext + '');\n var markerspan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo);\n if (markerspan.length) {\n var handles = shape.getHandlePositions();\n var positionLeft = handles.moveHandle.x - (markerspan.outerWidth() / 2) - 4;\n var positionTop = handles.moveHandle.y - (markerspan.outerHeight() / 2);\n markerspan\n .css('left', positionLeft)\n .css('top', positionTop);\n markerspan\n .data('originX', markerspan.position().left / bgRatio)\n .data('originY', markerspan.position().top / bgRatio);\n this.handleElementScale(markerspan, 'center');\n }\n }\n\n var shapeSVG = shape.makeSvg(svg[0]);\n shapeSVG.setAttribute('class', 'dropzone ' + colourClass);\n\n this.shapes[this.shapes.length] = shape;\n this.shapeSVGs[this.shapeSVGs.length] = shapeSVG;\n };\n\n /**\n * Draws the drag items on the page (and drop zones if required).\n * The idea is to re-draw all the drags and drops whenever there is a change\n * like a widow resize or an item dropped in place.\n */\n DragDropMarkersQuestion.prototype.repositionDrags = function() {\n var root = this.getRoot(),\n thisQ = this;\n\n root.find('div.draghomes .marker').not('.dragplaceholder').each(function(key, item) {\n $(item).addClass('unneeded');\n });\n\n root.find('input.choices').each(function(key, input) {\n var choiceNo = thisQ.getChoiceNoFromElement(input),\n coords = thisQ.getCoords(input);\n if (coords.length) {\n var drag = thisQ.getRoot().find('.draghomes' + ' span.marker' + '.choice' + choiceNo).not('.dragplaceholder');\n drag.remove();\n for (var i = 0; i < coords.length; i++) {\n var dragInDrop = drag.clone();\n dragInDrop.data('pagex', coords[i].x).data('pagey', coords[i].y);\n // We always save the coordinates in the 1:1 ratio.\n // So we need to set the scale ratio to 1 for the initial load.\n dragInDrop.data('scaleRatio', 1);\n thisQ.sendDragToDrop(dragInDrop, false, true);\n }\n thisQ.getDragClone(drag).addClass('active');\n thisQ.cloneDragIfNeeded(drag);\n }\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 DragDropMarkersQuestion.prototype.getQuestionAnsweredValues = function() {\n let result = {};\n this.getRoot().find('input.choices').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 DragDropMarkersQuestion.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 * Determine what drag items need to be shown and\n * return coords of all drag items except any that are currently being dragged\n * based on contents of hidden inputs and whether drags are 'infinite' or how many\n * drags should be shown.\n *\n * @param {jQuery} inputNode\n * @returns {Point[]} coordinates of however many copies of the drag item should be shown.\n */\n DragDropMarkersQuestion.prototype.getCoords = function(inputNode) {\n var coords = [],\n val = $(inputNode).val();\n if (val !== '') {\n var coordsStrings = val.split(';');\n for (var i = 0; i < coordsStrings.length; i++) {\n coords[i] = this.convertToWindowXY(Shapes.Point.parse(coordsStrings[i]));\n }\n }\n return coords;\n };\n\n /**\n * Converts the relative x and y position coordinates into\n * absolute x and y position coordinates.\n *\n * @param {Point} point relative to the background image.\n * @returns {Point} point relative to the page.\n */\n DragDropMarkersQuestion.prototype.convertToWindowXY = function(point) {\n var bgImage = this.bgImage();\n // The +1 seems rather odd, but seems to give the best results in\n // the three main browsers at a range of zoom levels.\n // (Its due to the 1px border around the image, that shifts the\n // image pixels by 1 down and to the left.)\n return point.offset(bgImage.offset().left + 1, bgImage.offset().top + 1);\n };\n\n /**\n * Utility function converting window coordinates to relative to the\n * background image coordinates.\n *\n * @param {Point} point relative to the page.\n * @returns {Point} point relative to the background image.\n */\n DragDropMarkersQuestion.prototype.convertToBgImgXY = function(point) {\n var bgImage = this.bgImage();\n return point.offset(-bgImage.offset().left - 1, -bgImage.offset().top - 1);\n };\n\n /**\n * Is the point within the background image?\n *\n * @param {Point} point relative to the BG image.\n * @return {boolean} true it they are.\n */\n DragDropMarkersQuestion.prototype.coordsInBgImg = function(point) {\n var bgImage = this.bgImage();\n var bgPosition = bgImage.offset();\n\n return point.x >= bgPosition.left && point.x < bgPosition.left + bgImage.width()\n && point.y >= bgPosition.top && point.y < bgPosition.top + bgImage.height();\n };\n\n /**\n * Get the outer div for this question.\n * @returns {jQuery} containing that div.\n */\n DragDropMarkersQuestion.prototype.getRoot = function() {\n return $(document.getElementById(this.containerId));\n };\n\n /**\n * Get the img that is the background image.\n * @returns {jQuery} containing that img.\n */\n DragDropMarkersQuestion.prototype.bgImage = function() {\n return this.getRoot().find('img.dropbackground');\n };\n\n DragDropMarkersQuestion.prototype.handleDragStart = function(e) {\n var thisQ = this,\n dragged = $(e.target).closest('.marker');\n\n var info = dragDrop.prepare(e);\n if (!info.start) {\n return;\n }\n\n dragged.addClass('beingdragged').css('transform', '');\n\n var placed = !dragged.hasClass('unneeded');\n if (!placed) {\n var hiddenDrag = thisQ.getDragClone(dragged);\n if (hiddenDrag.length) {\n hiddenDrag.addClass('active');\n dragged.offset(hiddenDrag.offset());\n }\n }\n\n dragDrop.start(e, dragged, function() {\n void (1);\n }, function(x, y, dragged) {\n thisQ.dragEnd(dragged);\n });\n };\n\n /**\n * Functionality at the end of a drag drop.\n * @param {jQuery} dragged the marker that was dragged.\n */\n DragDropMarkersQuestion.prototype.dragEnd = function(dragged) {\n var placed = false,\n choiceNo = this.getChoiceNoFromElement(dragged),\n bgRatio = this.bgRatio(),\n dragXY;\n\n dragged.data('pagex', dragged.offset().left).data('pagey', dragged.offset().top);\n dragXY = new Shapes.Point(dragged.data('pagex'), dragged.data('pagey'));\n if (this.coordsInBgImg(dragXY)) {\n this.sendDragToDrop(dragged, true);\n placed = true;\n\n // It seems that the dragdrop sometimes leaves the drag\n // one pixel out of position. Put it in exactly the right place.\n var bgImgXY = this.convertToBgImgXY(dragXY);\n bgImgXY = new Shapes.Point(bgImgXY.x / bgRatio, bgImgXY.y / bgRatio);\n dragged.data('originX', bgImgXY.x).data('originY', bgImgXY.y);\n }\n\n if (!placed) {\n this.sendDragHome(dragged);\n this.removeDragIfNeeded(dragged);\n } else {\n this.cloneDragIfNeeded(dragged);\n }\n\n this.saveCoordsForChoice(choiceNo);\n };\n\n /**\n * Save the coordinates for a dropped item in the form field.\n * @param {Number} choiceNo which copy of the choice this was.\n */\n DragDropMarkersQuestion.prototype.saveCoordsForChoice = function(choiceNo) {\n var coords = [],\n items = this.getRoot().find('div.droparea span.marker.choice' + choiceNo),\n thiQ = this,\n bgRatio = this.bgRatio();\n\n if (items.length) {\n items.each(function() {\n var drag = $(this);\n if (!drag.hasClass('beingdragged')) {\n if (drag.data('scaleRatio') !== bgRatio) {\n // The scale ratio for the draggable item was changed. We need to update that.\n drag.data('pagex', drag.offset().left).data('pagey', drag.offset().top);\n }\n var dragXY = new Shapes.Point(drag.data('pagex'), drag.data('pagey'));\n if (thiQ.coordsInBgImg(dragXY)) {\n var bgImgXY = thiQ.convertToBgImgXY(dragXY);\n bgImgXY = new Shapes.Point(bgImgXY.x / bgRatio, bgImgXY.y / bgRatio);\n coords[coords.length] = bgImgXY;\n }\n }\n });\n }\n\n this.getRoot().find('input.choice' + choiceNo).val(coords.join(';'));\n if (this.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 this.questionAnswer = this.getQuestionAnsweredValues();\n }\n };\n\n /**\n * Handle key down / press events on markers.\n * @param {KeyboardEvent} e\n */\n DragDropMarkersQuestion.prototype.handleKeyPress = function(e) {\n var drag = $(e.target).closest('.marker'),\n point = new Shapes.Point(drag.offset().left, drag.offset().top),\n choiceNo = this.getChoiceNoFromElement(drag);\n\n switch (e.keyCode) {\n case keys.arrowLeft:\n case 65: // A.\n point.x -= 1;\n break;\n case keys.arrowRight:\n case 68: // D.\n point.x += 1;\n break;\n case keys.arrowDown:\n case 83: // S.\n point.y += 1;\n break;\n case keys.arrowUp:\n case 87: // W.\n point.y -= 1;\n break;\n case keys.space:\n case keys.escape:\n point = null;\n break;\n default:\n return; // Ingore other keys.\n }\n e.preventDefault();\n\n if (point !== null) {\n point = this.constrainToBgImg(point);\n drag.offset({'left': point.x, 'top': point.y});\n drag.data('pagex', drag.offset().left).data('pagey', drag.offset().top);\n var dragXY = this.convertToBgImgXY(new Shapes.Point(drag.data('pagex'), drag.data('pagey')));\n drag.data('originX', dragXY.x / this.bgRatio()).data('originY', dragXY.y / this.bgRatio());\n if (this.coordsInBgImg(new Shapes.Point(drag.offset().left, drag.offset().top))) {\n if (drag.hasClass('unneeded')) {\n this.sendDragToDrop(drag, true);\n var hiddenDrag = this.getDragClone(drag);\n if (hiddenDrag.length) {\n hiddenDrag.addClass('active');\n }\n this.cloneDragIfNeeded(drag);\n }\n }\n } else {\n drag.css('left', '').css('top', '');\n drag.data('pagex', drag.offset().left).data('pagey', drag.offset().top);\n this.sendDragHome(drag);\n this.removeDragIfNeeded(drag);\n }\n drag.focus();\n this.saveCoordsForChoice(choiceNo);\n };\n\n /**\n * Makes sure the dragged item always exists within the background image area.\n *\n * @param {Point} windowxy\n * @returns {Point} coordinates\n */\n DragDropMarkersQuestion.prototype.constrainToBgImg = function(windowxy) {\n var bgImg = this.bgImage(),\n bgImgXY = this.convertToBgImgXY(windowxy);\n bgImgXY.x = Math.max(0, bgImgXY.x);\n bgImgXY.y = Math.max(0, bgImgXY.y);\n bgImgXY.x = Math.min(bgImg.width(), bgImgXY.x);\n bgImgXY.y = Math.min(bgImg.height(), bgImgXY.y);\n return this.convertToWindowXY(bgImgXY);\n };\n\n /**\n * Returns the choice number for a node.\n *\n * @param {Element|jQuery} node\n * @returns {Number}\n */\n DragDropMarkersQuestion.prototype.getChoiceNoFromElement = function(node) {\n return Number(this.getClassnameNumericSuffix(node, 'choice'));\n };\n\n /**\n * Returns the numeric part of a class with the given prefix.\n *\n * @param {Element|jQuery} node\n * @param {String} prefix\n * @returns {Number|null}\n */\n DragDropMarkersQuestion.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 * Handle when the window is resized.\n */\n DragDropMarkersQuestion.prototype.handleResize = function() {\n var thisQ = this,\n bgRatio = this.bgRatio();\n if (this.isPrinting) {\n bgRatio = 1;\n }\n\n this.getRoot().find('div.droparea .marker').not('.beingdragged').each(function(key, drag) {\n $(drag)\n .css('left', parseFloat($(drag).data('originX')) * parseFloat(bgRatio))\n .css('top', parseFloat($(drag).data('originY')) * parseFloat(bgRatio));\n thisQ.handleElementScale(drag, 'left top');\n });\n\n this.getRoot().find('div.droparea svg.dropzones')\n .width(this.bgImage().width())\n .height(this.bgImage().height());\n\n for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) {\n var dropZone = thisQ.visibleDropZones[dropZoneNo];\n var originCoords = dropZone.coords;\n var shape = thisQ.shapes[dropZoneNo];\n var shapeSVG = thisQ.shapeSVGs[dropZoneNo];\n shape.parse(originCoords, bgRatio);\n shape.updateSvg(shapeSVG);\n\n var handles = shape.getHandlePositions();\n var markerSpan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo);\n markerSpan\n .css('left', handles.moveHandle.x - (markerSpan.outerWidth() / 2) - 4)\n .css('top', handles.moveHandle.y - (markerSpan.outerHeight() / 2));\n thisQ.handleElementScale(markerSpan, 'center');\n }\n };\n\n /**\n * Clone the drag.\n */\n DragDropMarkersQuestion.prototype.cloneDrags = function() {\n var thisQ = this;\n this.getRoot().find('div.draghomes span.marker').each(function(index, draghome) {\n var drag = $(draghome);\n var placeHolder = drag.clone();\n placeHolder.removeClass();\n placeHolder.addClass('marker');\n placeHolder.addClass('choice' + thisQ.getChoiceNoFromElement(drag));\n placeHolder.addClass(thisQ.getDragNoClass(drag, false));\n placeHolder.addClass('dragplaceholder');\n drag.before(placeHolder);\n });\n };\n\n /**\n * Get the drag number of a drag.\n *\n * @param {jQuery} drag the drag.\n * @returns {Number} the drag number.\n */\n DragDropMarkersQuestion.prototype.getDragNo = function(drag) {\n return this.getClassnameNumericSuffix(drag, 'dragno');\n };\n\n /**\n * Get the drag number prefix of a drag.\n *\n * @param {jQuery} drag the drag.\n * @param {Boolean} includeSelector include the CSS selector prefix or not.\n * @return {String} Class name\n */\n DragDropMarkersQuestion.prototype.getDragNoClass = function(drag, includeSelector) {\n var className = 'dragno' + this.getDragNo(drag);\n if (this.isInfiniteDrag(drag)) {\n className = 'infinite';\n }\n\n if (includeSelector) {\n return '.' + className;\n }\n\n return className;\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 DragDropMarkersQuestion.prototype.getDragClone = function(drag) {\n return this.getRoot().find('.draghomes' + ' span.marker' +\n '.choice' + this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true) + '.dragplaceholder');\n };\n\n /**\n * Get the drop area element.\n * @returns {jQuery} droparea element.\n */\n DragDropMarkersQuestion.prototype.dropArea = function() {\n return this.getRoot().find('div.droparea');\n };\n\n /**\n * Animate a drag back to its home.\n *\n * @param {jQuery} drag the item being moved.\n */\n DragDropMarkersQuestion.prototype.sendDragHome = function(drag) {\n drag.removeClass('beingdragged')\n .addClass('unneeded')\n .css('top', '')\n .css('left', '')\n .css('transform', '');\n var placeHolder = this.getDragClone(drag);\n placeHolder.after(drag);\n placeHolder.removeClass('active');\n };\n\n /**\n * Animate a drag item into a given place.\n *\n * @param {jQuery} drag the item to place.\n * @param {boolean} isScaling Scaling or not.\n * @param {boolean} initialLoad Whether it is the initial load or not.\n */\n DragDropMarkersQuestion.prototype.sendDragToDrop = function(drag, isScaling, initialLoad = false) {\n var dropArea = this.dropArea(),\n bgRatio = this.bgRatio();\n drag.removeClass('beingdragged').removeClass('unneeded');\n var dragXY = this.convertToBgImgXY(new Shapes.Point(drag.data('pagex'), drag.data('pagey')));\n if (isScaling) {\n drag.data('originX', dragXY.x / bgRatio).data('originY', dragXY.y / bgRatio);\n drag.css('left', dragXY.x).css('top', dragXY.y);\n } else {\n drag.data('originX', dragXY.x).data('originY', dragXY.y);\n drag.css('left', dragXY.x * bgRatio).css('top', dragXY.y * bgRatio);\n }\n // We need to save the original scale ratio for each draggable item.\n if (!initialLoad) {\n // Only set the scale ratio for a current being-dragged item, not for the initial loading.\n drag.data('scaleRatio', bgRatio);\n }\n dropArea.append(drag);\n this.handleElementScale(drag, 'left top');\n };\n\n /**\n * Clone the drag at the draghome area if needed.\n *\n * @param {jQuery} drag the item to place.\n */\n DragDropMarkersQuestion.prototype.cloneDragIfNeeded = function(drag) {\n var inputNode = this.getInput(drag),\n noOfDrags = Number(this.getClassnameNumericSuffix(inputNode, 'noofdrags')),\n displayedDragsInDropArea = this.getRoot().find('div.droparea .marker.choice' +\n this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true)).length,\n displayedDragsInDragHomes = this.getRoot().find('div.draghomes .marker.choice' +\n this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true)).not('.dragplaceholder').length;\n\n if ((this.isInfiniteDrag(drag) ||\n !this.isInfiniteDrag(drag) && displayedDragsInDropArea < noOfDrags) && displayedDragsInDragHomes === 0) {\n var dragClone = drag.clone();\n dragClone.addClass('unneeded')\n .css('top', '')\n .css('left', '')\n .css('transform', '');\n this.getDragClone(drag)\n .removeClass('active')\n .after(dragClone);\n questionManager.addEventHandlersToMarker(dragClone);\n }\n };\n\n /**\n * Remove the clone drag at the draghome area if needed.\n *\n * @param {jQuery} drag the item to place.\n */\n DragDropMarkersQuestion.prototype.removeDragIfNeeded = function(drag) {\n var dragsInHome = this.getRoot().find('div.draghomes .marker.choice' +\n this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true)).not('.dragplaceholder');\n var displayedDrags = dragsInHome.length;\n while (displayedDrags > 1) {\n dragsInHome.first().remove();\n displayedDrags--;\n }\n };\n\n /**\n * Get the input belong to drag.\n *\n * @param {jQuery} drag the item to place.\n * @returns {jQuery} input element.\n */\n DragDropMarkersQuestion.prototype.getInput = function(drag) {\n var choiceNo = this.getChoiceNoFromElement(drag);\n return this.getRoot().find('input.choices.choice' + choiceNo);\n };\n\n /**\n * Return the background ratio.\n *\n * @returns {number} Background ratio.\n */\n DragDropMarkersQuestion.prototype.bgRatio = function() {\n var bgImg = this.bgImage();\n var bgImgNaturalWidth = bgImg.get(0).naturalWidth;\n var bgImgClientWidth = bgImg.width();\n\n return bgImgClientWidth / bgImgNaturalWidth;\n };\n\n /**\n * Scale the drag if needed.\n *\n * @param {jQuery} element the item to place.\n * @param {String} type scaling type\n */\n DragDropMarkersQuestion.prototype.handleElementScale = function(element, type) {\n var bgRatio = parseFloat(this.bgRatio());\n if (this.isPrinting) {\n bgRatio = 1;\n }\n $(element).css({\n '-webkit-transform': 'scale(' + bgRatio + ')',\n '-moz-transform': 'scale(' + bgRatio + ')',\n '-ms-transform': 'scale(' + bgRatio + ')',\n '-o-transform': 'scale(' + bgRatio + ')',\n 'transform': 'scale(' + bgRatio + ')',\n 'transform-origin': type\n });\n };\n\n /**\n * Check if the given drag is in infinite mode or not.\n *\n * @param {jQuery} drag The drag item need to check.\n */\n DragDropMarkersQuestion.prototype.isInfiniteDrag = function(drag) {\n return drag.hasClass('infinite');\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 /**\n * {boolean} ensures that the event handlers are only initialised once per page.\n */\n eventHandlersInitialised: false,\n\n /**\n * {Object} ensures that the marker event handlers are only initialised once per question,\n * indexed by containerId (id on the .que div).\n */\n markerEventHandlersInitialised: {},\n\n /**\n * {boolean} is printing or not.\n */\n isPrinting: false,\n\n /**\n * {boolean} is keyboard navigation.\n */\n isKeyboardNavigation: false,\n\n /**\n * {Object} all the questions on this page, indexed by containerId (id on the .que div).\n */\n questions: {}, // An object containing all the information about each question on the page.\n\n /**\n * Initialise one question.\n *\n * @param {String} containerId the id of the div.que that contains this question.\n * @param {boolean} readOnly whether the question is read-only.\n * @param {Object[]} visibleDropZones data on any drop zones to draw as part of the feedback.\n */\n init: function(containerId, readOnly, visibleDropZones) {\n questionManager.questions[containerId] =\n new DragDropMarkersQuestion(containerId, readOnly, visibleDropZones);\n if (!questionManager.eventHandlersInitialised) {\n questionManager.setupEventHandlers();\n questionManager.eventHandlersInitialised = true;\n }\n if (!questionManager.markerEventHandlersInitialised.hasOwnProperty(containerId)) {\n questionManager.markerEventHandlersInitialised[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('ddmarker') &&\n !questionContainer.classList.contains('qtype_ddmarker-readonly')) {\n // TODO: Convert all the jQuery selectors and events to native Javascript.\n questionManager.addEventHandlersToMarker($(questionContainer).find('div.draghomes .marker'));\n questionManager.addEventHandlersToMarker($(questionContainer).find('div.droparea .marker'));\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 $(window).on('resize', function() {\n questionManager.handleWindowResize(false);\n });\n window.addEventListener('beforeprint', function() {\n questionManager.isPrinting = true;\n questionManager.handleWindowResize(questionManager.isPrinting);\n });\n window.addEventListener('afterprint', function() {\n questionManager.isPrinting = false;\n questionManager.handleWindowResize(questionManager.isPrinting);\n });\n setTimeout(function() {\n questionManager.fixLayoutIfThingsMoved();\n }, 100);\n },\n\n /**\n * Binding the event again for newly created element.\n *\n * @param {jQuery} element Element to bind the event\n */\n addEventHandlersToMarker: function(element) {\n element\n .on('mousedown touchstart', questionManager.handleDragStart)\n .on('keydown keypress', questionManager.handleKeyPress)\n .focusin(function(e) {\n questionManager.handleKeyboardFocus(e, true);\n })\n .focusout(function(e) {\n questionManager.handleKeyboardFocus(e, false);\n });\n },\n\n /**\n * Handle mouse down / touch start events on markers.\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 events on markers.\n * @param {Event} e\n */\n handleKeyPress: function(e) {\n var question = questionManager.getQuestionForEvent(e);\n if (question) {\n question.handleKeyPress(e);\n }\n },\n\n /**\n * Handle when the window is resized.\n * @param {boolean} isPrinting\n */\n handleWindowResize: function(isPrinting) {\n for (var containerId in questionManager.questions) {\n if (questionManager.questions.hasOwnProperty(containerId)) {\n questionManager.questions[containerId].isPrinting = isPrinting;\n questionManager.questions[containerId].handleResize();\n }\n }\n },\n\n /**\n * Handle focus lost events on markers.\n * @param {Event} e\n * @param {boolean} isNavigating\n */\n handleKeyboardFocus: function(e, isNavigating) {\n questionManager.isKeyboardNavigation = isNavigating;\n },\n\n /**\n * Sometimes, despite our best efforts, things change in a way that cannot\n * be specifically caught (e.g. dock expanding or collapsing in Boost).\n * Therefore, we need to periodically check everything is in the right position.\n */\n fixLayoutIfThingsMoved: function() {\n if (!questionManager.isKeyboardNavigation) {\n this.handleWindowResize(questionManager.isPrinting);\n }\n // We use setTimeout after finishing work, rather than setInterval,\n // in case positioning things is slow. We want 100 ms gap\n // between executions, not what setInterval does.\n setTimeout(function() {\n questionManager.fixLayoutIfThingsMoved(questionManager.isPrinting);\n }, 100);\n },\n\n /**\n * Given an event, work out which question it effects.\n * @param {Event} e the event.\n * @returns {DragDropMarkersQuestion|undefined} The question, or undefined.\n */\n getQuestionForEvent: function(e) {\n var containerId = $(e.currentTarget).closest('.que.ddmarker').attr('id');\n return questionManager.questions[containerId];\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_ddmarker/question\n */\n return {\n /**\n * Initialise one drag-drop markers question.\n *\n * @param {String} containerId id of the outer div for this question.\n * @param {String} bgImgUrl the URL of the background image.\n * @param {boolean} readOnly whether the question is being displayed read-only.\n * @param {String[]} visibleDropZones the geometry of any drop-zones to show.\n */\n init: questionManager.init\n };\n});\n"],"names":["define","$","dragDrop","Shapes","keys","FormChangeChecker","DragDropMarkersQuestion","containerId","readOnly","visibleDropZones","shapes","shapeSVGs","isPrinting","questionAnswer","getRoot","addClass","this","cloneDrags","repositionDrags","drawDropzones","prototype","length","bgImage","find","html","outerWidth","outerHeight","svg","nextColourIndex","dropZoneNo","colourClass","addDropzone","existingmarkertext","dropZone","shape","make","bgRatio","parse","coords","markertext","remove","classnames","append","markerspan","handles","getHandlePositions","positionLeft","moveHandle","x","positionTop","y","css","data","position","left","top","handleElementScale","shapeSVG","makeSvg","setAttribute","root","thisQ","not","each","key","item","input","choiceNo","getChoiceNoFromElement","getCoords","drag","i","dragInDrop","clone","sendDragToDrop","getDragClone","cloneDragIfNeeded","getQuestionAnsweredValues","result","inputNode","id","value","isQuestionInteracted","oldAnswer","newAnswer","isInteracted","JSON","stringify","Object","forEach","val","coordsStrings","split","convertToWindowXY","Point","point","offset","convertToBgImgXY","coordsInBgImg","bgPosition","width","height","document","getElementById","handleDragStart","e","dragged","target","closest","prepare","start","hasClass","hiddenDrag","dragEnd","dragXY","placed","bgImgXY","sendDragHome","removeDragIfNeeded","saveCoordsForChoice","items","thiQ","join","questionManager","handleFormDirty","handleKeyPress","keyCode","arrowLeft","arrowRight","arrowDown","arrowUp","space","escape","preventDefault","constrainToBgImg","focus","windowxy","bgImg","Math","max","min","node","Number","getClassnameNumericSuffix","prefix","classes","attr","undefined","classesarr","index","RegExp","test","match","exec","handleResize","parseFloat","originCoords","updateSvg","markerSpan","draghome","placeHolder","removeClass","getDragNoClass","before","getDragNo","includeSelector","className","isInfiniteDrag","dropArea","after","isScaling","initialLoad","getInput","noOfDrags","displayedDragsInDropArea","displayedDragsInDragHomes","dragClone","addEventHandlersToMarker","dragsInHome","displayedDrags","first","bgImgNaturalWidth","get","naturalWidth","element","type","eventHandlersInitialised","markerEventHandlersInitialised","isKeyboardNavigation","questions","init","setupEventHandlers","hasOwnProperty","questionContainer","classList","contains","window","on","handleWindowResize","addEventListener","setTimeout","fixLayoutIfThingsMoved","focusin","handleKeyboardFocus","focusout","question","getQuestionForEvent","isNavigating","currentTarget","responseForm","markFormAsDirty"],"mappings":";;;;;;;AAuBAA,iCAAO,CACH,SACA,gBACA,wBACA,iBACA,4BACD,SACCC,EACAC,SACAC,OACAC,KACAC,4BAcSC,wBAAwBC,YAAaC,SAAUC,uBAE/CF,YAAcA,iBACdE,iBAAmBA,sBACnBC,OAAS,QACTC,UAAY,QACZC,YAAa,OACbC,eAAiB,GAClBL,eACKM,UAAUC,SAAS,2BARhBC,KAUNC,aAVMD,KAWNE,kBAXMF,KAYNG,gBAMVb,wBAAwBc,UAAUD,cAAgB,cAC1CH,KAAKP,iBAAiBY,OAAS,EAAG,KAC9BC,QAAUN,KAAKM,eAEdR,UAAUS,KAAK,iBAAiBC,KAAK,oEAC1BF,QAAQG,aADkB,aAEzBH,QAAQI,cAAgB,oBACrCC,IAAMX,KAAKF,UAAUS,KAAK,iBAE1BK,gBAAkB,EACbC,WAAa,EAAGA,WAAab,KAAKP,iBAAiBY,OAAQQ,aAAc,KAC1EC,YAAc,QAAUF,gBAC5BA,iBAAmBA,gBAAkB,GAAK,OACrCG,YAAYJ,IAAKE,WAAYC,gBAY9CxB,wBAAwBc,UAAUW,YAAc,SAASJ,IAAKE,WAAYC,iBAGlEE,mBAFAC,SAAWjB,KAAKP,iBAAiBoB,YACjCK,MAAQ/B,OAAOgC,KAAKF,SAASC,MAAO,IAEpCE,QAAUpB,KAAKoB,aACdF,MAAMG,MAAMJ,SAASK,OAAQF,cAIlCJ,mBAAqBhB,KAAKF,UAAUS,KAAK,kCAAoCM,aACtDR,OACS,KAAxBY,SAASM,WACTP,mBAAmBR,KAAKS,SAASM,YAEjCP,mBAAmBQ,cAEpB,GAA4B,KAAxBP,SAASM,WAAmB,KAC/BE,WAAa,wBAA0BZ,gBACtCf,UAAUS,KAAK,mBAAmBmB,OAAO,gBAAkBD,WAAa,KACzER,SAASM,WAAa,eACtBI,WAAa3B,KAAKF,UAAUS,KAAK,6CAA+CM,eAChFc,WAAWtB,OAAQ,KACfuB,QAAUV,MAAMW,qBAChBC,aAAeF,QAAQG,WAAWC,EAAKL,WAAWlB,aAAe,EAAK,EACtEwB,YAAcL,QAAQG,WAAWG,EAAKP,WAAWjB,cAAgB,EACrEiB,WACKQ,IAAI,OAAQL,cACZK,IAAI,MAAOF,aAChBN,WACKS,KAAK,UAAWT,WAAWU,WAAWC,KAAOlB,SAC7CgB,KAAK,UAAWT,WAAWU,WAAWE,IAAMnB,cAC5CoB,mBAAmBb,WAAY,eAIxCc,SAAWvB,MAAMwB,QAAQ/B,IAAI,IACjC8B,SAASE,aAAa,QAAS,YAAc7B,kBAExCpB,OAAOM,KAAKN,OAAOW,QAAUa,WAC7BvB,UAAUK,KAAKL,UAAUU,QAAUoC,WAQ5CnD,wBAAwBc,UAAUF,gBAAkB,eAC5C0C,KAAO5C,KAAKF,UACZ+C,MAAQ7C,KAEZ4C,KAAKrC,KAAK,yBAAyBuC,IAAI,oBAAoBC,MAAK,SAASC,IAAKC,MAC1EhE,EAAEgE,MAAMlD,SAAS,eAGrB6C,KAAKrC,KAAK,iBAAiBwC,MAAK,SAASC,IAAKE,WACtCC,SAAWN,MAAMO,uBAAuBF,OACxC5B,OAASuB,MAAMQ,UAAUH,UACzB5B,OAAOjB,OAAQ,KACXiD,KAAOT,MAAM/C,UAAUS,KAAK,gCAA4C4C,UAAUL,IAAI,oBAC1FQ,KAAK9B,aACA,IAAI+B,EAAI,EAAGA,EAAIjC,OAAOjB,OAAQkD,IAAK,KAChCC,WAAaF,KAAKG,QACtBD,WAAWpB,KAAK,QAASd,OAAOiC,GAAGvB,GAAGI,KAAK,QAASd,OAAOiC,GAAGrB,GAG9DsB,WAAWpB,KAAK,aAAc,GAC9BS,MAAMa,eAAeF,YAAY,GAAO,GAE5CX,MAAMc,aAAaL,MAAMvD,SAAS,UAClC8C,MAAMe,kBAAkBN,UAKhCT,MAAMhD,eAAiBgD,MAAMgB,6BAQjCvE,wBAAwBc,UAAUyD,0BAA4B,eACtDC,OAAS,eACRhE,UAAUS,KAAK,iBAAiBwC,MAAK,CAACQ,EAAGQ,aAC1CD,OAAOC,UAAUC,IAAMD,UAAUE,SAG9BH,QAQXxE,wBAAwBc,UAAU8D,qBAAuB,iBAC/CC,UAAYnE,KAAKH,eACjBuE,UAAYpE,KAAK6D,gCACnBQ,cAAe,SAGfC,KAAKC,UAAUH,aAAeE,KAAKC,UAAUJ,YAC7CE,cAAe,EACRA,eAGXG,OAAOpF,KAAKgF,WAAWK,SAAQzB,MACvBoB,UAAUpB,OAASmB,UAAUnB,OAC7BqB,cAAe,MAIhBA,eAYX/E,wBAAwBc,UAAUiD,UAAY,SAASU,eAC/CzC,OAAS,GACToD,IAAMzF,EAAE8E,WAAWW,SACX,KAARA,YACIC,cAAgBD,IAAIE,MAAM,KACrBrB,EAAI,EAAGA,EAAIoB,cAActE,OAAQkD,IACtCjC,OAAOiC,GAAKvD,KAAK6E,kBAAkB1F,OAAO2F,MAAMzD,MAAMsD,cAAcpB,YAGrEjC,QAUXhC,wBAAwBc,UAAUyE,kBAAoB,SAASE,WACvDzE,QAAUN,KAAKM,iBAKZyE,MAAMC,OAAO1E,QAAQ0E,SAAS1C,KAAO,EAAGhC,QAAQ0E,SAASzC,IAAM,IAU1EjD,wBAAwBc,UAAU6E,iBAAmB,SAASF,WACtDzE,QAAUN,KAAKM,iBACZyE,MAAMC,QAAQ1E,QAAQ0E,SAAS1C,KAAO,GAAIhC,QAAQ0E,SAASzC,IAAM,IAS5EjD,wBAAwBc,UAAU8E,cAAgB,SAASH,WACnDzE,QAAUN,KAAKM,UACf6E,WAAa7E,QAAQ0E,gBAElBD,MAAM/C,GAAKmD,WAAW7C,MAAQyC,MAAM/C,EAAImD,WAAW7C,KAAOhC,QAAQ8E,SAClEL,MAAM7C,GAAKiD,WAAW5C,KAAOwC,MAAM7C,EAAIiD,WAAW5C,IAAMjC,QAAQ+E,UAO3E/F,wBAAwBc,UAAUN,QAAU,kBACjCb,EAAEqG,SAASC,eAAevF,KAAKT,eAO1CD,wBAAwBc,UAAUE,QAAU,kBACjCN,KAAKF,UAAUS,KAAK,uBAG/BjB,wBAAwBc,UAAUoF,gBAAkB,SAASC,OACrD5C,MAAQ7C,KACR0F,QAAUzG,EAAEwG,EAAEE,QAAQC,QAAQ,cAEvB1G,SAAS2G,QAAQJ,GAClBK,UAIVJ,QAAQ3F,SAAS,gBAAgBoC,IAAI,YAAa,MAEpCuD,QAAQK,SAAS,YAClB,KACLC,WAAanD,MAAMc,aAAa+B,SAChCM,WAAW3F,SACX2F,WAAWjG,SAAS,UACpB2F,QAAQV,OAAOgB,WAAWhB,WAIlC9F,SAAS4G,MAAML,EAAGC,SAAS,eAExB,SAAS1D,EAAGE,EAAGwD,SACd7C,MAAMoD,QAAQP,cAQtBpG,wBAAwBc,UAAU6F,QAAU,SAASP,aAI7CQ,OAHAC,QAAS,EACThD,SAAWnD,KAAKoD,uBAAuBsC,SACvCtE,QAAUpB,KAAKoB,aAGnBsE,QAAQtD,KAAK,QAASsD,QAAQV,SAAS1C,MAAMF,KAAK,QAASsD,QAAQV,SAASzC,KAC5E2D,OAAS,IAAI/G,OAAO2F,MAAMY,QAAQtD,KAAK,SAAUsD,QAAQtD,KAAK,UAC1DpC,KAAKkF,cAAcgB,QAAS,MACvBxC,eAAegC,SAAS,GAC7BS,QAAS,MAILC,QAAUpG,KAAKiF,iBAAiBiB,QACpCE,QAAU,IAAIjH,OAAO2F,MAAMsB,QAAQpE,EAAIZ,QAASgF,QAAQlE,EAAId,SAC5DsE,QAAQtD,KAAK,UAAWgE,QAAQpE,GAAGI,KAAK,UAAWgE,QAAQlE,GAG1DiE,YAIIvC,kBAAkB8B,eAHlBW,aAAaX,cACbY,mBAAmBZ,eAKvBa,oBAAoBpD,WAO7B7D,wBAAwBc,UAAUmG,oBAAsB,SAASpD,cACzD7B,OAAS,GACTkF,MAAQxG,KAAKF,UAAUS,KAAK,kCAAoC4C,UAChEsD,KAAOzG,KACPoB,QAAUpB,KAAKoB,UAEfoF,MAAMnG,QACNmG,MAAMzD,MAAK,eACHO,KAAOrE,EAAEe,UACRsD,KAAKyC,SAAS,gBAAiB,CAC5BzC,KAAKlB,KAAK,gBAAkBhB,SAE5BkC,KAAKlB,KAAK,QAASkB,KAAK0B,SAAS1C,MAAMF,KAAK,QAASkB,KAAK0B,SAASzC,SAEnE2D,OAAS,IAAI/G,OAAO2F,MAAMxB,KAAKlB,KAAK,SAAUkB,KAAKlB,KAAK,aACxDqE,KAAKvB,cAAcgB,QAAS,KACxBE,QAAUK,KAAKxB,iBAAiBiB,QACpCE,QAAU,IAAIjH,OAAO2F,MAAMsB,QAAQpE,EAAIZ,QAASgF,QAAQlE,EAAId,SAC5DE,OAAOA,OAAOjB,QAAU+F,kBAMnCtG,UAAUS,KAAK,eAAiB4C,UAAUuB,IAAIpD,OAAOoF,KAAK,MAC3D1G,KAAKkE,yBAELyC,gBAAgBC,uBAEX/G,eAAiBG,KAAK6D,8BAQnCvE,wBAAwBc,UAAUyG,eAAiB,SAASpB,OACpDnC,KAAOrE,EAAEwG,EAAEE,QAAQC,QAAQ,WAC3Bb,MAAQ,IAAI5F,OAAO2F,MAAMxB,KAAK0B,SAAS1C,KAAMgB,KAAK0B,SAASzC,KAC3DY,SAAWnD,KAAKoD,uBAAuBE,aAEnCmC,EAAEqB,cACD1H,KAAK2H,eACL,GACDhC,MAAM/C,GAAK,aAEV5C,KAAK4H,gBACL,GACDjC,MAAM/C,GAAK,aAEV5C,KAAK6H,eACL,GACDlC,MAAM7C,GAAK,aAEV9C,KAAK8H,aACL,GACDnC,MAAM7C,GAAK,aAEV9C,KAAK+H,WACL/H,KAAKgI,OACNrC,MAAQ,6BAKhBU,EAAE4B,iBAEY,OAAVtC,MAAgB,CAChBA,MAAQ/E,KAAKsH,iBAAiBvC,OAC9BzB,KAAK0B,OAAO,MAASD,MAAM/C,MAAU+C,MAAM7C,IAC3CoB,KAAKlB,KAAK,QAASkB,KAAK0B,SAAS1C,MAAMF,KAAK,QAASkB,KAAK0B,SAASzC,SAC/D2D,OAASlG,KAAKiF,iBAAiB,IAAI9F,OAAO2F,MAAMxB,KAAKlB,KAAK,SAAUkB,KAAKlB,KAAK,cAClFkB,KAAKlB,KAAK,UAAW8D,OAAOlE,EAAIhC,KAAKoB,WAAWgB,KAAK,UAAW8D,OAAOhE,EAAIlC,KAAKoB,WAC5EpB,KAAKkF,cAAc,IAAI/F,OAAO2F,MAAMxB,KAAK0B,SAAS1C,KAAMgB,KAAK0B,SAASzC,OAClEe,KAAKyC,SAAS,YAAa,MACtBrC,eAAeJ,MAAM,OACtB0C,WAAahG,KAAK2D,aAAaL,MAC/B0C,WAAW3F,QACX2F,WAAWjG,SAAS,eAEnB6D,kBAAkBN,YAI/BA,KAAKnB,IAAI,OAAQ,IAAIA,IAAI,MAAO,IAChCmB,KAAKlB,KAAK,QAASkB,KAAK0B,SAAS1C,MAAMF,KAAK,QAASkB,KAAK0B,SAASzC,UAC9D8D,aAAa/C,WACbgD,mBAAmBhD,MAE5BA,KAAKiE,aACAhB,oBAAoBpD,WAS7B7D,wBAAwBc,UAAUkH,iBAAmB,SAASE,cACtDC,MAAQzH,KAAKM,UACb8F,QAAUpG,KAAKiF,iBAAiBuC,iBACpCpB,QAAQpE,EAAI0F,KAAKC,IAAI,EAAGvB,QAAQpE,GAChCoE,QAAQlE,EAAIwF,KAAKC,IAAI,EAAGvB,QAAQlE,GAChCkE,QAAQpE,EAAI0F,KAAKE,IAAIH,MAAMrC,QAASgB,QAAQpE,GAC5CoE,QAAQlE,EAAIwF,KAAKE,IAAIH,MAAMpC,SAAUe,QAAQlE,GACtClC,KAAK6E,kBAAkBuB,UASlC9G,wBAAwBc,UAAUgD,uBAAyB,SAASyE,aACzDC,OAAO9H,KAAK+H,0BAA0BF,KAAM,YAUvDvI,wBAAwBc,UAAU2H,0BAA4B,SAASF,KAAMG,YACrEC,QAAUhJ,EAAE4I,MAAMK,KAAK,iBACXC,IAAZF,SAAqC,KAAZA,gBACrBG,WAAaH,QAAQrD,MAAM,KACtByD,MAAQ,EAAGA,MAAQD,WAAW/H,OAAQgI,QAAS,IACxC,IAAIC,OAAO,IAAMN,OAAS,aAC5BO,KAAKH,WAAWC,QAAS,KAE3BG,MADQ,IAAIF,OAAO,aACLG,KAAKL,WAAWC,eAC3BP,OAAOU,MAAM,YAIzB,MAMXlJ,wBAAwBc,UAAUsI,aAAe,eACzC7F,MAAQ7C,KACRoB,QAAUpB,KAAKoB,UACfpB,KAAKJ,aACLwB,QAAU,QAGTtB,UAAUS,KAAK,wBAAwBuC,IAAI,iBAAiBC,MAAK,SAASC,IAAKM,MAChFrE,EAAEqE,MACGnB,IAAI,OAAQwG,WAAW1J,EAAEqE,MAAMlB,KAAK,YAAcuG,WAAWvH,UAC7De,IAAI,MAAOwG,WAAW1J,EAAEqE,MAAMlB,KAAK,YAAcuG,WAAWvH,UACjEyB,MAAML,mBAAmBc,KAAM,oBAG9BxD,UAAUS,KAAK,8BACf6E,MAAMpF,KAAKM,UAAU8E,SACrBC,OAAOrF,KAAKM,UAAU+E,cAEtB,IAAIxE,WAAa,EAAGA,WAAab,KAAKP,iBAAiBY,OAAQQ,aAAc,KAE1E+H,aADW/F,MAAMpD,iBAAiBoB,YACVS,OACxBJ,MAAQ2B,MAAMnD,OAAOmB,YACrB4B,SAAWI,MAAMlD,UAAUkB,YAC/BK,MAAMG,MAAMuH,aAAcxH,SAC1BF,MAAM2H,UAAUpG,cAEZb,QAAUV,MAAMW,qBAChBiH,WAAa9I,KAAKF,UAAUS,KAAK,6CAA+CM,YACpFiI,WACK3G,IAAI,OAAQP,QAAQG,WAAWC,EAAK8G,WAAWrI,aAAe,EAAK,GACnE0B,IAAI,MAAOP,QAAQG,WAAWG,EAAK4G,WAAWpI,cAAgB,GACnEmC,MAAML,mBAAmBsG,WAAY,YAO7CxJ,wBAAwBc,UAAUH,WAAa,eACvC4C,MAAQ7C,UACPF,UAAUS,KAAK,6BAA6BwC,MAAK,SAASsF,MAAOU,cAC9DzF,KAAOrE,EAAE8J,UACTC,YAAc1F,KAAKG,QACvBuF,YAAYC,cACZD,YAAYjJ,SAAS,UACrBiJ,YAAYjJ,SAAS,SAAW8C,MAAMO,uBAAuBE,OAC7D0F,YAAYjJ,SAAS8C,MAAMqG,eAAe5F,MAAM,IAChD0F,YAAYjJ,SAAS,mBACrBuD,KAAK6F,OAAOH,iBAUpB1J,wBAAwBc,UAAUgJ,UAAY,SAAS9F,aAC5CtD,KAAK+H,0BAA0BzE,KAAM,WAUhDhE,wBAAwBc,UAAU8I,eAAiB,SAAS5F,KAAM+F,qBAC1DC,UAAY,SAAWtJ,KAAKoJ,UAAU9F,aACtCtD,KAAKuJ,eAAejG,QACpBgG,UAAY,YAGZD,gBACO,IAAMC,UAGVA,WASXhK,wBAAwBc,UAAUuD,aAAe,SAASL,aAC/CtD,KAAKF,UAAUS,KAAK,gCACXP,KAAKoD,uBAAuBE,MAAQtD,KAAKkJ,eAAe5F,MAAM,GAAQ,qBAO1FhE,wBAAwBc,UAAUoJ,SAAW,kBAClCxJ,KAAKF,UAAUS,KAAK,iBAQ/BjB,wBAAwBc,UAAUiG,aAAe,SAAS/C,MACtDA,KAAK2F,YAAY,gBACZlJ,SAAS,YACToC,IAAI,MAAO,IACXA,IAAI,OAAQ,IACZA,IAAI,YAAa,QAClB6G,YAAchJ,KAAK2D,aAAaL,MACpC0F,YAAYS,MAAMnG,MAClB0F,YAAYC,YAAY,WAU5B3J,wBAAwBc,UAAUsD,eAAiB,SAASJ,KAAMoG,eAAWC,wEACrEH,SAAWxJ,KAAKwJ,WAChBpI,QAAUpB,KAAKoB,UACnBkC,KAAK2F,YAAY,gBAAgBA,YAAY,gBACzC/C,OAASlG,KAAKiF,iBAAiB,IAAI9F,OAAO2F,MAAMxB,KAAKlB,KAAK,SAAUkB,KAAKlB,KAAK,WAC9EsH,WACApG,KAAKlB,KAAK,UAAW8D,OAAOlE,EAAIZ,SAASgB,KAAK,UAAW8D,OAAOhE,EAAId,SACpEkC,KAAKnB,IAAI,OAAQ+D,OAAOlE,GAAGG,IAAI,MAAO+D,OAAOhE,KAE7CoB,KAAKlB,KAAK,UAAW8D,OAAOlE,GAAGI,KAAK,UAAW8D,OAAOhE,GACtDoB,KAAKnB,IAAI,OAAQ+D,OAAOlE,EAAIZ,SAASe,IAAI,MAAO+D,OAAOhE,EAAId,UAG1DuI,aAEDrG,KAAKlB,KAAK,aAAchB,SAE5BoI,SAAS9H,OAAO4B,WACXd,mBAAmBc,KAAM,aAQlChE,wBAAwBc,UAAUwD,kBAAoB,SAASN,UACvDS,UAAY/D,KAAK4J,SAAStG,MAC1BuG,UAAY/B,OAAO9H,KAAK+H,0BAA0BhE,UAAW,cAC7D+F,yBAA2B9J,KAAKF,UAAUS,KAAK,8BAC3CP,KAAKoD,uBAAuBE,MAAQtD,KAAKkJ,eAAe5F,MAAM,IAAOjD,OACzE0J,0BAA4B/J,KAAKF,UAAUS,KAAK,+BAC5CP,KAAKoD,uBAAuBE,MAAQtD,KAAKkJ,eAAe5F,MAAM,IAAOR,IAAI,oBAAoBzC,WAEhGL,KAAKuJ,eAAejG,QAChBtD,KAAKuJ,eAAejG,OAASwG,yBAA2BD,YAA4C,IAA9BE,0BAAiC,KACxGC,UAAY1G,KAAKG,QACrBuG,UAAUjK,SAAS,YACdoC,IAAI,MAAO,IACXA,IAAI,OAAQ,IACZA,IAAI,YAAa,SACjBwB,aAAaL,MACb2F,YAAY,UACZQ,MAAMO,WACXrD,gBAAgBsD,yBAAyBD,aASjD1K,wBAAwBc,UAAUkG,mBAAqB,SAAShD,cACxD4G,YAAclK,KAAKF,UAAUS,KAAK,+BAClCP,KAAKoD,uBAAuBE,MAAQtD,KAAKkJ,eAAe5F,MAAM,IAAOR,IAAI,oBACzEqH,eAAiBD,YAAY7J,OAC1B8J,eAAiB,GACpBD,YAAYE,QAAQ5I,SACpB2I,kBAUR7K,wBAAwBc,UAAUwJ,SAAW,SAAStG,UAC9CH,SAAWnD,KAAKoD,uBAAuBE,aACpCtD,KAAKF,UAAUS,KAAK,uBAAyB4C,WAQxD7D,wBAAwBc,UAAUgB,QAAU,eACpCqG,MAAQzH,KAAKM,UACb+J,kBAAoB5C,MAAM6C,IAAI,GAAGC,oBACd9C,MAAMrC,QAEHiF,mBAS9B/K,wBAAwBc,UAAUoC,mBAAqB,SAASgI,QAASC,UACjErJ,QAAUuH,WAAW3I,KAAKoB,WAC1BpB,KAAKJ,aACLwB,QAAU,GAEdnC,EAAEuL,SAASrI,IAAI,qBACU,SAAWf,QAAU,qBACxB,SAAWA,QAAU,oBACtB,SAAWA,QAAU,mBACtB,SAAWA,QAAU,cACxB,SAAWA,QAAU,uBACdqJ,QAS5BnL,wBAAwBc,UAAUmJ,eAAiB,SAASjG,aACjDA,KAAKyC,SAAS,iBASrBY,gBAAkB,CAKlB+D,0BAA0B,EAM1BC,+BAAgC,GAKhC/K,YAAY,EAKZgL,sBAAsB,EAKtBC,UAAW,GASXC,KAAM,SAASvL,YAAaC,SAAUC,qBAClCkH,gBAAgBkE,UAAUtL,aACtB,IAAID,wBAAwBC,YAAaC,SAAUC,kBAClDkH,gBAAgB+D,2BACjB/D,gBAAgBoE,qBAChBpE,gBAAgB+D,0BAA2B,IAE1C/D,gBAAgBgE,+BAA+BK,eAAezL,aAAc,CAC7EoH,gBAAgBgE,+BAA+BpL,cAAe,MAE1D0L,kBAAoB3F,SAASC,eAAehG,aAC5C0L,kBAAkBC,UAAUC,SAAS,cACpCF,kBAAkBC,UAAUC,SAAS,6BAEtCxE,gBAAgBsD,yBAAyBhL,EAAEgM,mBAAmB1K,KAAK,0BACnEoG,gBAAgBsD,yBAAyBhL,EAAEgM,mBAAmB1K,KAAK,4BAQ/EwK,mBAAoB,WAChB9L,EAAEmM,QAAQC,GAAG,UAAU,WACnB1E,gBAAgB2E,oBAAmB,MAEvCF,OAAOG,iBAAiB,eAAe,WACnC5E,gBAAgB/G,YAAa,EAC7B+G,gBAAgB2E,mBAAmB3E,gBAAgB/G,eAEvDwL,OAAOG,iBAAiB,cAAc,WAClC5E,gBAAgB/G,YAAa,EAC7B+G,gBAAgB2E,mBAAmB3E,gBAAgB/G,eAEvD4L,YAAW,WACP7E,gBAAgB8E,2BACjB,MAQPxB,yBAA0B,SAASO,SAC/BA,QACKa,GAAG,uBAAwB1E,gBAAgBnB,iBAC3C6F,GAAG,mBAAoB1E,gBAAgBE,gBACvC6E,SAAQ,SAASjG,GACdkB,gBAAgBgF,oBAAoBlG,GAAG,MAE1CmG,UAAS,SAASnG,GACfkB,gBAAgBgF,oBAAoBlG,GAAG,OAQnDD,gBAAiB,SAASC,GACtBA,EAAE4B,qBACEwE,SAAWlF,gBAAgBmF,oBAAoBrG,GAC/CoG,UACAA,SAASrG,gBAAgBC,IAQjCoB,eAAgB,SAASpB,OACjBoG,SAAWlF,gBAAgBmF,oBAAoBrG,GAC/CoG,UACAA,SAAShF,eAAepB,IAQhC6F,mBAAoB,SAAS1L,gBACpB,IAAIL,eAAeoH,gBAAgBkE,UAChClE,gBAAgBkE,UAAUG,eAAezL,eACzCoH,gBAAgBkE,UAAUtL,aAAaK,WAAaA,WACpD+G,gBAAgBkE,UAAUtL,aAAamJ,iBAUnDiD,oBAAqB,SAASlG,EAAGsG,cAC7BpF,gBAAgBiE,qBAAuBmB,cAQ3CN,uBAAwB,WACf9E,gBAAgBiE,2BACZU,mBAAmB3E,gBAAgB/G,YAK5C4L,YAAW,WACP7E,gBAAgB8E,uBAAuB9E,gBAAgB/G,cACxD,MAQPkM,oBAAqB,SAASrG,OACtBlG,YAAcN,EAAEwG,EAAEuG,eAAepG,QAAQ,iBAAiBsC,KAAK,aAC5DvB,gBAAgBkE,UAAUtL,cAMrCqH,gBAAiB,iBACPqF,aAAe3G,SAASC,eAAe,gBAC7ClG,kBAAkB6M,gBAAgBD,sBAOnC,CASHnB,KAAMnE,gBAAgBmE"}
\ No newline at end of file
diff --git a/question/type/ddmarker/amd/src/question.js b/question/type/ddmarker/amd/src/question.js
index 49fe0a26f5e..b75d8cf77f6 100644
--- a/question/type/ddmarker/amd/src/question.js
+++ b/question/type/ddmarker/amd/src/question.js
@@ -21,7 +21,19 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], function($, dragDrop, Shapes, keys) {
+define([
+ 'jquery',
+ 'core/dragdrop',
+ 'qtype_ddmarker/shapes',
+ 'core/key_codes',
+ 'core_form/changechecker'
+], function(
+ $,
+ dragDrop,
+ Shapes,
+ keys,
+ FormChangeChecker
+) {
"use strict";
@@ -41,6 +53,7 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
this.shapes = [];
this.shapeSVGs = [];
this.isPrinting = false;
+ this.questionAnswer = {};
if (readOnly) {
this.getRoot().addClass('qtype_ddmarker-readonly');
}
@@ -150,6 +163,48 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
thisQ.cloneDragIfNeeded(drag);
}
});
+
+ // Save the question answer.
+ thisQ.questionAnswer = thisQ.getQuestionAnsweredValues();
+ };
+
+ /**
+ * Get the question answered values.
+ *
+ * @return {Object} Contain key-value with key is the input id and value is the input value.
+ */
+ DragDropMarkersQuestion.prototype.getQuestionAnsweredValues = function() {
+ let result = {};
+ this.getRoot().find('input.choices').each((i, inputNode) => {
+ result[inputNode.id] = inputNode.value;
+ });
+
+ return result;
+ };
+
+ /**
+ * Check if the question is being interacted or not.
+ *
+ * @return {boolean} Return true if the user has changed the question-answer.
+ */
+ DragDropMarkersQuestion.prototype.isQuestionInteracted = function() {
+ const oldAnswer = this.questionAnswer;
+ const newAnswer = this.getQuestionAnsweredValues();
+ let isInteracted = false;
+
+ // First, check both answers have the same structure or not.
+ if (JSON.stringify(newAnswer) !== JSON.stringify(oldAnswer)) {
+ isInteracted = true;
+ return isInteracted;
+ }
+ // Check the values.
+ Object.keys(newAnswer).forEach(key => {
+ if (newAnswer[key] !== oldAnswer[key]) {
+ isInteracted = true;
+ }
+ });
+
+ return isInteracted;
};
/**
@@ -320,6 +375,12 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
}
this.getRoot().find('input.choice' + choiceNo).val(coords.join(';'));
+ if (this.isQuestionInteracted()) {
+ // The user has interacted with the draggable items. We need to mark the form as dirty.
+ questionManager.handleFormDirty();
+ // Save the new answered value.
+ this.questionAnswer = this.getQuestionAnsweredValues();
+ }
};
/**
@@ -844,6 +905,14 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
getQuestionForEvent: function(e) {
var containerId = $(e.currentTarget).closest('.que.ddmarker').attr('id');
return questionManager.questions[containerId];
+ },
+
+ /**
+ * Handle when the form is dirty.
+ */
+ handleFormDirty: function() {
+ const responseForm = document.getElementById('responseform');
+ FormChangeChecker.markFormAsDirty(responseForm);
}
};
diff --git a/question/type/ddwtos/amd/build/ddwtos.min.js b/question/type/ddwtos/amd/build/ddwtos.min.js
index 0c576113b63..d4487217d8a 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"],(function($,dragDrop,keys){function DragDropToTextQuestion(containerId,readOnly){this.containerId=containerId,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).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)}};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 ffb019ea756..60c2a228829 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(['jquery', 'core/dragdrop', 'core/key_codes'], function($, dragDrop, keys) {\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 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\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.drop.group' + this.getGroup(drag)).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 this.getRoot().find('span.draghome.placed.group' + this.getGroup(drag)).not('.beingdragged').each(function(i, dropNode) {\n var drop = $(dropNode);\n if (thisQ.isPointInDrop(pageX, pageY, drop) && !thisQ.isDragSameAsDrop(drag, 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.drop.group' + this.getGroup(drag)).each(function(i, dropNode) {\n var drop = $(dropNode);\n if (!thisQ.isPointInDrop(pageX, pageY, drop)) {\n // Not this drop.\n return true;\n }\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\n root.find('span.draghome.placed.group' + this.getGroup(drag)).not('.beingdragged').each(function(i, placedNode) {\n var placedDrag = $(placedNode);\n if (!thisQ.isPointInDrop(pageX, pageY, placedDrag) || thisQ.isDragSameAsDrop(drag, placedDrag)) {\n // Not this placed drag.\n return true;\n }\n\n // Now put this drag into the drop.\n placedDrag.removeClass('valid-drag-over-drop');\n var currentPlace = thisQ.getClassnameNumericSuffix(placedDrag, 'inplace');\n var drop = thisQ.getDrop(drag, currentPlace);\n thisQ.sendDragToDrop(drag, drop);\n placed = true;\n return false; // Stop the each() here.\n });\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 // 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 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 !== '') {\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 * Check that the drag is drop to it's clone.\n *\n * @param {jQuery} drag The drag.\n * @param {jQuery} drop The drop.\n * @returns {boolean}\n */\n DragDropToTextQuestion.prototype.isDragSameAsDrop = function(drag, drop) {\n return this.getChoice(drag) === this.getChoice(drop) && this.getGroup(drag) === this.getGroup(drop);\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 }\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","DragDropToTextQuestion","containerId","readOnly","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","handleDragStart","e","target","closest","prepare","start","setInputValue","hiddenDrop","getDrop","x","y","dragMove","dragEnd","pageX","pageY","dropNode","isPointInDrop","isDragSameAsDrop","placed","placedNode","placedDrag","sendDragHome","oldDrag","getCurrentDragInPlace","focus","attr","animateTo","getDragHome","handleKeyPress","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","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"],"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.drop.group' + this.getGroup(drag)).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 this.getRoot().find('span.draghome.placed.group' + this.getGroup(drag)).not('.beingdragged').each(function(i, dropNode) {\n var drop = $(dropNode);\n if (thisQ.isPointInDrop(pageX, pageY, drop) && !thisQ.isDragSameAsDrop(drag, 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.drop.group' + this.getGroup(drag)).each(function(i, dropNode) {\n var drop = $(dropNode);\n if (!thisQ.isPointInDrop(pageX, pageY, drop)) {\n // Not this drop.\n return true;\n }\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\n root.find('span.draghome.placed.group' + this.getGroup(drag)).not('.beingdragged').each(function(i, placedNode) {\n var placedDrag = $(placedNode);\n if (!thisQ.isPointInDrop(pageX, pageY, placedDrag) || thisQ.isDragSameAsDrop(drag, placedDrag)) {\n // Not this placed drag.\n return true;\n }\n\n // Now put this drag into the drop.\n placedDrag.removeClass('valid-drag-over-drop');\n var currentPlace = thisQ.getClassnameNumericSuffix(placedDrag, 'inplace');\n var drop = thisQ.getDrop(drag, currentPlace);\n thisQ.sendDragToDrop(drag, drop);\n placed = true;\n return false; // Stop the each() here.\n });\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 // 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 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 !== '') {\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 * Check that the drag is drop to it's clone.\n *\n * @param {jQuery} drag The drag.\n * @param {jQuery} drop The drop.\n * @returns {boolean}\n */\n DragDropToTextQuestion.prototype.isDragSameAsDrop = function(drag, drop) {\n return this.getChoice(drag) === this.getChoice(drop) && this.getGroup(drag) === this.getGroup(drop);\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","isDragSameAsDrop","placed","placedNode","placedDrag","sendDragHome","oldDrag","getCurrentDragInPlace","focus","attr","animateTo","getDragHome","handleKeyPress","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","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 ed40565d8f9..c4e14fb53fd 100644
--- a/question/type/ddwtos/amd/src/ddwtos.js
+++ b/question/type/ddwtos/amd/src/ddwtos.js
@@ -37,7 +37,17 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.6
*/
-define(['jquery', 'core/dragdrop', 'core/key_codes'], function($, dragDrop, keys) {
+define([
+ 'jquery',
+ 'core/dragdrop',
+ 'core/key_codes',
+ 'core_form/changechecker'
+], function(
+ $,
+ dragDrop,
+ keys,
+ FormChangeChecker
+) {
"use strict";
@@ -50,6 +60,7 @@ define(['jquery', 'core/dragdrop', 'core/key_codes'], function($, dragDrop, keys
*/
function DragDropToTextQuestion(containerId, readOnly) {
this.containerId = containerId;
+ this.questionAnswer = {};
if (readOnly) {
this.getRoot().addClass('qtype_ddwtos-readonly');
}
@@ -187,6 +198,48 @@ define(['jquery', 'core/dragdrop', 'core/key_codes'], function($, dragDrop, keys
// Send the drag to drop.
thisQ.sendDragToDrop(thisQ.getUnplacedChoice(thisQ.getGroup(input), choice), drop);
});
+
+ // Save the question answer.
+ thisQ.questionAnswer = thisQ.getQuestionAnsweredValues();
+ };
+
+ /**
+ * Get the question answered values.
+ *
+ * @return {Object} Contain key-value with key is the input id and value is the input value.
+ */
+ DragDropToTextQuestion.prototype.getQuestionAnsweredValues = function() {
+ let result = {};
+ this.getRoot().find('input.placeinput').each((i, inputNode) => {
+ result[inputNode.id] = inputNode.value;
+ });
+
+ return result;
+ };
+
+ /**
+ * Check if the question is being interacted or not.
+ *
+ * @return {boolean} Return true if the user has changed the question-answer.
+ */
+ DragDropToTextQuestion.prototype.isQuestionInteracted = function() {
+ const oldAnswer = this.questionAnswer;
+ const newAnswer = this.getQuestionAnsweredValues();
+ let isInteracted = false;
+
+ // First, check both answers have the same structure or not.
+ if (JSON.stringify(newAnswer) !== JSON.stringify(oldAnswer)) {
+ isInteracted = true;
+ return isInteracted;
+ }
+ // Check the values.
+ Object.keys(newAnswer).forEach(key => {
+ if (newAnswer[key] !== oldAnswer[key]) {
+ isInteracted = true;
+ }
+ });
+
+ return isInteracted;
};
/**
@@ -868,6 +921,20 @@ define(['jquery', 'core/dragdrop', 'core/key_codes'], function($, dragDrop, keys
if (questionManager.isKeyboardNavigation) {
questionManager.isKeyboardNavigation = false;
}
+ if (thisQ.isQuestionInteracted()) {
+ // The user has interacted with the draggable items. We need to mark the form as dirty.
+ questionManager.handleFormDirty();
+ // Save the new answered value.
+ thisQ.questionAnswer = thisQ.getQuestionAnsweredValues();
+ }
+ },
+
+ /**
+ * Handle when the form is dirty.
+ */
+ handleFormDirty: function() {
+ const responseForm = document.getElementById('responseform');
+ FormChangeChecker.markFormAsDirty(responseForm);
}
};