AutorÃa | Ultima modificación | Ver Log |
{"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/cop
yleft/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.len
gth; 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 (ex
istingmarkertext.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 mark
erspan\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 init
ial 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 co
ordinates 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('un
needed');\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.sendDrag
ToDrop(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.coordsInBgIm
g(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 clo
ne 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', dra
gXY.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.getDrag
NoClass(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 s
croll 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.isPrintin
g = 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","dropZ
oneNo","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","bgImg
XY","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","eventHandlers
Initialised","markerEventHandlersInitialised","isKeyboardNavigation","questions","init","setupEventHandlers","hasOwnProperty","questionContainer","classList","contains","window","on","handleWindowResize","addEventListener","fixLayoutIfThingsMoved","focusin","handleKeyboardFocus","focusout","question","getQuestionForEvent","isNavigating","currentTarget","responseForm","markFormAsDirty"],"mappings":"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"}