diff --git a/question/type/ddmarker/amd/build/question.min.js b/question/type/ddmarker/amd/build/question.min.js
index 9a55053a2dd..0d5612c6480 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","core_form/changechecker"],(function($,dragDrop,Shapes,keys,FormChangeChecker){function DragDropMarkersQuestion(containerId,readOnly,visibleDropZones){var thisQ=this;this.containerId=containerId,this.visibleDropZones=visibleDropZones,this.shapes=[],this.shapeSVGs=[],this.isPrinting=!1,this.questionAnswer={},readOnly&&this.getRoot().addClass("qtype_ddmarker-readonly"),thisQ.allImagesLoaded=!1,thisQ.getNotYetLoadedImages().one("load",(function(){thisQ.waitForAllImagesToBeLoaded()})),thisQ.waitForAllImagesToBeLoaded()}DragDropMarkersQuestion.prototype.drawDropzones=function(){if(this.visibleDropZones.length>0){var bgImage=this.bgImage();this.getRoot().find("div.dropzones").html('<svg xmlns="http://www.w3.org/2000/svg" class="dropzones" width="'+bgImage.outerWidth()+'" height="'+bgImage.outerHeight()+'"></svg>');for(var svg=this.getRoot().find("svg.dropzones"),nextColourIndex=0,dropZoneNo=0;dropZoneNo<this.visibleDropZones.length;dropZoneNo++){var colourClass="color"+nextColourIndex;nextColourIndex=(nextColourIndex+1)%8,this.addDropzone(svg,dropZoneNo,colourClass)}}},DragDropMarkersQuestion.prototype.addDropzone=function(svg,dropZoneNo,colourClass){var existingmarkertext,dropZone=this.visibleDropZones[dropZoneNo],shape=Shapes.make(dropZone.shape,""),bgRatio=this.bgRatio();if(shape.parse(dropZone.coords,bgRatio)){if((existingmarkertext=this.getRoot().find("div.markertexts span.markertext"+dropZoneNo)).length)""!==dropZone.markertext?existingmarkertext.html(dropZone.markertext):existingmarkertext.remove();else if(""!==dropZone.markertext){var classnames="markertext markertext"+dropZoneNo;this.getRoot().find("div.markertexts").append('<span class="'+classnames+'">'+dropZone.markertext+"</span>");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<coords.length;i++){var dragInDrop=drag.clone();dragInDrop.data("pagex",coords[i].x).data("pagey",coords[i].y),dragInDrop.data("scaleRatio",1),thisQ.sendDragToDrop(dragInDrop,!1,!0)}thisQ.getDragClone(drag).addClass("active"),thisQ.cloneDragIfNeeded(drag)}})),thisQ.questionAnswer=thisQ.getQuestionAnsweredValues()},DragDropMarkersQuestion.prototype.getQuestionAnsweredValues=function(){let result={};return this.getRoot().find("input.choices").each(((i,inputNode)=>{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<coordsStrings.length;i++)coords[i]=this.convertToWindowXY(Shapes.Point.parse(coordsStrings[i]));return coords},DragDropMarkersQuestion.prototype.convertToWindowXY=function(point){var bgImage=this.bgImage();return point.offset(bgImage.offset().left+1,bgImage.offset().top+1)},DragDropMarkersQuestion.prototype.convertToBgImgXY=function(point){var bgImage=this.bgImage();return point.offset(-bgImage.offset().left-1,-bgImage.offset().top-1)},DragDropMarkersQuestion.prototype.coordsInBgImg=function(point){var bgImage=this.bgImage(),bgPosition=bgImage.offset();return point.x>=bgPosition.left&&point.x<bgPosition.left+bgImage.width()&&point.y>=bgPosition.top&&point.y<bgPosition.top+bgImage.height()},DragDropMarkersQuestion.prototype.getRoot=function(){return $(document.getElementById(this.containerId))},DragDropMarkersQuestion.prototype.bgImage=function(){return this.getRoot().find("img.dropbackground")},DragDropMarkersQuestion.prototype.handleDragStart=function(e){var thisQ=this,dragged=$(e.target).closest(".marker");if(dragDrop.prepare(e).start){if(dragged.addClass("beingdragged").css("transform",""),!!dragged.hasClass("unneeded")){var hiddenDrag=thisQ.getDragClone(dragged);hiddenDrag.length&&(hiddenDrag.addClass("active"),dragged.offset(hiddenDrag.offset()))}dragDrop.start(e,dragged,(function(){}),(function(x,y,dragged){thisQ.dragEnd(dragged)}))}},DragDropMarkersQuestion.prototype.dragEnd=function(dragged){var dragXY,placed=!1,choiceNo=this.getChoiceNoFromElement(dragged),bgRatio=this.bgRatio();if(dragged.data("pagex",dragged.offset().left).data("pagey",dragged.offset().top),dragXY=new Shapes.Point(dragged.data("pagex"),dragged.data("pagey")),this.coordsInBgImg(dragXY)){this.sendDragToDrop(dragged,!0),placed=!0;var bgImgXY=this.convertToBgImgXY(dragXY);bgImgXY=new Shapes.Point(bgImgXY.x/bgRatio,bgImgXY.y/bgRatio),dragged.data("originX",bgImgXY.x).data("originY",bgImgXY.y)}placed?this.cloneDragIfNeeded(dragged):(this.sendDragHome(dragged),this.removeDragIfNeeded(dragged)),this.saveCoordsForChoice(choiceNo)},DragDropMarkersQuestion.prototype.saveCoordsForChoice=function(choiceNo){var coords=[],items=this.getRoot().find("div.droparea span.marker.choice"+choiceNo),thiQ=this,bgRatio=this.bgRatio();items.length&&items.each((function(){var drag=$(this);if(!drag.hasClass("beingdragged")){drag.data("scaleRatio")!==bgRatio&&drag.data("pagex",drag.offset().left).data("pagey",drag.offset().top);var dragXY=new Shapes.Point(drag.data("pagex"),drag.data("pagey"));if(thiQ.coordsInBgImg(dragXY)){var bgImgXY=thiQ.convertToBgImgXY(dragXY);bgImgXY=new Shapes.Point(bgImgXY.x/bgRatio,bgImgXY.y/bgRatio),coords[coords.length]=bgImgXY}}})),this.getRoot().find("input.choice"+choiceNo).val(coords.join(";")),this.isQuestionInteracted()&&(questionManager.handleFormDirty(),this.questionAnswer=this.getQuestionAnsweredValues())},DragDropMarkersQuestion.prototype.handleKeyPress=function(e){var drag=$(e.target).closest(".marker"),point=new Shapes.Point(drag.offset().left,drag.offset().top),choiceNo=this.getChoiceNoFromElement(drag);switch(e.keyCode){case keys.arrowLeft:case 65:point.x-=1;break;case keys.arrowRight:case 68:point.x+=1;break;case keys.arrowDown:case 83:point.y+=1;break;case keys.arrowUp:case 87:point.y-=1;break;case keys.space:case keys.escape:point=null;break;default:return}if(e.preventDefault(),null!==point){point=this.constrainToBgImg(point),drag.offset({left:point.x,top:point.y}),drag.data("pagex",drag.offset().left).data("pagey",drag.offset().top);var dragXY=this.convertToBgImgXY(new Shapes.Point(drag.data("pagex"),drag.data("pagey")));if(drag.data("originX",dragXY.x/this.bgRatio()).data("originY",dragXY.y/this.bgRatio()),this.coordsInBgImg(new Shapes.Point(drag.offset().left,drag.offset().top))&&drag.hasClass("unneeded")){this.sendDragToDrop(drag,!0);var hiddenDrag=this.getDragClone(drag);hiddenDrag.length&&hiddenDrag.addClass("active"),this.cloneDragIfNeeded(drag)}}else drag.css("left","").css("top",""),drag.data("pagex",drag.offset().left).data("pagey",drag.offset().top),this.sendDragHome(drag),this.removeDragIfNeeded(drag);drag.focus(),this.saveCoordsForChoice(choiceNo)},DragDropMarkersQuestion.prototype.constrainToBgImg=function(windowxy){var bgImg=this.bgImage(),bgImgXY=this.convertToBgImgXY(windowxy);return bgImgXY.x=Math.max(0,bgImgXY.x),bgImgXY.y=Math.max(0,bgImgXY.y),bgImgXY.x=Math.min(bgImg.width(),bgImgXY.x),bgImgXY.y=Math.min(bgImg.height(),bgImgXY.y),this.convertToWindowXY(bgImgXY)},DragDropMarkersQuestion.prototype.getChoiceNoFromElement=function(node){return Number(this.getClassnameNumericSuffix(node,"choice"))},DragDropMarkersQuestion.prototype.getClassnameNumericSuffix=function(node,prefix){var classes=$(node).attr("class");if(void 0!==classes&&""!==classes)for(var classesarr=classes.split(" "),index=0;index<classesarr.length;index++){if(new RegExp("^"+prefix+"([0-9])+$").test(classesarr[index])){var match=new RegExp("([0-9])+$").exec(classesarr[index]);return Number(match[0])}}return null},DragDropMarkersQuestion.prototype.handleResize=function(){var thisQ=this,bgRatio=this.bgRatio();this.isPrinting&&(bgRatio=1),this.getRoot().find("div.droparea .marker").not(".beingdragged").each((function(key,drag){$(drag).css("left",parseFloat($(drag).data("originX"))*parseFloat(bgRatio)).css("top",parseFloat($(drag).data("originY"))*parseFloat(bgRatio)),thisQ.handleElementScale(drag,"left top")})),this.getRoot().find("div.droparea svg.dropzones").width(this.bgImage().width()).height(this.bgImage().height());for(var dropZoneNo=0;dropZoneNo<this.visibleDropZones.length;dropZoneNo++){var originCoords=thisQ.visibleDropZones[dropZoneNo].coords,shape=thisQ.shapes[dropZoneNo],shapeSVG=thisQ.shapeSVGs[dropZoneNo];shape.parse(originCoords,bgRatio),shape.updateSvg(shapeSVG);var handles=shape.getHandlePositions(),markerSpan=this.getRoot().find("div.ddarea div.markertexts span.markertext"+dropZoneNo);markerSpan.css("left",handles.moveHandle.x-markerSpan.outerWidth()/2-4).css("top",handles.moveHandle.y-markerSpan.outerHeight()/2),thisQ.handleElementScale(markerSpan,"center")}},DragDropMarkersQuestion.prototype.cloneDrags=function(){var thisQ=this;this.getRoot().find("div.draghomes span.marker").each((function(index,draghome){var drag=$(draghome),placeHolder=drag.clone();placeHolder.removeClass(),placeHolder.addClass("marker"),placeHolder.addClass("choice"+thisQ.getChoiceNoFromElement(drag)),placeHolder.addClass(thisQ.getDragNoClass(drag,!1)),placeHolder.addClass("dragplaceholder"),drag.before(placeHolder)}))},DragDropMarkersQuestion.prototype.getDragNo=function(drag){return this.getClassnameNumericSuffix(drag,"dragno")},DragDropMarkersQuestion.prototype.getDragNoClass=function(drag,includeSelector){var className="dragno"+this.getDragNo(drag);return this.isInfiniteDrag(drag)&&(className="infinite"),includeSelector?"."+className:className},DragDropMarkersQuestion.prototype.getDragClone=function(drag){return this.getRoot().find(".draghomes span.marker.choice"+this.getChoiceNoFromElement(drag)+this.getDragNoClass(drag,!0)+".dragplaceholder")},DragDropMarkersQuestion.prototype.dropArea=function(){return this.getRoot().find("div.droparea")},DragDropMarkersQuestion.prototype.sendDragHome=function(drag){drag.removeClass("beingdragged").addClass("unneeded").css("top","").css("left","").css("transform","");var placeHolder=this.getDragClone(drag);placeHolder.after(drag),placeHolder.removeClass("active")},DragDropMarkersQuestion.prototype.sendDragToDrop=function(drag,isScaling){let initialLoad=arguments.length>2&&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)&&displayedDragsInDropArea<noOfDrags)&&0===displayedDragsInDragHomes){var dragClone=drag.clone();dragClone.addClass("unneeded").css("top","").css("left","").css("transform",""),this.getDragClone(drag).removeClass("active").after(dragClone),questionManager.addEventHandlersToMarker(dragClone)}},DragDropMarkersQuestion.prototype.removeDragIfNeeded=function(drag){for(var dragsInHome=this.getRoot().find("div.draghomes .marker.choice"+this.getChoiceNoFromElement(drag)+this.getDragNoClass(drag,!0)).not(".dragplaceholder"),displayedDrags=dragsInHome.length;displayedDrags>1;)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")},DragDropMarkersQuestion.prototype.waitForAllImagesToBeLoaded=function(){this.allImagesLoaded||(null!==this.imageLoadingTimeoutId&&clearTimeout(this.imageLoadingTimeoutId),this.getNotYetLoadedImages().length>0?this.imageLoadingTimeoutId=setTimeout((function(){this.waitForAllImagesToBeLoaded()}),100):(this.allImagesLoaded=!0,this.cloneDrags(),this.repositionDrags(),this.drawDropzones()))},DragDropMarkersQuestion.prototype.getNotYetLoadedImages=function(){return this.getRoot().find(".ddmarker img.dropbackground").not((function(i,imgNode){return this.imageIsLoaded(imgNode)}))},DragDropMarkersQuestion.prototype.imageIsLoaded=function(imgElement){return imgElement.complete&&0!==imgElement.naturalHeight};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}}));
+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){var thisQ=this;this.containerId=containerId,this.visibleDropZones=visibleDropZones,this.shapes=[],this.shapeSVGs=[],this.isPrinting=!1,this.questionAnswer={},readOnly&&this.getRoot().addClass("qtype_ddmarker-readonly"),thisQ.allImagesLoaded=!1,thisQ.getNotYetLoadedImages().one("load",(function(){thisQ.waitForAllImagesToBeLoaded()})),thisQ.waitForAllImagesToBeLoaded()}DragDropMarkersQuestion.prototype.drawDropzones=function(){if(this.visibleDropZones.length>0){var bgImage=this.bgImage();this.getRoot().find("div.dropzones").html('<svg xmlns="http://www.w3.org/2000/svg" class="dropzones" width="'+bgImage.outerWidth()+'" height="'+bgImage.outerHeight()+'"></svg>');for(var svg=this.getRoot().find("svg.dropzones"),nextColourIndex=0,dropZoneNo=0;dropZoneNo<this.visibleDropZones.length;dropZoneNo++){var colourClass="color"+nextColourIndex;nextColourIndex=(nextColourIndex+1)%8,this.addDropzone(svg,dropZoneNo,colourClass)}}},DragDropMarkersQuestion.prototype.addDropzone=function(svg,dropZoneNo,colourClass){var existingmarkertext,dropZone=this.visibleDropZones[dropZoneNo],shape=Shapes.make(dropZone.shape,""),bgRatio=this.bgRatio();if(shape.parse(dropZone.coords,bgRatio)){if((existingmarkertext=this.getRoot().find("div.markertexts span.markertext"+dropZoneNo)).length)""!==dropZone.markertext?existingmarkertext.html(dropZone.markertext):existingmarkertext.remove();else if(""!==dropZone.markertext){var classnames="markertext markertext"+dropZoneNo;this.getRoot().find("div.markertexts").append('<span class="'+classnames+'">'+dropZone.markertext+"</span>");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),imageCoords=thisQ.getImageCoords(input);if(imageCoords.length){var drag=thisQ.getRoot().find(".draghomes span.marker.choice"+choiceNo).not(".dragplaceholder");drag.remove();for(var i=0;i<imageCoords.length;i++){var dragInDrop=drag.clone();const screenCoords=thisQ.convertToWindowXY(imageCoords[i]);dragInDrop.data("pagex",screenCoords.x).data("pagey",screenCoords.y),dragInDrop.data("imageCoords",imageCoords[i]),dragInDrop.data("scaleRatio",1),thisQ.sendDragToDrop(dragInDrop,!1,!0)}thisQ.getDragClone(drag).addClass("active"),thisQ.cloneDragIfNeeded(drag)}})),thisQ.questionAnswer=thisQ.getQuestionAnsweredValues()},DragDropMarkersQuestion.prototype.getQuestionAnsweredValues=function(){let result={};return this.getRoot().find("input.choices").each(((i,inputNode)=>{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.getImageCoords=function(inputNode){var imageCoords=[],val=$(inputNode).val();if(""!==val)for(var coordsStrings=val.split(";"),i=0;i<coordsStrings.length;i++)imageCoords[i]=Shapes.Point.parse(coordsStrings[i]);return imageCoords},DragDropMarkersQuestion.prototype.convertToWindowXY=function(point){var bgImage=this.bgImage();return point.offset(bgImage.offset().left+1,bgImage.offset().top+1)},DragDropMarkersQuestion.prototype.convertToBgImgXY=function(point){var bgImage=this.bgImage();return point.offset(-bgImage.offset().left-1,-bgImage.offset().top-1)},DragDropMarkersQuestion.prototype.coordsInBgImg=function(point){var bgImage=this.bgImage(),bgPosition=bgImage.offset();return point.x>=bgPosition.left&&point.x<bgPosition.left+bgImage.width()&&point.y>=bgPosition.top&&point.y<bgPosition.top+bgImage.height()},DragDropMarkersQuestion.prototype.getRoot=function(){return $(document.getElementById(this.containerId))},DragDropMarkersQuestion.prototype.bgImage=function(){return this.getRoot().find("img.dropbackground")},DragDropMarkersQuestion.prototype.handleDragStart=function(e){var thisQ=this,dragged=$(e.target).closest(".marker");if(dragDrop.prepare(e).start){if(dragged.addClass("beingdragged").css("transform",""),!!dragged.hasClass("unneeded")){var hiddenDrag=thisQ.getDragClone(dragged);hiddenDrag.length&&(hiddenDrag.addClass("active"),dragged.offset(hiddenDrag.offset()))}dragDrop.start(e,dragged,(function(){}),(function(x,y,dragged){thisQ.dragEnd(dragged)}))}},DragDropMarkersQuestion.prototype.dragEnd=function(dragged){var dragXY,placed=!1,choiceNo=this.getChoiceNoFromElement(dragged),bgRatio=this.bgRatio();if(dragged.data("pagex",dragged.offset().left).data("pagey",dragged.offset().top),dragXY=new Shapes.Point(dragged.data("pagex"),dragged.data("pagey")),this.coordsInBgImg(dragXY)){this.sendDragToDrop(dragged,!0),placed=!0,dragged.data("imageCoords")&&dragged.data("imageCoords",null);var bgImgXY=this.convertToBgImgXY(dragXY);bgImgXY=new Shapes.Point(bgImgXY.x/bgRatio,bgImgXY.y/bgRatio),dragged.data("originX",bgImgXY.x).data("originY",bgImgXY.y)}placed?this.cloneDragIfNeeded(dragged):(this.sendDragHome(dragged),this.removeDragIfNeeded(dragged)),this.saveCoordsForChoice(choiceNo)},DragDropMarkersQuestion.prototype.saveCoordsForChoice=function(choiceNo){let imageCoords=[];var items=this.getRoot().find("div.droparea span.marker.choice"+choiceNo),thiQ=this,bgRatio=this.bgRatio();items.length&&items.each((function(){var drag=$(this);if(drag.hasClass("beingdragged")||drag.data("imageCoords"))drag.data("imageCoords")&&(imageCoords[imageCoords.length]=drag.data("imageCoords"));else{drag.data("scaleRatio")!==bgRatio&&drag.data("pagex",drag.offset().left).data("pagey",drag.offset().top);var dragXY=new Shapes.Point(drag.data("pagex"),drag.data("pagey"));if(thiQ.coordsInBgImg(dragXY)){var bgImgXY=thiQ.convertToBgImgXY(dragXY);bgImgXY=new Shapes.Point(bgImgXY.x/bgRatio,bgImgXY.y/bgRatio),imageCoords[imageCoords.length]=bgImgXY}}})),this.getRoot().find("input.choice"+choiceNo).val(imageCoords.join(";")),this.isQuestionInteracted()&&(questionManager.handleFormDirty(),this.questionAnswer=this.getQuestionAnsweredValues())},DragDropMarkersQuestion.prototype.handleKeyPress=function(e){var drag=$(e.target).closest(".marker"),point=new Shapes.Point(drag.offset().left,drag.offset().top),choiceNo=this.getChoiceNoFromElement(drag);switch(e.keyCode){case keys.arrowLeft:case 65:point.x-=1;break;case keys.arrowRight:case 68:point.x+=1;break;case keys.arrowDown:case 83:point.y+=1;break;case keys.arrowUp:case 87:point.y-=1;break;case keys.space:case keys.escape:point=null;break;default:return}if(e.preventDefault(),null!==point){point=this.constrainToBgImg(point),drag.offset({left:point.x,top:point.y}),drag.data("pagex",drag.offset().left).data("pagey",drag.offset().top);var dragXY=this.convertToBgImgXY(new Shapes.Point(drag.data("pagex"),drag.data("pagey")));if(drag.data("originX",dragXY.x/this.bgRatio()).data("originY",dragXY.y/this.bgRatio()),this.coordsInBgImg(new Shapes.Point(drag.offset().left,drag.offset().top))&&drag.hasClass("unneeded")){this.sendDragToDrop(drag,!0);var hiddenDrag=this.getDragClone(drag);hiddenDrag.length&&hiddenDrag.addClass("active"),this.cloneDragIfNeeded(drag)}}else drag.css("left","").css("top",""),drag.data("pagex",drag.offset().left).data("pagey",drag.offset().top),this.sendDragHome(drag),this.removeDragIfNeeded(drag);drag.focus(),this.saveCoordsForChoice(choiceNo)},DragDropMarkersQuestion.prototype.constrainToBgImg=function(windowxy){var bgImg=this.bgImage(),bgImgXY=this.convertToBgImgXY(windowxy);return bgImgXY.x=Math.max(0,bgImgXY.x),bgImgXY.y=Math.max(0,bgImgXY.y),bgImgXY.x=Math.min(bgImg.width(),bgImgXY.x),bgImgXY.y=Math.min(bgImg.height(),bgImgXY.y),this.convertToWindowXY(bgImgXY)},DragDropMarkersQuestion.prototype.getChoiceNoFromElement=function(node){return Number(this.getClassnameNumericSuffix(node,"choice"))},DragDropMarkersQuestion.prototype.getClassnameNumericSuffix=function(node,prefix){var classes=$(node).attr("class");if(void 0!==classes&&""!==classes)for(var classesarr=classes.split(" "),index=0;index<classesarr.length;index++){if(new RegExp("^"+prefix+"([0-9])+$").test(classesarr[index])){var match=new RegExp("([0-9])+$").exec(classesarr[index]);return Number(match[0])}}return null},DragDropMarkersQuestion.prototype.handleResize=function(){var thisQ=this,bgRatio=this.bgRatio();this.isPrinting&&(bgRatio=1),this.getRoot().find("div.droparea .marker").not(".beingdragged").each((function(key,drag){$(drag).css("left",parseFloat($(drag).data("originX"))*parseFloat(bgRatio)).css("top",parseFloat($(drag).data("originY"))*parseFloat(bgRatio)),thisQ.handleElementScale(drag,"left top")})),this.getRoot().find("div.droparea svg.dropzones").width(this.bgImage().width()).height(this.bgImage().height());for(var dropZoneNo=0;dropZoneNo<this.visibleDropZones.length;dropZoneNo++){var originCoords=thisQ.visibleDropZones[dropZoneNo].coords,shape=thisQ.shapes[dropZoneNo],shapeSVG=thisQ.shapeSVGs[dropZoneNo];shape.parse(originCoords,bgRatio),shape.updateSvg(shapeSVG);var handles=shape.getHandlePositions(),markerSpan=this.getRoot().find("div.ddarea div.markertexts span.markertext"+dropZoneNo);markerSpan.css("left",handles.moveHandle.x-markerSpan.outerWidth()/2-4).css("top",handles.moveHandle.y-markerSpan.outerHeight()/2),thisQ.handleElementScale(markerSpan,"center")}},DragDropMarkersQuestion.prototype.cloneDrags=function(){var thisQ=this;this.getRoot().find("div.draghomes span.marker").each((function(index,draghome){var drag=$(draghome),placeHolder=drag.clone();placeHolder.removeClass(),placeHolder.addClass("marker"),placeHolder.addClass("choice"+thisQ.getChoiceNoFromElement(drag)),placeHolder.addClass(thisQ.getDragNoClass(drag,!1)),placeHolder.addClass("dragplaceholder"),drag.before(placeHolder)}))},DragDropMarkersQuestion.prototype.getDragNo=function(drag){return this.getClassnameNumericSuffix(drag,"dragno")},DragDropMarkersQuestion.prototype.getDragNoClass=function(drag,includeSelector){var className="dragno"+this.getDragNo(drag);return this.isInfiniteDrag(drag)&&(className="infinite"),includeSelector?"."+className:className},DragDropMarkersQuestion.prototype.getDragClone=function(drag){return this.getRoot().find(".draghomes span.marker.choice"+this.getChoiceNoFromElement(drag)+this.getDragNoClass(drag,!0)+".dragplaceholder")},DragDropMarkersQuestion.prototype.dropArea=function(){return this.getRoot().find("div.droparea")},DragDropMarkersQuestion.prototype.sendDragHome=function(drag){drag.removeClass("beingdragged").addClass("unneeded").css("top","").css("left","").css("transform","");var placeHolder=this.getDragClone(drag);placeHolder.after(drag),placeHolder.removeClass("active")},DragDropMarkersQuestion.prototype.sendDragToDrop=function(drag,isScaling){let initialLoad=arguments.length>2&&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)&&displayedDragsInDropArea<noOfDrags)&&0===displayedDragsInDragHomes){var dragClone=drag.clone();dragClone.addClass("unneeded").css("top","").css("left","").css("transform",""),this.getDragClone(drag).removeClass("active").after(dragClone),questionManager.addEventHandlersToMarker(dragClone)}},DragDropMarkersQuestion.prototype.removeDragIfNeeded=function(drag){for(var dragsInHome=this.getRoot().find("div.draghomes .marker.choice"+this.getChoiceNoFromElement(drag)+this.getDragNoClass(drag,!0)).not(".dragplaceholder"),displayedDrags=dragsInHome.length;displayedDrags>1;)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")},DragDropMarkersQuestion.prototype.waitForAllImagesToBeLoaded=function(){this.allImagesLoaded||(null!==this.imageLoadingTimeoutId&&clearTimeout(this.imageLoadingTimeoutId),this.getNotYetLoadedImages().length>0?this.imageLoadingTimeoutId=setTimeout((function(){this.waitForAllImagesToBeLoaded()}),100):(this.allImagesLoaded=!0,this.cloneDrags(),this.repositionDrags(),this.drawDropzones()))},DragDropMarkersQuestion.prototype.getNotYetLoadedImages=function(){return this.getRoot().find(".ddmarker img.dropbackground").not((function(i,imgNode){return this.imageIsLoaded(imgNode)}))},DragDropMarkersQuestion.prototype.imageIsLoaded=function(imgElement){return imgElement.complete&&0!==imgElement.naturalHeight};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 f03ff661aaf..14a7916e0c6 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 <http://www.gnu.org/licenses/>.\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.allImagesLoaded = false;\n        thisQ.getNotYetLoadedImages().one('load', function() {\n            thisQ.waitForAllImagesToBeLoaded();\n        });\n        thisQ.waitForAllImagesToBeLoaded();\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('<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"dropzones\" ' +\n                'width=\"' + bgImage.outerWidth() + '\" ' +\n                'height=\"' + bgImage.outerHeight() + '\"></svg>');\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('<span class=\"' + classnames + '\">' +\n                dropZone.markertext + '</span>');\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     * 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    DragDropMarkersQuestion.prototype.waitForAllImagesToBeLoaded = function() {\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                this.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        this.cloneDrags();\n        this.repositionDrags();\n        this.drawDropzones();\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    DragDropMarkersQuestion.prototype.getNotYetLoadedImages = function() {\n        return this.getRoot().find('.ddmarker img.dropbackground').not(function(i, imgNode) {\n            return this.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    DragDropMarkersQuestion.prototype.imageIsLoaded = function(imgElement) {\n        return imgElement.complete && imgElement.naturalHeight !== 0;\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","thisQ","this","shapes","shapeSVGs","isPrinting","questionAnswer","getRoot","addClass","allImagesLoaded","getNotYetLoadedImages","one","waitForAllImagesToBeLoaded","prototype","drawDropzones","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","repositionDrags","root","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","cloneDrags","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","imageLoadingTimeoutId","clearTimeout","setTimeout","imgNode","imageIsLoaded","imgElement","complete","naturalHeight","eventHandlersInitialised","markerEventHandlersInitialised","isKeyboardNavigation","questions","init","setupEventHandlers","hasOwnProperty","questionContainer","classList","contains","window","on","handleWindowResize","addEventListener","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,sBAChDC,MAAQC,UACPJ,YAAcA,iBACdE,iBAAmBA,sBACnBG,OAAS,QACTC,UAAY,QACZC,YAAa,OACbC,eAAiB,GAClBP,eACKQ,UAAUC,SAAS,2BAE5BP,MAAMQ,iBAAkB,EACxBR,MAAMS,wBAAwBC,IAAI,QAAQ,WACtCV,MAAMW,gCAEVX,MAAMW,6BAMVf,wBAAwBgB,UAAUC,cAAgB,cAC1CZ,KAAKF,iBAAiBe,OAAS,EAAG,KAC9BC,QAAUd,KAAKc,eAEdT,UAAUU,KAAK,iBAAiBC,KAAK,oEAC1BF,QAAQG,aADkB,aAEzBH,QAAQI,cAAgB,oBACrCC,IAAMnB,KAAKK,UAAUU,KAAK,iBAE1BK,gBAAkB,EACbC,WAAa,EAAGA,WAAarB,KAAKF,iBAAiBe,OAAQQ,aAAc,KAC1EC,YAAc,QAAUF,gBAC5BA,iBAAmBA,gBAAkB,GAAK,OACrCG,YAAYJ,IAAKE,WAAYC,gBAY9C3B,wBAAwBgB,UAAUY,YAAc,SAASJ,IAAKE,WAAYC,iBAGlEE,mBAFAC,SAAWzB,KAAKF,iBAAiBuB,YACjCK,MAAQlC,OAAOmC,KAAKF,SAASC,MAAO,IAEpCE,QAAU5B,KAAK4B,aACdF,MAAMG,MAAMJ,SAASK,OAAQF,cAIlCJ,mBAAqBxB,KAAKK,UAAUU,KAAK,kCAAoCM,aACtDR,OACS,KAAxBY,SAASM,WACTP,mBAAmBR,KAAKS,SAASM,YAEjCP,mBAAmBQ,cAEpB,GAA4B,KAAxBP,SAASM,WAAmB,KAC/BE,WAAa,wBAA0BZ,gBACtChB,UAAUU,KAAK,mBAAmBmB,OAAO,gBAAkBD,WAAa,KACzER,SAASM,WAAa,eACtBI,WAAanC,KAAKK,UAAUU,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,kBAExCrB,OAAOD,KAAKC,OAAOY,QAAUa,WAC7BxB,UAAUF,KAAKE,UAAUW,QAAUoC,WAQ5CtD,wBAAwBgB,UAAUyC,gBAAkB,eAC5CC,KAAOrD,KAAKK,UACZN,MAAQC,KAEZqD,KAAKtC,KAAK,yBAAyBuC,IAAI,oBAAoBC,MAAK,SAASC,IAAKC,MAC1EnE,EAAEmE,MAAMnD,SAAS,eAGrB+C,KAAKtC,KAAK,iBAAiBwC,MAAK,SAASC,IAAKE,WACtCC,SAAW5D,MAAM6D,uBAAuBF,OACxC5B,OAAS/B,MAAM8D,UAAUH,UACzB5B,OAAOjB,OAAQ,KACXiD,KAAO/D,MAAMM,UAAUU,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,GAC9B7C,MAAMmE,eAAeF,YAAY,GAAO,GAE5CjE,MAAMoE,aAAaL,MAAMxD,SAAS,UAClCP,MAAMqE,kBAAkBN,UAKhC/D,MAAMK,eAAiBL,MAAMsE,6BAQjC1E,wBAAwBgB,UAAU0D,0BAA4B,eACtDC,OAAS,eACRjE,UAAUU,KAAK,iBAAiBwC,MAAK,CAACQ,EAAGQ,aAC1CD,OAAOC,UAAUC,IAAMD,UAAUE,SAG9BH,QAQX3E,wBAAwBgB,UAAU+D,qBAAuB,iBAC/CC,UAAY3E,KAAKI,eACjBwE,UAAY5E,KAAKqE,gCACnBQ,cAAe,SAGfC,KAAKC,UAAUH,aAAeE,KAAKC,UAAUJ,YAC7CE,cAAe,EACRA,eAGXG,OAAOvF,KAAKmF,WAAWK,SAAQzB,MACvBoB,UAAUpB,OAASmB,UAAUnB,OAC7BqB,cAAe,MAIhBA,eAYXlF,wBAAwBgB,UAAUkD,UAAY,SAASU,eAC/CzC,OAAS,GACToD,IAAM5F,EAAEiF,WAAWW,SACX,KAARA,YACIC,cAAgBD,IAAIE,MAAM,KACrBrB,EAAI,EAAGA,EAAIoB,cAActE,OAAQkD,IACtCjC,OAAOiC,GAAK/D,KAAKqF,kBAAkB7F,OAAO8F,MAAMzD,MAAMsD,cAAcpB,YAGrEjC,QAUXnC,wBAAwBgB,UAAU0E,kBAAoB,SAASE,WACvDzE,QAAUd,KAAKc,iBAKZyE,MAAMC,OAAO1E,QAAQ0E,SAAS1C,KAAO,EAAGhC,QAAQ0E,SAASzC,IAAM,IAU1EpD,wBAAwBgB,UAAU8E,iBAAmB,SAASF,WACtDzE,QAAUd,KAAKc,iBACZyE,MAAMC,QAAQ1E,QAAQ0E,SAAS1C,KAAO,GAAIhC,QAAQ0E,SAASzC,IAAM,IAS5EpD,wBAAwBgB,UAAU+E,cAAgB,SAASH,WACnDzE,QAAUd,KAAKc,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,UAO3ElG,wBAAwBgB,UAAUN,QAAU,kBACjCf,EAAEwG,SAASC,eAAe/F,KAAKJ,eAO1CD,wBAAwBgB,UAAUG,QAAU,kBACjCd,KAAKK,UAAUU,KAAK,uBAG/BpB,wBAAwBgB,UAAUqF,gBAAkB,SAASC,OACrDlG,MAAQC,KACRkG,QAAU5G,EAAE2G,EAAEE,QAAQC,QAAQ,cAEvB7G,SAAS8G,QAAQJ,GAClBK,UAIVJ,QAAQ5F,SAAS,gBAAgBqC,IAAI,YAAa,MAEpCuD,QAAQK,SAAS,YAClB,KACLC,WAAazG,MAAMoE,aAAa+B,SAChCM,WAAW3F,SACX2F,WAAWlG,SAAS,UACpB4F,QAAQV,OAAOgB,WAAWhB,WAIlCjG,SAAS+G,MAAML,EAAGC,SAAS,eAExB,SAAS1D,EAAGE,EAAGwD,SACdnG,MAAM0G,QAAQP,cAQtBvG,wBAAwBgB,UAAU8F,QAAU,SAASP,aAI7CQ,OAHAC,QAAS,EACThD,SAAW3D,KAAK4D,uBAAuBsC,SACvCtE,QAAU5B,KAAK4B,aAGnBsE,QAAQtD,KAAK,QAASsD,QAAQV,SAAS1C,MAAMF,KAAK,QAASsD,QAAQV,SAASzC,KAC5E2D,OAAS,IAAIlH,OAAO8F,MAAMY,QAAQtD,KAAK,SAAUsD,QAAQtD,KAAK,UAC1D5C,KAAK0F,cAAcgB,QAAS,MACvBxC,eAAegC,SAAS,GAC7BS,QAAS,MAILC,QAAU5G,KAAKyF,iBAAiBiB,QACpCE,QAAU,IAAIpH,OAAO8F,MAAMsB,QAAQpE,EAAIZ,QAASgF,QAAQlE,EAAId,SAC5DsE,QAAQtD,KAAK,UAAWgE,QAAQpE,GAAGI,KAAK,UAAWgE,QAAQlE,GAG1DiE,YAIIvC,kBAAkB8B,eAHlBW,aAAaX,cACbY,mBAAmBZ,eAKvBa,oBAAoBpD,WAO7BhE,wBAAwBgB,UAAUoG,oBAAsB,SAASpD,cACzD7B,OAAS,GACTkF,MAAQhH,KAAKK,UAAUU,KAAK,kCAAoC4C,UAChEsD,KAAOjH,KACP4B,QAAU5B,KAAK4B,UAEfoF,MAAMnG,QACNmG,MAAMzD,MAAK,eACHO,KAAOxE,EAAEU,UACR8D,KAAKyC,SAAS,gBAAiB,CAC5BzC,KAAKlB,KAAK,gBAAkBhB,SAE5BkC,KAAKlB,KAAK,QAASkB,KAAK0B,SAAS1C,MAAMF,KAAK,QAASkB,KAAK0B,SAASzC,SAEnE2D,OAAS,IAAIlH,OAAO8F,MAAMxB,KAAKlB,KAAK,SAAUkB,KAAKlB,KAAK,aACxDqE,KAAKvB,cAAcgB,QAAS,KACxBE,QAAUK,KAAKxB,iBAAiBiB,QACpCE,QAAU,IAAIpH,OAAO8F,MAAMsB,QAAQpE,EAAIZ,QAASgF,QAAQlE,EAAId,SAC5DE,OAAOA,OAAOjB,QAAU+F,kBAMnCvG,UAAUU,KAAK,eAAiB4C,UAAUuB,IAAIpD,OAAOoF,KAAK,MAC3DlH,KAAK0E,yBAELyC,gBAAgBC,uBAEXhH,eAAiBJ,KAAKqE,8BAQnC1E,wBAAwBgB,UAAU0G,eAAiB,SAASpB,OACpDnC,KAAOxE,EAAE2G,EAAEE,QAAQC,QAAQ,WAC3Bb,MAAQ,IAAI/F,OAAO8F,MAAMxB,KAAK0B,SAAS1C,KAAMgB,KAAK0B,SAASzC,KAC3DY,SAAW3D,KAAK4D,uBAAuBE,aAEnCmC,EAAEqB,cACD7H,KAAK8H,eACL,GACDhC,MAAM/C,GAAK,aAEV/C,KAAK+H,gBACL,GACDjC,MAAM/C,GAAK,aAEV/C,KAAKgI,eACL,GACDlC,MAAM7C,GAAK,aAEVjD,KAAKiI,aACL,GACDnC,MAAM7C,GAAK,aAEVjD,KAAKkI,WACLlI,KAAKmI,OACNrC,MAAQ,6BAKhBU,EAAE4B,iBAEY,OAAVtC,MAAgB,CAChBA,MAAQvF,KAAK8H,iBAAiBvC,OAC9BzB,KAAK0B,OAAO,MAASD,MAAM/C,MAAU+C,MAAM7C,IAC3CoB,KAAKlB,KAAK,QAASkB,KAAK0B,SAAS1C,MAAMF,KAAK,QAASkB,KAAK0B,SAASzC,SAC/D2D,OAAS1G,KAAKyF,iBAAiB,IAAIjG,OAAO8F,MAAMxB,KAAKlB,KAAK,SAAUkB,KAAKlB,KAAK,cAClFkB,KAAKlB,KAAK,UAAW8D,OAAOlE,EAAIxC,KAAK4B,WAAWgB,KAAK,UAAW8D,OAAOhE,EAAI1C,KAAK4B,WAC5E5B,KAAK0F,cAAc,IAAIlG,OAAO8F,MAAMxB,KAAK0B,SAAS1C,KAAMgB,KAAK0B,SAASzC,OAClEe,KAAKyC,SAAS,YAAa,MACtBrC,eAAeJ,MAAM,OACtB0C,WAAaxG,KAAKmE,aAAaL,MAC/B0C,WAAW3F,QACX2F,WAAWlG,SAAS,eAEnB8D,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,WAS7BhE,wBAAwBgB,UAAUmH,iBAAmB,SAASE,cACtDC,MAAQjI,KAAKc,UACb8F,QAAU5G,KAAKyF,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,GACtC1C,KAAKqF,kBAAkBuB,UASlCjH,wBAAwBgB,UAAUiD,uBAAyB,SAASyE,aACzDC,OAAOtI,KAAKuI,0BAA0BF,KAAM,YAUvD1I,wBAAwBgB,UAAU4H,0BAA4B,SAASF,KAAMG,YACrEC,QAAUnJ,EAAE+I,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,MAMXrJ,wBAAwBgB,UAAUuI,aAAe,eACzCnJ,MAAQC,KACR4B,QAAU5B,KAAK4B,UACf5B,KAAKG,aACLyB,QAAU,QAGTvB,UAAUU,KAAK,wBAAwBuC,IAAI,iBAAiBC,MAAK,SAASC,IAAKM,MAChFxE,EAAEwE,MACGnB,IAAI,OAAQwG,WAAW7J,EAAEwE,MAAMlB,KAAK,YAAcuG,WAAWvH,UAC7De,IAAI,MAAOwG,WAAW7J,EAAEwE,MAAMlB,KAAK,YAAcuG,WAAWvH,UACjE7B,MAAMiD,mBAAmBc,KAAM,oBAG9BzD,UAAUU,KAAK,8BACf6E,MAAM5F,KAAKc,UAAU8E,SACrBC,OAAO7F,KAAKc,UAAU+E,cAEtB,IAAIxE,WAAa,EAAGA,WAAarB,KAAKF,iBAAiBe,OAAQQ,aAAc,KAE1E+H,aADWrJ,MAAMD,iBAAiBuB,YACVS,OACxBJ,MAAQ3B,MAAME,OAAOoB,YACrB4B,SAAWlD,MAAMG,UAAUmB,YAC/BK,MAAMG,MAAMuH,aAAcxH,SAC1BF,MAAM2H,UAAUpG,cAEZb,QAAUV,MAAMW,qBAChBiH,WAAatJ,KAAKK,UAAUU,KAAK,6CAA+CM,YACpFiI,WACK3G,IAAI,OAAQP,QAAQG,WAAWC,EAAK8G,WAAWrI,aAAe,EAAK,GACnE0B,IAAI,MAAOP,QAAQG,WAAWG,EAAK4G,WAAWpI,cAAgB,GACnEnB,MAAMiD,mBAAmBsG,WAAY,YAO7C3J,wBAAwBgB,UAAU4I,WAAa,eACvCxJ,MAAQC,UACPK,UAAUU,KAAK,6BAA6BwC,MAAK,SAASsF,MAAOW,cAC9D1F,KAAOxE,EAAEkK,UACTC,YAAc3F,KAAKG,QACvBwF,YAAYC,cACZD,YAAYnJ,SAAS,UACrBmJ,YAAYnJ,SAAS,SAAWP,MAAM6D,uBAAuBE,OAC7D2F,YAAYnJ,SAASP,MAAM4J,eAAe7F,MAAM,IAChD2F,YAAYnJ,SAAS,mBACrBwD,KAAK8F,OAAOH,iBAUpB9J,wBAAwBgB,UAAUkJ,UAAY,SAAS/F,aAC5C9D,KAAKuI,0BAA0BzE,KAAM,WAUhDnE,wBAAwBgB,UAAUgJ,eAAiB,SAAS7F,KAAMgG,qBAC1DC,UAAY,SAAW/J,KAAK6J,UAAU/F,aACtC9D,KAAKgK,eAAelG,QACpBiG,UAAY,YAGZD,gBACO,IAAMC,UAGVA,WASXpK,wBAAwBgB,UAAUwD,aAAe,SAASL,aAC/C9D,KAAKK,UAAUU,KAAK,gCACXf,KAAK4D,uBAAuBE,MAAQ9D,KAAK2J,eAAe7F,MAAM,GAAQ,qBAO1FnE,wBAAwBgB,UAAUsJ,SAAW,kBAClCjK,KAAKK,UAAUU,KAAK,iBAQ/BpB,wBAAwBgB,UAAUkG,aAAe,SAAS/C,MACtDA,KAAK4F,YAAY,gBACZpJ,SAAS,YACTqC,IAAI,MAAO,IACXA,IAAI,OAAQ,IACZA,IAAI,YAAa,QAClB8G,YAAczJ,KAAKmE,aAAaL,MACpC2F,YAAYS,MAAMpG,MAClB2F,YAAYC,YAAY,WAU5B/J,wBAAwBgB,UAAUuD,eAAiB,SAASJ,KAAMqG,eAAWC,wEACrEH,SAAWjK,KAAKiK,WAChBrI,QAAU5B,KAAK4B,UACnBkC,KAAK4F,YAAY,gBAAgBA,YAAY,gBACzChD,OAAS1G,KAAKyF,iBAAiB,IAAIjG,OAAO8F,MAAMxB,KAAKlB,KAAK,SAAUkB,KAAKlB,KAAK,WAC9EuH,WACArG,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,UAG1DwI,aAEDtG,KAAKlB,KAAK,aAAchB,SAE5BqI,SAAS/H,OAAO4B,WACXd,mBAAmBc,KAAM,aAQlCnE,wBAAwBgB,UAAUyD,kBAAoB,SAASN,UACvDS,UAAYvE,KAAKqK,SAASvG,MAC1BwG,UAAYhC,OAAOtI,KAAKuI,0BAA0BhE,UAAW,cAC7DgG,yBAA2BvK,KAAKK,UAAUU,KAAK,8BAC3Cf,KAAK4D,uBAAuBE,MAAQ9D,KAAK2J,eAAe7F,MAAM,IAAOjD,OACzE2J,0BAA4BxK,KAAKK,UAAUU,KAAK,+BAC5Cf,KAAK4D,uBAAuBE,MAAQ9D,KAAK2J,eAAe7F,MAAM,IAAOR,IAAI,oBAAoBzC,WAEhGb,KAAKgK,eAAelG,QAChB9D,KAAKgK,eAAelG,OAASyG,yBAA2BD,YAA4C,IAA9BE,0BAAiC,KACxGC,UAAY3G,KAAKG,QACrBwG,UAAUnK,SAAS,YACdqC,IAAI,MAAO,IACXA,IAAI,OAAQ,IACZA,IAAI,YAAa,SACjBwB,aAAaL,MACb4F,YAAY,UACZQ,MAAMO,WACXtD,gBAAgBuD,yBAAyBD,aASjD9K,wBAAwBgB,UAAUmG,mBAAqB,SAAShD,cACxD6G,YAAc3K,KAAKK,UAAUU,KAAK,+BAClCf,KAAK4D,uBAAuBE,MAAQ9D,KAAK2J,eAAe7F,MAAM,IAAOR,IAAI,oBACzEsH,eAAiBD,YAAY9J,OAC1B+J,eAAiB,GACpBD,YAAYE,QAAQ7I,SACpB4I,kBAURjL,wBAAwBgB,UAAU0J,SAAW,SAASvG,UAC9CH,SAAW3D,KAAK4D,uBAAuBE,aACpC9D,KAAKK,UAAUU,KAAK,uBAAyB4C,WAQxDhE,wBAAwBgB,UAAUiB,QAAU,eACpCqG,MAAQjI,KAAKc,UACbgK,kBAAoB7C,MAAM8C,IAAI,GAAGC,oBACd/C,MAAMrC,QAEHkF,mBAS9BnL,wBAAwBgB,UAAUqC,mBAAqB,SAASiI,QAASC,UACjEtJ,QAAUuH,WAAWnJ,KAAK4B,WAC1B5B,KAAKG,aACLyB,QAAU,GAEdtC,EAAE2L,SAAStI,IAAI,qBACU,SAAWf,QAAU,qBACxB,SAAWA,QAAU,oBACtB,SAAWA,QAAU,mBACtB,SAAWA,QAAU,cACxB,SAAWA,QAAU,uBACdsJ,QAS5BvL,wBAAwBgB,UAAUqJ,eAAiB,SAASlG,aACjDA,KAAKyC,SAAS,aASzB5G,wBAAwBgB,UAAUD,2BAA6B,WAIvDV,KAAKO,kBAK0B,OAA/BP,KAAKmL,uBACLC,aAAapL,KAAKmL,uBAMlBnL,KAAKQ,wBAAwBK,OAAS,OACjCsK,sBAAwBE,YAAW,gBAC/B3K,+BACN,WAKFH,iBAAkB,OAClBgJ,kBACAnG,uBACAxC,mBAQTjB,wBAAwBgB,UAAUH,sBAAwB,kBAC/CR,KAAKK,UAAUU,KAAK,gCAAgCuC,KAAI,SAASS,EAAGuH,gBAChEtL,KAAKuL,cAAcD,aAUlC3L,wBAAwBgB,UAAU4K,cAAgB,SAASC,mBAChDA,WAAWC,UAAyC,IAA7BD,WAAWE,mBASzCvE,gBAAkB,CAKlBwE,0BAA0B,EAM1BC,+BAAgC,GAKhCzL,YAAY,EAKZ0L,sBAAsB,EAKtBC,UAAW,GASXC,KAAM,SAASnM,YAAaC,SAAUC,qBAClCqH,gBAAgB2E,UAAUlM,aACtB,IAAID,wBAAwBC,YAAaC,SAAUC,kBAClDqH,gBAAgBwE,2BACjBxE,gBAAgB6E,qBAChB7E,gBAAgBwE,0BAA2B,IAE1CxE,gBAAgByE,+BAA+BK,eAAerM,aAAc,CAC7EuH,gBAAgByE,+BAA+BhM,cAAe,MAE1DsM,kBAAoBpG,SAASC,eAAenG,aAC5CsM,kBAAkBC,UAAUC,SAAS,cACpCF,kBAAkBC,UAAUC,SAAS,6BAEtCjF,gBAAgBuD,yBAAyBpL,EAAE4M,mBAAmBnL,KAAK,0BACnEoG,gBAAgBuD,yBAAyBpL,EAAE4M,mBAAmBnL,KAAK,4BAQ/EiL,mBAAoB,WAChB1M,EAAE+M,QAAQC,GAAG,UAAU,WACnBnF,gBAAgBoF,oBAAmB,MAEvCF,OAAOG,iBAAiB,eAAe,WACnCrF,gBAAgBhH,YAAa,EAC7BgH,gBAAgBoF,mBAAmBpF,gBAAgBhH,eAEvDkM,OAAOG,iBAAiB,cAAc,WAClCrF,gBAAgBhH,YAAa,EAC7BgH,gBAAgBoF,mBAAmBpF,gBAAgBhH,eAEvDkL,YAAW,WACPlE,gBAAgBsF,2BACjB,MAQP/B,yBAA0B,SAASO,SAC/BA,QACKqB,GAAG,uBAAwBnF,gBAAgBnB,iBAC3CsG,GAAG,mBAAoBnF,gBAAgBE,gBACvCqF,SAAQ,SAASzG,GACdkB,gBAAgBwF,oBAAoB1G,GAAG,MAE1C2G,UAAS,SAAS3G,GACfkB,gBAAgBwF,oBAAoB1G,GAAG,OAQnDD,gBAAiB,SAASC,GACtBA,EAAE4B,qBACEgF,SAAW1F,gBAAgB2F,oBAAoB7G,GAC/C4G,UACAA,SAAS7G,gBAAgBC,IAQjCoB,eAAgB,SAASpB,OACjB4G,SAAW1F,gBAAgB2F,oBAAoB7G,GAC/C4G,UACAA,SAASxF,eAAepB,IAQhCsG,mBAAoB,SAASpM,gBACpB,IAAIP,eAAeuH,gBAAgB2E,UAChC3E,gBAAgB2E,UAAUG,eAAerM,eACzCuH,gBAAgB2E,UAAUlM,aAAaO,WAAaA,WACpDgH,gBAAgB2E,UAAUlM,aAAasJ,iBAUnDyD,oBAAqB,SAAS1G,EAAG8G,cAC7B5F,gBAAgB0E,qBAAuBkB,cAQ3CN,uBAAwB,WACftF,gBAAgB0E,2BACZU,mBAAmBpF,gBAAgBhH,YAK5CkL,YAAW,WACPlE,gBAAgBsF,uBAAuBtF,gBAAgBhH,cACxD,MAQP2M,oBAAqB,SAAS7G,OACtBrG,YAAcN,EAAE2G,EAAE+G,eAAe5G,QAAQ,iBAAiBsC,KAAK,aAC5DvB,gBAAgB2E,UAAUlM,cAMrCwH,gBAAiB,iBACP6F,aAAenH,SAASC,eAAe,gBAC7CrG,kBAAkBwN,gBAAgBD,sBAOnC,CASHlB,KAAM5E,gBAAgB4E"}
\ 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 <http://www.gnu.org/licenses/>.\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.allImagesLoaded = false;\n        thisQ.getNotYetLoadedImages().one('load', function() {\n            thisQ.waitForAllImagesToBeLoaded();\n        });\n        thisQ.waitForAllImagesToBeLoaded();\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('<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"dropzones\" ' +\n                'width=\"' + bgImage.outerWidth() + '\" ' +\n                'height=\"' + bgImage.outerHeight() + '\"></svg>');\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('<span class=\"' + classnames + '\">' +\n                dropZone.markertext + '</span>');\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                imageCoords = thisQ.getImageCoords(input);\n            if (imageCoords.length) {\n                var drag = thisQ.getRoot().find('.draghomes' + ' span.marker' + '.choice' + choiceNo).not('.dragplaceholder');\n                drag.remove();\n                for (var i = 0; i < imageCoords.length; i++) {\n                    var dragInDrop = drag.clone();\n                    // Convert image coords to screen coords.\n                    const screenCoords = thisQ.convertToWindowXY(imageCoords[i]);\n                    dragInDrop.data('pagex', screenCoords.x).data('pagey', screenCoords.y);\n                    // Save image coords to the drag item so we can use it later.\n                    dragInDrop.data('imageCoords', imageCoords[i]);\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[]} image coordinates of however many copies of the drag item should be shown.\n     */\n    DragDropMarkersQuestion.prototype.getImageCoords = function(inputNode) {\n        var imageCoords = [],\n            val = $(inputNode).val();\n        if (val !== '') {\n            var coordsStrings = val.split(';');\n            for (var i = 0; i < coordsStrings.length; i++) {\n                imageCoords[i] = Shapes.Point.parse(coordsStrings[i]);\n            }\n        }\n        return imageCoords;\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            // Since we already move the drag item to new position.\n            // Remove the image coords if this drag item have it.\n            // We will get the new image coords for this drag item in saveCoordsForChoice.\n            if (dragged.data('imageCoords')) {\n                dragged.data('imageCoords', null);\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        let imageCoords = [];\n        var 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') && !drag.data('imageCoords')) {\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                        imageCoords[imageCoords.length] = bgImgXY;\n                    }\n                } else if (drag.data('imageCoords')) {\n                    imageCoords[imageCoords.length] = drag.data('imageCoords');\n                }\n            });\n        }\n\n        this.getRoot().find('input.choice' + choiceNo).val(imageCoords.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     * 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    DragDropMarkersQuestion.prototype.waitForAllImagesToBeLoaded = function() {\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                this.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        this.cloneDrags();\n        this.repositionDrags();\n        this.drawDropzones();\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    DragDropMarkersQuestion.prototype.getNotYetLoadedImages = function() {\n        return this.getRoot().find('.ddmarker img.dropbackground').not(function(i, imgNode) {\n            return this.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    DragDropMarkersQuestion.prototype.imageIsLoaded = function(imgElement) {\n        return imgElement.complete && imgElement.naturalHeight !== 0;\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","thisQ","this","shapes","shapeSVGs","isPrinting","questionAnswer","getRoot","addClass","allImagesLoaded","getNotYetLoadedImages","one","waitForAllImagesToBeLoaded","prototype","drawDropzones","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","repositionDrags","root","not","each","key","item","input","choiceNo","getChoiceNoFromElement","imageCoords","getImageCoords","drag","i","dragInDrop","clone","screenCoords","convertToWindowXY","sendDragToDrop","getDragClone","cloneDragIfNeeded","getQuestionAnsweredValues","result","inputNode","id","value","isQuestionInteracted","oldAnswer","newAnswer","isInteracted","JSON","stringify","Object","forEach","val","coordsStrings","split","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","cloneDrags","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","imageLoadingTimeoutId","clearTimeout","setTimeout","imgNode","imageIsLoaded","imgElement","complete","naturalHeight","eventHandlersInitialised","markerEventHandlersInitialised","isKeyboardNavigation","questions","init","setupEventHandlers","hasOwnProperty","questionContainer","classList","contains","window","on","handleWindowResize","addEventListener","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,sBAChDC,MAAQC,UACPJ,YAAcA,iBACdE,iBAAmBA,sBACnBG,OAAS,QACTC,UAAY,QACZC,YAAa,OACbC,eAAiB,GAClBP,eACKQ,UAAUC,SAAS,2BAE5BP,MAAMQ,iBAAkB,EACxBR,MAAMS,wBAAwBC,IAAI,QAAQ,WACtCV,MAAMW,gCAEVX,MAAMW,6BAMVf,wBAAwBgB,UAAUC,cAAgB,cAC1CZ,KAAKF,iBAAiBe,OAAS,EAAG,KAC9BC,QAAUd,KAAKc,eAEdT,UAAUU,KAAK,iBAAiBC,KAAK,oEAC1BF,QAAQG,aADkB,aAEzBH,QAAQI,cAAgB,oBACrCC,IAAMnB,KAAKK,UAAUU,KAAK,iBAE1BK,gBAAkB,EACbC,WAAa,EAAGA,WAAarB,KAAKF,iBAAiBe,OAAQQ,aAAc,KAC1EC,YAAc,QAAUF,gBAC5BA,iBAAmBA,gBAAkB,GAAK,OACrCG,YAAYJ,IAAKE,WAAYC,gBAY9C3B,wBAAwBgB,UAAUY,YAAc,SAASJ,IAAKE,WAAYC,iBAGlEE,mBAFAC,SAAWzB,KAAKF,iBAAiBuB,YACjCK,MAAQlC,OAAOmC,KAAKF,SAASC,MAAO,IAEpCE,QAAU5B,KAAK4B,aACdF,MAAMG,MAAMJ,SAASK,OAAQF,cAIlCJ,mBAAqBxB,KAAKK,UAAUU,KAAK,kCAAoCM,aACtDR,OACS,KAAxBY,SAASM,WACTP,mBAAmBR,KAAKS,SAASM,YAEjCP,mBAAmBQ,cAEpB,GAA4B,KAAxBP,SAASM,WAAmB,KAC/BE,WAAa,wBAA0BZ,gBACtChB,UAAUU,KAAK,mBAAmBmB,OAAO,gBAAkBD,WAAa,KACzER,SAASM,WAAa,eACtBI,WAAanC,KAAKK,UAAUU,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,kBAExCrB,OAAOD,KAAKC,OAAOY,QAAUa,WAC7BxB,UAAUF,KAAKE,UAAUW,QAAUoC,WAQ5CtD,wBAAwBgB,UAAUyC,gBAAkB,eAC5CC,KAAOrD,KAAKK,UACZN,MAAQC,KAEZqD,KAAKtC,KAAK,yBAAyBuC,IAAI,oBAAoBC,MAAK,SAASC,IAAKC,MAC1EnE,EAAEmE,MAAMnD,SAAS,eAGrB+C,KAAKtC,KAAK,iBAAiBwC,MAAK,SAASC,IAAKE,WACtCC,SAAW5D,MAAM6D,uBAAuBF,OACxCG,YAAc9D,MAAM+D,eAAeJ,UACnCG,YAAYhD,OAAQ,KAChBkD,KAAOhE,MAAMM,UAAUU,KAAK,gCAA4C4C,UAAUL,IAAI,oBAC1FS,KAAK/B,aACA,IAAIgC,EAAI,EAAGA,EAAIH,YAAYhD,OAAQmD,IAAK,KACrCC,WAAaF,KAAKG,cAEhBC,aAAepE,MAAMqE,kBAAkBP,YAAYG,IACzDC,WAAWrB,KAAK,QAASuB,aAAa3B,GAAGI,KAAK,QAASuB,aAAazB,GAEpEuB,WAAWrB,KAAK,cAAeiB,YAAYG,IAG3CC,WAAWrB,KAAK,aAAc,GAC9B7C,MAAMsE,eAAeJ,YAAY,GAAO,GAE5ClE,MAAMuE,aAAaP,MAAMzD,SAAS,UAClCP,MAAMwE,kBAAkBR,UAKhChE,MAAMK,eAAiBL,MAAMyE,6BAQjC7E,wBAAwBgB,UAAU6D,0BAA4B,eACtDC,OAAS,eACRpE,UAAUU,KAAK,iBAAiBwC,MAAK,CAACS,EAAGU,aAC1CD,OAAOC,UAAUC,IAAMD,UAAUE,SAG9BH,QAQX9E,wBAAwBgB,UAAUkE,qBAAuB,iBAC/CC,UAAY9E,KAAKI,eACjB2E,UAAY/E,KAAKwE,gCACnBQ,cAAe,SAGfC,KAAKC,UAAUH,aAAeE,KAAKC,UAAUJ,YAC7CE,cAAe,EACRA,eAGXG,OAAO1F,KAAKsF,WAAWK,SAAQ5B,MACvBuB,UAAUvB,OAASsB,UAAUtB,OAC7BwB,cAAe,MAIhBA,eAYXrF,wBAAwBgB,UAAUmD,eAAiB,SAASY,eACpDb,YAAc,GACdwB,IAAM/F,EAAEoF,WAAWW,SACX,KAARA,YACIC,cAAgBD,IAAIE,MAAM,KACrBvB,EAAI,EAAGA,EAAIsB,cAAczE,OAAQmD,IACtCH,YAAYG,GAAKxE,OAAOgG,MAAM3D,MAAMyD,cAActB,WAGnDH,aAUXlE,wBAAwBgB,UAAUyD,kBAAoB,SAASqB,WACvD3E,QAAUd,KAAKc,iBAKZ2E,MAAMC,OAAO5E,QAAQ4E,SAAS5C,KAAO,EAAGhC,QAAQ4E,SAAS3C,IAAM,IAU1EpD,wBAAwBgB,UAAUgF,iBAAmB,SAASF,WACtD3E,QAAUd,KAAKc,iBACZ2E,MAAMC,QAAQ5E,QAAQ4E,SAAS5C,KAAO,GAAIhC,QAAQ4E,SAAS3C,IAAM,IAS5EpD,wBAAwBgB,UAAUiF,cAAgB,SAASH,WACnD3E,QAAUd,KAAKc,UACf+E,WAAa/E,QAAQ4E,gBAElBD,MAAMjD,GAAKqD,WAAW/C,MAAQ2C,MAAMjD,EAAIqD,WAAW/C,KAAOhC,QAAQgF,SAClEL,MAAM/C,GAAKmD,WAAW9C,KAAO0C,MAAM/C,EAAImD,WAAW9C,IAAMjC,QAAQiF,UAO3EpG,wBAAwBgB,UAAUN,QAAU,kBACjCf,EAAE0G,SAASC,eAAejG,KAAKJ,eAO1CD,wBAAwBgB,UAAUG,QAAU,kBACjCd,KAAKK,UAAUU,KAAK,uBAG/BpB,wBAAwBgB,UAAUuF,gBAAkB,SAASC,OACrDpG,MAAQC,KACRoG,QAAU9G,EAAE6G,EAAEE,QAAQC,QAAQ,cAEvB/G,SAASgH,QAAQJ,GAClBK,UAIVJ,QAAQ9F,SAAS,gBAAgBqC,IAAI,YAAa,MAEpCyD,QAAQK,SAAS,YAClB,KACLC,WAAa3G,MAAMuE,aAAa8B,SAChCM,WAAW7F,SACX6F,WAAWpG,SAAS,UACpB8F,QAAQV,OAAOgB,WAAWhB,WAIlCnG,SAASiH,MAAML,EAAGC,SAAS,eAExB,SAAS5D,EAAGE,EAAG0D,SACdrG,MAAM4G,QAAQP,cAQtBzG,wBAAwBgB,UAAUgG,QAAU,SAASP,aAI7CQ,OAHAC,QAAS,EACTlD,SAAW3D,KAAK4D,uBAAuBwC,SACvCxE,QAAU5B,KAAK4B,aAGnBwE,QAAQxD,KAAK,QAASwD,QAAQV,SAAS5C,MAAMF,KAAK,QAASwD,QAAQV,SAAS3C,KAC5E6D,OAAS,IAAIpH,OAAOgG,MAAMY,QAAQxD,KAAK,SAAUwD,QAAQxD,KAAK,UAC1D5C,KAAK4F,cAAcgB,QAAS,MACvBvC,eAAe+B,SAAS,GAC7BS,QAAS,EAILT,QAAQxD,KAAK,gBACbwD,QAAQxD,KAAK,cAAe,UAI5BkE,QAAU9G,KAAK2F,iBAAiBiB,QACpCE,QAAU,IAAItH,OAAOgG,MAAMsB,QAAQtE,EAAIZ,QAASkF,QAAQpE,EAAId,SAC5DwE,QAAQxD,KAAK,UAAWkE,QAAQtE,GAAGI,KAAK,UAAWkE,QAAQpE,GAG1DmE,YAIItC,kBAAkB6B,eAHlBW,aAAaX,cACbY,mBAAmBZ,eAKvBa,oBAAoBtD,WAO7BhE,wBAAwBgB,UAAUsG,oBAAsB,SAAStD,cACzDE,YAAc,OACdqD,MAAQlH,KAAKK,UAAUU,KAAK,kCAAoC4C,UAChEwD,KAAOnH,KACP4B,QAAU5B,KAAK4B,UAEfsF,MAAMrG,QACNqG,MAAM3D,MAAK,eACHQ,KAAOzE,EAAEU,SACR+D,KAAK0C,SAAS,iBAAoB1C,KAAKnB,KAAK,eAWtCmB,KAAKnB,KAAK,iBACjBiB,YAAYA,YAAYhD,QAAUkD,KAAKnB,KAAK,oBAZiB,CACzDmB,KAAKnB,KAAK,gBAAkBhB,SAE5BmC,KAAKnB,KAAK,QAASmB,KAAK2B,SAAS5C,MAAMF,KAAK,QAASmB,KAAK2B,SAAS3C,SAEnE6D,OAAS,IAAIpH,OAAOgG,MAAMzB,KAAKnB,KAAK,SAAUmB,KAAKnB,KAAK,aACxDuE,KAAKvB,cAAcgB,QAAS,KACxBE,QAAUK,KAAKxB,iBAAiBiB,QACpCE,QAAU,IAAItH,OAAOgG,MAAMsB,QAAQtE,EAAIZ,QAASkF,QAAQpE,EAAId,SAC5DiC,YAAYA,YAAYhD,QAAUiG,kBAQ7CzG,UAAUU,KAAK,eAAiB4C,UAAU0B,IAAIxB,YAAYuD,KAAK,MAChEpH,KAAK6E,yBAELwC,gBAAgBC,uBAEXlH,eAAiBJ,KAAKwE,8BAQnC7E,wBAAwBgB,UAAU4G,eAAiB,SAASpB,OACpDpC,KAAOzE,EAAE6G,EAAEE,QAAQC,QAAQ,WAC3Bb,MAAQ,IAAIjG,OAAOgG,MAAMzB,KAAK2B,SAAS5C,KAAMiB,KAAK2B,SAAS3C,KAC3DY,SAAW3D,KAAK4D,uBAAuBG,aAEnCoC,EAAEqB,cACD/H,KAAKgI,eACL,GACDhC,MAAMjD,GAAK,aAEV/C,KAAKiI,gBACL,GACDjC,MAAMjD,GAAK,aAEV/C,KAAKkI,eACL,GACDlC,MAAM/C,GAAK,aAEVjD,KAAKmI,aACL,GACDnC,MAAM/C,GAAK,aAEVjD,KAAKoI,WACLpI,KAAKqI,OACNrC,MAAQ,6BAKhBU,EAAE4B,iBAEY,OAAVtC,MAAgB,CAChBA,MAAQzF,KAAKgI,iBAAiBvC,OAC9B1B,KAAK2B,OAAO,MAASD,MAAMjD,MAAUiD,MAAM/C,IAC3CqB,KAAKnB,KAAK,QAASmB,KAAK2B,SAAS5C,MAAMF,KAAK,QAASmB,KAAK2B,SAAS3C,SAC/D6D,OAAS5G,KAAK2F,iBAAiB,IAAInG,OAAOgG,MAAMzB,KAAKnB,KAAK,SAAUmB,KAAKnB,KAAK,cAClFmB,KAAKnB,KAAK,UAAWgE,OAAOpE,EAAIxC,KAAK4B,WAAWgB,KAAK,UAAWgE,OAAOlE,EAAI1C,KAAK4B,WAC5E5B,KAAK4F,cAAc,IAAIpG,OAAOgG,MAAMzB,KAAK2B,SAAS5C,KAAMiB,KAAK2B,SAAS3C,OAClEgB,KAAK0C,SAAS,YAAa,MACtBpC,eAAeN,MAAM,OACtB2C,WAAa1G,KAAKsE,aAAaP,MAC/B2C,WAAW7F,QACX6F,WAAWpG,SAAS,eAEnBiE,kBAAkBR,YAI/BA,KAAKpB,IAAI,OAAQ,IAAIA,IAAI,MAAO,IAChCoB,KAAKnB,KAAK,QAASmB,KAAK2B,SAAS5C,MAAMF,KAAK,QAASmB,KAAK2B,SAAS3C,UAC9DgE,aAAahD,WACbiD,mBAAmBjD,MAE5BA,KAAKkE,aACAhB,oBAAoBtD,WAS7BhE,wBAAwBgB,UAAUqH,iBAAmB,SAASE,cACtDC,MAAQnI,KAAKc,UACbgG,QAAU9G,KAAK2F,iBAAiBuC,iBACpCpB,QAAQtE,EAAI4F,KAAKC,IAAI,EAAGvB,QAAQtE,GAChCsE,QAAQpE,EAAI0F,KAAKC,IAAI,EAAGvB,QAAQpE,GAChCoE,QAAQtE,EAAI4F,KAAKE,IAAIH,MAAMrC,QAASgB,QAAQtE,GAC5CsE,QAAQpE,EAAI0F,KAAKE,IAAIH,MAAMpC,SAAUe,QAAQpE,GACtC1C,KAAKoE,kBAAkB0C,UASlCnH,wBAAwBgB,UAAUiD,uBAAyB,SAAS2E,aACzDC,OAAOxI,KAAKyI,0BAA0BF,KAAM,YAUvD5I,wBAAwBgB,UAAU8H,0BAA4B,SAASF,KAAMG,YACrEC,QAAUrJ,EAAEiJ,MAAMK,KAAK,iBACXC,IAAZF,SAAqC,KAAZA,gBACrBG,WAAaH,QAAQpD,MAAM,KACtBwD,MAAQ,EAAGA,MAAQD,WAAWjI,OAAQkI,QAAS,IACxC,IAAIC,OAAO,IAAMN,OAAS,aAC5BO,KAAKH,WAAWC,QAAS,KAE3BG,MADQ,IAAIF,OAAO,aACLG,KAAKL,WAAWC,eAC3BP,OAAOU,MAAM,YAIzB,MAMXvJ,wBAAwBgB,UAAUyI,aAAe,eACzCrJ,MAAQC,KACR4B,QAAU5B,KAAK4B,UACf5B,KAAKG,aACLyB,QAAU,QAGTvB,UAAUU,KAAK,wBAAwBuC,IAAI,iBAAiBC,MAAK,SAASC,IAAKO,MAChFzE,EAAEyE,MACGpB,IAAI,OAAQ0G,WAAW/J,EAAEyE,MAAMnB,KAAK,YAAcyG,WAAWzH,UAC7De,IAAI,MAAO0G,WAAW/J,EAAEyE,MAAMnB,KAAK,YAAcyG,WAAWzH,UACjE7B,MAAMiD,mBAAmBe,KAAM,oBAG9B1D,UAAUU,KAAK,8BACf+E,MAAM9F,KAAKc,UAAUgF,SACrBC,OAAO/F,KAAKc,UAAUiF,cAEtB,IAAI1E,WAAa,EAAGA,WAAarB,KAAKF,iBAAiBe,OAAQQ,aAAc,KAE1EiI,aADWvJ,MAAMD,iBAAiBuB,YACVS,OACxBJ,MAAQ3B,MAAME,OAAOoB,YACrB4B,SAAWlD,MAAMG,UAAUmB,YAC/BK,MAAMG,MAAMyH,aAAc1H,SAC1BF,MAAM6H,UAAUtG,cAEZb,QAAUV,MAAMW,qBAChBmH,WAAaxJ,KAAKK,UAAUU,KAAK,6CAA+CM,YACpFmI,WACK7G,IAAI,OAAQP,QAAQG,WAAWC,EAAKgH,WAAWvI,aAAe,EAAK,GACnE0B,IAAI,MAAOP,QAAQG,WAAWG,EAAK8G,WAAWtI,cAAgB,GACnEnB,MAAMiD,mBAAmBwG,WAAY,YAO7C7J,wBAAwBgB,UAAU8I,WAAa,eACvC1J,MAAQC,UACPK,UAAUU,KAAK,6BAA6BwC,MAAK,SAASwF,MAAOW,cAC9D3F,KAAOzE,EAAEoK,UACTC,YAAc5F,KAAKG,QACvByF,YAAYC,cACZD,YAAYrJ,SAAS,UACrBqJ,YAAYrJ,SAAS,SAAWP,MAAM6D,uBAAuBG,OAC7D4F,YAAYrJ,SAASP,MAAM8J,eAAe9F,MAAM,IAChD4F,YAAYrJ,SAAS,mBACrByD,KAAK+F,OAAOH,iBAUpBhK,wBAAwBgB,UAAUoJ,UAAY,SAAShG,aAC5C/D,KAAKyI,0BAA0B1E,KAAM,WAUhDpE,wBAAwBgB,UAAUkJ,eAAiB,SAAS9F,KAAMiG,qBAC1DC,UAAY,SAAWjK,KAAK+J,UAAUhG,aACtC/D,KAAKkK,eAAenG,QACpBkG,UAAY,YAGZD,gBACO,IAAMC,UAGVA,WASXtK,wBAAwBgB,UAAU2D,aAAe,SAASP,aAC/C/D,KAAKK,UAAUU,KAAK,gCACXf,KAAK4D,uBAAuBG,MAAQ/D,KAAK6J,eAAe9F,MAAM,GAAQ,qBAO1FpE,wBAAwBgB,UAAUwJ,SAAW,kBAClCnK,KAAKK,UAAUU,KAAK,iBAQ/BpB,wBAAwBgB,UAAUoG,aAAe,SAAShD,MACtDA,KAAK6F,YAAY,gBACZtJ,SAAS,YACTqC,IAAI,MAAO,IACXA,IAAI,OAAQ,IACZA,IAAI,YAAa,QAClBgH,YAAc3J,KAAKsE,aAAaP,MACpC4F,YAAYS,MAAMrG,MAClB4F,YAAYC,YAAY,WAU5BjK,wBAAwBgB,UAAU0D,eAAiB,SAASN,KAAMsG,eAAWC,wEACrEH,SAAWnK,KAAKmK,WAChBvI,QAAU5B,KAAK4B,UACnBmC,KAAK6F,YAAY,gBAAgBA,YAAY,gBACzChD,OAAS5G,KAAK2F,iBAAiB,IAAInG,OAAOgG,MAAMzB,KAAKnB,KAAK,SAAUmB,KAAKnB,KAAK,WAC9EyH,WACAtG,KAAKnB,KAAK,UAAWgE,OAAOpE,EAAIZ,SAASgB,KAAK,UAAWgE,OAAOlE,EAAId,SACpEmC,KAAKpB,IAAI,OAAQiE,OAAOpE,GAAGG,IAAI,MAAOiE,OAAOlE,KAE7CqB,KAAKnB,KAAK,UAAWgE,OAAOpE,GAAGI,KAAK,UAAWgE,OAAOlE,GACtDqB,KAAKpB,IAAI,OAAQiE,OAAOpE,EAAIZ,SAASe,IAAI,MAAOiE,OAAOlE,EAAId,UAG1D0I,aAEDvG,KAAKnB,KAAK,aAAchB,SAE5BuI,SAASjI,OAAO6B,WACXf,mBAAmBe,KAAM,aAQlCpE,wBAAwBgB,UAAU4D,kBAAoB,SAASR,UACvDW,UAAY1E,KAAKuK,SAASxG,MAC1ByG,UAAYhC,OAAOxI,KAAKyI,0BAA0B/D,UAAW,cAC7D+F,yBAA2BzK,KAAKK,UAAUU,KAAK,8BAC3Cf,KAAK4D,uBAAuBG,MAAQ/D,KAAK6J,eAAe9F,MAAM,IAAOlD,OACzE6J,0BAA4B1K,KAAKK,UAAUU,KAAK,+BAC5Cf,KAAK4D,uBAAuBG,MAAQ/D,KAAK6J,eAAe9F,MAAM,IAAOT,IAAI,oBAAoBzC,WAEhGb,KAAKkK,eAAenG,QAChB/D,KAAKkK,eAAenG,OAAS0G,yBAA2BD,YAA4C,IAA9BE,0BAAiC,KACxGC,UAAY5G,KAAKG,QACrByG,UAAUrK,SAAS,YACdqC,IAAI,MAAO,IACXA,IAAI,OAAQ,IACZA,IAAI,YAAa,SACjB2B,aAAaP,MACb6F,YAAY,UACZQ,MAAMO,WACXtD,gBAAgBuD,yBAAyBD,aASjDhL,wBAAwBgB,UAAUqG,mBAAqB,SAASjD,cACxD8G,YAAc7K,KAAKK,UAAUU,KAAK,+BAClCf,KAAK4D,uBAAuBG,MAAQ/D,KAAK6J,eAAe9F,MAAM,IAAOT,IAAI,oBACzEwH,eAAiBD,YAAYhK,OAC1BiK,eAAiB,GACpBD,YAAYE,QAAQ/I,SACpB8I,kBAURnL,wBAAwBgB,UAAU4J,SAAW,SAASxG,UAC9CJ,SAAW3D,KAAK4D,uBAAuBG,aACpC/D,KAAKK,UAAUU,KAAK,uBAAyB4C,WAQxDhE,wBAAwBgB,UAAUiB,QAAU,eACpCuG,MAAQnI,KAAKc,UACbkK,kBAAoB7C,MAAM8C,IAAI,GAAGC,oBACd/C,MAAMrC,QAEHkF,mBAS9BrL,wBAAwBgB,UAAUqC,mBAAqB,SAASmI,QAASC,UACjExJ,QAAUyH,WAAWrJ,KAAK4B,WAC1B5B,KAAKG,aACLyB,QAAU,GAEdtC,EAAE6L,SAASxI,IAAI,qBACU,SAAWf,QAAU,qBACxB,SAAWA,QAAU,oBACtB,SAAWA,QAAU,mBACtB,SAAWA,QAAU,cACxB,SAAWA,QAAU,uBACdwJ,QAS5BzL,wBAAwBgB,UAAUuJ,eAAiB,SAASnG,aACjDA,KAAK0C,SAAS,aASzB9G,wBAAwBgB,UAAUD,2BAA6B,WAIvDV,KAAKO,kBAK0B,OAA/BP,KAAKqL,uBACLC,aAAatL,KAAKqL,uBAMlBrL,KAAKQ,wBAAwBK,OAAS,OACjCwK,sBAAwBE,YAAW,gBAC/B7K,+BACN,WAKFH,iBAAkB,OAClBkJ,kBACArG,uBACAxC,mBAQTjB,wBAAwBgB,UAAUH,sBAAwB,kBAC/CR,KAAKK,UAAUU,KAAK,gCAAgCuC,KAAI,SAASU,EAAGwH,gBAChExL,KAAKyL,cAAcD,aAUlC7L,wBAAwBgB,UAAU8K,cAAgB,SAASC,mBAChDA,WAAWC,UAAyC,IAA7BD,WAAWE,mBASzCvE,gBAAkB,CAKlBwE,0BAA0B,EAM1BC,+BAAgC,GAKhC3L,YAAY,EAKZ4L,sBAAsB,EAKtBC,UAAW,GASXC,KAAM,SAASrM,YAAaC,SAAUC,qBAClCuH,gBAAgB2E,UAAUpM,aACtB,IAAID,wBAAwBC,YAAaC,SAAUC,kBAClDuH,gBAAgBwE,2BACjBxE,gBAAgB6E,qBAChB7E,gBAAgBwE,0BAA2B,IAE1CxE,gBAAgByE,+BAA+BK,eAAevM,aAAc,CAC7EyH,gBAAgByE,+BAA+BlM,cAAe,MAE1DwM,kBAAoBpG,SAASC,eAAerG,aAC5CwM,kBAAkBC,UAAUC,SAAS,cACpCF,kBAAkBC,UAAUC,SAAS,6BAEtCjF,gBAAgBuD,yBAAyBtL,EAAE8M,mBAAmBrL,KAAK,0BACnEsG,gBAAgBuD,yBAAyBtL,EAAE8M,mBAAmBrL,KAAK,4BAQ/EmL,mBAAoB,WAChB5M,EAAEiN,QAAQC,GAAG,UAAU,WACnBnF,gBAAgBoF,oBAAmB,MAEvCF,OAAOG,iBAAiB,eAAe,WACnCrF,gBAAgBlH,YAAa,EAC7BkH,gBAAgBoF,mBAAmBpF,gBAAgBlH,eAEvDoM,OAAOG,iBAAiB,cAAc,WAClCrF,gBAAgBlH,YAAa,EAC7BkH,gBAAgBoF,mBAAmBpF,gBAAgBlH,eAEvDoL,YAAW,WACPlE,gBAAgBsF,2BACjB,MAQP/B,yBAA0B,SAASO,SAC/BA,QACKqB,GAAG,uBAAwBnF,gBAAgBnB,iBAC3CsG,GAAG,mBAAoBnF,gBAAgBE,gBACvCqF,SAAQ,SAASzG,GACdkB,gBAAgBwF,oBAAoB1G,GAAG,MAE1C2G,UAAS,SAAS3G,GACfkB,gBAAgBwF,oBAAoB1G,GAAG,OAQnDD,gBAAiB,SAASC,GACtBA,EAAE4B,qBACEgF,SAAW1F,gBAAgB2F,oBAAoB7G,GAC/C4G,UACAA,SAAS7G,gBAAgBC,IAQjCoB,eAAgB,SAASpB,OACjB4G,SAAW1F,gBAAgB2F,oBAAoB7G,GAC/C4G,UACAA,SAASxF,eAAepB,IAQhCsG,mBAAoB,SAAStM,gBACpB,IAAIP,eAAeyH,gBAAgB2E,UAChC3E,gBAAgB2E,UAAUG,eAAevM,eACzCyH,gBAAgB2E,UAAUpM,aAAaO,WAAaA,WACpDkH,gBAAgB2E,UAAUpM,aAAawJ,iBAUnDyD,oBAAqB,SAAS1G,EAAG8G,cAC7B5F,gBAAgB0E,qBAAuBkB,cAQ3CN,uBAAwB,WACftF,gBAAgB0E,2BACZU,mBAAmBpF,gBAAgBlH,YAK5CoL,YAAW,WACPlE,gBAAgBsF,uBAAuBtF,gBAAgBlH,cACxD,MAQP6M,oBAAqB,SAAS7G,OACtBvG,YAAcN,EAAE6G,EAAE+G,eAAe5G,QAAQ,iBAAiBsC,KAAK,aAC5DvB,gBAAgB2E,UAAUpM,cAMrC0H,gBAAiB,iBACP6F,aAAenH,SAASC,eAAe,gBAC7CvG,kBAAkB0N,gBAAgBD,sBAOnC,CASHlB,KAAM5E,gBAAgB4E"}
\ 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 abaf464c2f8..6ff3d7df726 100644
--- a/question/type/ddmarker/amd/src/question.js
+++ b/question/type/ddmarker/amd/src/question.js
@@ -149,13 +149,17 @@ define([
 
         root.find('input.choices').each(function(key, input) {
             var choiceNo = thisQ.getChoiceNoFromElement(input),
-                coords = thisQ.getCoords(input);
-            if (coords.length) {
+                imageCoords = thisQ.getImageCoords(input);
+            if (imageCoords.length) {
                 var drag = thisQ.getRoot().find('.draghomes' + ' span.marker' + '.choice' + choiceNo).not('.dragplaceholder');
                 drag.remove();
-                for (var i = 0; i < coords.length; i++) {
+                for (var i = 0; i < imageCoords.length; i++) {
                     var dragInDrop = drag.clone();
-                    dragInDrop.data('pagex', coords[i].x).data('pagey', coords[i].y);
+                    // Convert image coords to screen coords.
+                    const screenCoords = thisQ.convertToWindowXY(imageCoords[i]);
+                    dragInDrop.data('pagex', screenCoords.x).data('pagey', screenCoords.y);
+                    // Save image coords to the drag item so we can use it later.
+                    dragInDrop.data('imageCoords', imageCoords[i]);
                     // We always save the coordinates in the 1:1 ratio.
                     // So we need to set the scale ratio to 1 for the initial load.
                     dragInDrop.data('scaleRatio', 1);
@@ -216,18 +220,18 @@ define([
      * drags should be shown.
      *
      * @param {jQuery} inputNode
-     * @returns {Point[]} coordinates of however many copies of the drag item should be shown.
+     * @returns {Point[]} image coordinates of however many copies of the drag item should be shown.
      */
-    DragDropMarkersQuestion.prototype.getCoords = function(inputNode) {
-        var coords = [],
+    DragDropMarkersQuestion.prototype.getImageCoords = function(inputNode) {
+        var imageCoords = [],
             val = $(inputNode).val();
         if (val !== '') {
             var coordsStrings = val.split(';');
             for (var i = 0; i < coordsStrings.length; i++) {
-                coords[i] = this.convertToWindowXY(Shapes.Point.parse(coordsStrings[i]));
+                imageCoords[i] = Shapes.Point.parse(coordsStrings[i]);
             }
         }
-        return coords;
+        return imageCoords;
     };
 
     /**
@@ -330,7 +334,12 @@ define([
         if (this.coordsInBgImg(dragXY)) {
             this.sendDragToDrop(dragged, true);
             placed = true;
-
+            // Since we already move the drag item to new position.
+            // Remove the image coords if this drag item have it.
+            // We will get the new image coords for this drag item in saveCoordsForChoice.
+            if (dragged.data('imageCoords')) {
+                dragged.data('imageCoords', null);
+            }
             // It seems that the dragdrop sometimes leaves the drag
             // one pixel out of position. Put it in exactly the right place.
             var bgImgXY = this.convertToBgImgXY(dragXY);
@@ -353,15 +362,15 @@ define([
      * @param {Number} choiceNo which copy of the choice this was.
      */
     DragDropMarkersQuestion.prototype.saveCoordsForChoice = function(choiceNo) {
-        var coords = [],
-            items = this.getRoot().find('div.droparea span.marker.choice' + choiceNo),
+        let imageCoords = [];
+        var items = this.getRoot().find('div.droparea span.marker.choice' + choiceNo),
             thiQ = this,
             bgRatio = this.bgRatio();
 
         if (items.length) {
             items.each(function() {
                 var drag = $(this);
-                if (!drag.hasClass('beingdragged')) {
+                if (!drag.hasClass('beingdragged') && !drag.data('imageCoords')) {
                     if (drag.data('scaleRatio') !== bgRatio) {
                         // The scale ratio for the draggable item was changed. We need to update that.
                         drag.data('pagex', drag.offset().left).data('pagey', drag.offset().top);
@@ -370,13 +379,15 @@ define([
                     if (thiQ.coordsInBgImg(dragXY)) {
                         var bgImgXY = thiQ.convertToBgImgXY(dragXY);
                         bgImgXY = new Shapes.Point(bgImgXY.x / bgRatio, bgImgXY.y / bgRatio);
-                        coords[coords.length] = bgImgXY;
+                        imageCoords[imageCoords.length] = bgImgXY;
                     }
+                } else if (drag.data('imageCoords')) {
+                    imageCoords[imageCoords.length] = drag.data('imageCoords');
                 }
             });
         }
 
-        this.getRoot().find('input.choice' + choiceNo).val(coords.join(';'));
+        this.getRoot().find('input.choice' + choiceNo).val(imageCoords.join(';'));
         if (this.isQuestionInteracted()) {
             // The user has interacted with the draggable items. We need to mark the form as dirty.
             questionManager.handleFormDirty();
diff --git a/question/type/ddmarker/tests/behat/previewquiz.feature b/question/type/ddmarker/tests/behat/previewquiz.feature
new file mode 100644
index 00000000000..14085c3b3b7
--- /dev/null
+++ b/question/type/ddmarker/tests/behat/previewquiz.feature
@@ -0,0 +1,50 @@
+@qtype @qtype_ddmarker
+Feature: Preview a quiz with multiple maker question.
+  As a teacher
+  In order to check my drag-drop marker questions will work for students
+  I need to preview them in quiz with multiple questions.
+
+  Background:
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1        | 0        |
+    And the following "question categories" exist:
+      | contextlevel | reference | name           |
+      | Course       | C1        | Test questions |
+    And the following "questions" exist:
+      | questioncategory | qtype    | name           | template |
+      | Test questions   | ddmarker | Drag markers   | mkmap    |
+      | Test questions   | ddmarker | Drag markers 2 | mkmap    |
+    And the following "activities" exist:
+      | activity | name      | course | idnumber |
+      | quiz     | Test quiz | C1     | quiz1    |
+    And quiz "Test quiz" contains the following questions:
+      | Drag markers   | 1 |
+      | Drag markers 2 | 2 |
+
+  @javascript
+  Scenario: Preview a quiz with multiple markers question
+    Given I am on the "Test quiz" "mod_quiz > View" page logged in as "admin"
+    And I press "Preview quiz"
+    # Add change window size so we can drag-drop OU marker on 322,213 coordinates on firefox.
+    And I change viewport size to "large"
+    # Drag items and go back and forth between the question.
+    And I drag "OU" to "322,213" in the drag and drop markers question
+    And I drag "Railway station" to "144,84" in the drag and drop markers question
+    And I drag "Railway station" to "195,180" in the drag and drop markers question
+    And I press "Next page"
+    And I drag "OU" to "322,213" in the drag and drop markers question
+    And I drag "Railway station" to "144,84" in the drag and drop markers question
+    And I drag "Railway station" to "195,180" in the drag and drop markers question
+    And I press "Previous page"
+    And I drag "Railway station" to "267,302" in the drag and drop markers question
+    And I press "Next page"
+    And I drag "Railway station" to "267,302" in the drag and drop markers question
+    And I press "Previous page"
+    And I press "Next page"
+    And I press "Finish attempt ..."
+    And I press "Submit all and finish"
+    When I click on "Submit all and finish" "button" in the "Submit all your answers and finish?" "dialogue"
+    Then I should see "2.00/2.00"
+    And the state of "Please place the markers on the map of Milton Keynes and be aware that" question is shown as "Correct"
+    And I should see "Well done!"