Rev 1 | AutorÃa | Comparar con el anterior | 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 'core_filters/events',\n], function(\n $,\n dragDrop,\n keys,\n FormChangeChecker,\n filterEvent\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 const thisQ = this;\n this.containerId = containerId;\n this.questionAnswer = {};\n this.questionDragDropWidthHeight = [];\n if (readOnly) {\n this.getRoot().addClass('qtype_ddwtos-readonly');\n }\n this.resizeAllDragsAndDrops();\n this.cloneDrags();\n this.positionDrags();\n // Wait for all dynamic content loaded by filter to be completed.\n document.addEventListener(filterEvent.eventTypes.filterContentRenderingComplete, (elements) => {\n elements.detail.nodes.forEach((element) => {\n thisQ.changeAllDragsAndDropsToFilteredContent(element);\n });\n });\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 dragDropItems = this.getRoot().find('span.group' + group),\n maxWidth = 0,\n maxHeight = 0;\n\n // Find the maximum size of any drag in this groups.\n dragDropItems.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 thisQ.questionDragDropWidthHeight[group] = {maxWidth: maxWidth, maxHeight: maxHeight};\n // Set each drag home to that size.\n dragDropItems.each(function(i, drag) {\n thisQ.setElementSize(drag, maxWidth, maxHeight);\n });\n };\n\n /**\n * Change all the drags and drops related to the item that has been changed by filter to correct size and content.\n *\n * @param {object} filteredElement the element has been modified by filter.\n */\n DragDropToTextQuestion.prototype.changeAllDragsAndDropsToFilteredContent = function(filteredElement) {\n let currentFilteredItem = $(filteredElement);\n const parentIsDD = currentFilteredItem.parent().closest('span').hasClass('placed') ||\n currentFilteredItem.parent().closest('span').hasClass('draghome');\n const isDD = currentFilteredItem.hasClass('placed') || currentFilteredItem.hasClass('draghome');\n // The filtered element or parent element should a drag or drop item.\n if (!parentIsDD && !isDD) {\n return;\n }\n if (parentIsDD) {\n currentFilteredItem = currentFilteredItem.parent().closest('span');\n }\n const thisQ = this;\n if (thisQ.getRoot().find(currentFilteredItem).length <= 0) {\n // If the DD item doesn't belong to this question\n // In case we have multiple questions in the same page.\n return;\n }\n const group = thisQ.getGroup(currentFilteredItem),\n choice = thisQ.getChoice(currentFilteredItem);\n let listOfModifiedDragDrop = [];\n // Get the list of drag and drop item within the same group and choice.\n this.getRoot().find('.group' + group + '.choice' + choice).each(function(i, node) {\n // Same modified item, skip it.\n if ($(node).get(0) === currentFilteredItem.get(0)) {\nreturn;\n }\n const originalClass = $(node).attr('class');\n const originalStyle = $(node).attr('style');\n // We want to keep all the handler and event for filtered item, so using clone is the only choice.\n const filteredDragDropClone = currentFilteredItem.clone();\n // Replace the class and style of the drag drop item we want to replace for the clone.\n filteredDragDropClone.attr('class', originalClass);\n filteredDragDropClone.attr('style', originalStyle);\n // Insert into DOM.\n $(node).before(filteredDragDropClone);\n // Add the item has been replaced to a list so we can remove it later.\n listOfModifiedDragDrop.push(node);\n });\n\n listOfModifiedDragDrop.forEach(function(node) {\n $(node).remove();\n });\n // Save the current height and width.\n const currentHeight = currentFilteredItem.height();\n const currentWidth = currentFilteredItem.width();\n // Set to auto so we can get the real height and width of the filtered item.\n currentFilteredItem.height('auto');\n currentFilteredItem.width('auto');\n // We need to set display block so we can get height and width.\n // Some browser can't get the offsetWidth/Height if they are an inline element like span tag.\n if (!filteredElement.offsetWidth || !filteredElement.offsetHeight) {\n filteredElement.classList.add('d-block');\n }\n if (thisQ.questionDragDropWidthHeight[group].maxWidth < Math.ceil(filteredElement.offsetWidth) ||\n thisQ.questionDragDropWidthHeight[group].maxHeight < Math.ceil(0 + filteredElement.offsetHeight)) {\n // Remove the d-block class before calculation.\n filteredElement.classList.remove('d-block');\n // Now resize all the items in the same group if we have new maximum width or height.\n thisQ.resizeAllDragsAndDropsInGroup(group);\n } else {\n // Return the original height and width in case the real height and width is not the maximum.\n currentFilteredItem.height(currentHeight);\n currentFilteredItem.width(currentWidth);\n }\n // Remove the d-block class after resize.\n filteredElement.classList.remove('d-block');\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 varthisQ = 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) {\nif (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) {\nhiddenDrop.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.\nif (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:\nquestionManager.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,\ntop: 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)',\nquestionManager.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 ondrops.\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","filterEvent","DragDropToTextQuestion","containerId","readOnly","thisQ","this","questionAnswer","questionDragDropWidthHeight","getRoot","addClass","resizeAllDragsAndDrops","cloneDrags","positionDrags","document","addEventListener","eventTypes","filterContentRenderingComplete","elements","detail","nodes","forEach","element","changeAllDragsAndDropsToFilteredContent","prototype","find","each","i","node","resizeAllDragsAndDropsInGroup","getClassnameNumericSuffix","group","dragDropItems","maxWidth","maxHeight","drag","Math","max","ceil","offsetWidth","offsetHeight","setElementSize","filteredElement","currentFilteredItem","parentIsDD","parent","closest","hasClass","isDD","length","getGroup","choice","getChoice","listOfModifiedDragDrop","get","originalClass","attr","originalStyle","filteredDragDropClone","clone","before","push","remove","currentHeight","height","currentWidth","width","classList","add","css","index","draghome","placeHolder","removeClass","root","not","dragNode","currentPlace","removeAttr","inputNode","input","val","place","getPlace","drop","dropPosition","offset","data","top","left","unplacedDrag","getUnplacedChoice","hiddenDrag","getDragClone","noOfDrags","noOfDropsInGroup","getInfiniteDragClones","cloneDrag","after","questionManager","addEventHandlersToDrag","sendDragToDrop","getQuestionAnsweredValues","result","id","value","isQuestionInteracted","oldAnswer","newAnswer","isInteracted","JSON","stringify","Object","key","handleDragStart","e","target","prepare","start","setInputValue","hiddenDrop","getDrop","x","y","dragMove","dragEnd","pageX","pageY","dropNode","isPointInDrop","placed","dropZone","sendDragHome","oldDrag","getCurrentDragInPlace","hasDropSameDrag","focus","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","getElementById","is","slice","prefix","classes","undefined","classesArr","split","RegExp","test","match","exec","Number","inHome","eventHandlersInitialised","dragEventHandlersInitialised","questions","init","setupEventHandlers","hasOwnProperty","questionContainer","contains","on","handleDragMoved","unbind","question","getQuestionForEvent","currentTarget","removeData","first","handleFormDirty","responseForm","markFormAsDirty"],"mappings":"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"}