Proyectos de Subversion Moodle

Rev

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/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":"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"}