Proyectos de Subversion Moodle

Rev

Autoría | Ultima modificación | Ver Log |

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