diff --git a/question/type/ddmarker/amd/build/form.min.js b/question/type/ddmarker/amd/build/form.min.js index 35fb91b752a..2607883cd28 100644 --- a/question/type/ddmarker/amd/build/form.min.js +++ b/question/type/ddmarker/amd/build/form.min.js @@ -1,2 +1,2 @@ -define ("qtype_ddmarker/form",["jquery","core/dragdrop","qtype_ddmarker/shapes"],function(a,b,c){"use strict";function d(a){this.dropzoneNo=a;this.svgEl=null;this.shape=c.make(this.getShapeType(),this.getLabel());this.updateCoordinatesFromForm()}d.prototype.updateCoordinatesFromForm=function(a){var b=this.getCoordinates(),c="polygon"===this.shape.getType()&&this.shape.points.length;if(this.shape.getCoordinates()===b){return}if(!this.shape.parse(b)){return}if("polygon"===this.shape.getType()&&c!==this.shape.points.length){var d=this.isActive();this.removeFromSvg();if(a){this.addToSvg(a);if(d){this.setActive()}}}else{this.updateSvgEl()}};d.prototype.updateLabel=function(){var a=this.getLabel();if(this.shape.label!==a){this.shape.label=a;this.updateSvgEl()}};d.prototype.changeShape=function(a){var b=this.getShapeType(),d=this.isActive();if(b===this.shape.getType()){return}this.removeFromSvg();this.shape=c.getSimilar(b,this.shape);if(a){this.addToSvg(a);if(d){this.setActive()}}this.setCoordinatesInForm()};d.prototype.addToSvg=function(a){if(null!==this.svgEl){throw new Error("this.svgEl already set")}this.svgEl=this.shape.makeSvg(a);if(!this.svgEl){return}this.svgEl.setAttribute("class","dropzone");this.svgEl.setAttribute("data-dropzone-no",this.dropzoneNo);var b=this.shape.getHandlePositions();if(null===b){return}var d=c.createSvgElement(this.svgEl,"circle");d.setAttribute("cx",b.moveHandle.x);d.setAttribute("cy",b.moveHandle.y);d.setAttribute("r",7);d.setAttribute("class","handle move");for(var e=0;e
")},setOptionsForDragItemSelectors:function setOptionsForDragItemSelectors(){var b={0:""},c=f.form.getFormValue("noitems",[]),d=[],e,g,h;for(g=1;g<=c;g++){h=f.form.getMarkerText(g);if(""!==h){b[g]=a("
").text(h).html()}}for(g=0;g"+b[j]+"";e.append(l);var m=e.find("option[value=\""+j+"\"]");if(0===j){continue}if(j===d[g]){m.attr("selected",!0);continue}var n=f.form.getFormValue("drags",[j-1,"noofdrags"]);if(0===+n){continue}for(var o in d){if(+d[o]!==j){continue}if(1===+n){m.attr("disabled",!0);break}else{n--}}}if(0");for(c=0;c
")},setOptionsForDragItemSelectors:function setOptionsForDragItemSelectors(){var b={0:""},c=f.form.getFormValue("noitems",[]),d=[],e,g,h;for(g=1;g<=c;g++){h=f.form.getMarkerText(g);if(""!==h){b[g]=a("
").text(h).html()}}for(g=0;g"+b[j]+"";e.append(l);var m=e.find("option[value=\""+j+"\"]");if(0===j){continue}if(j===d[g]){m.attr("selected",!0);continue}var n=f.form.getFormValue("drags",[j-1,"noofdrags"]);if(0===+n){continue}for(var o in d){if(+d[o]!==j){continue}if(1===+n){m.attr("disabled",!0);break}else{n--}}}if(0");for(c=0;c.\n\n/**\n * This class provides the enhancements to the drag-drop marker editing form.\n *\n * @package qtype_ddmarker\n * @subpackage form\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes'], function($, dragDrop, Shapes) {\n\n \"use strict\";\n\n /**\n * Create the manager object that deals with keeping everything synchronised for one drop zone.\n *\n * @param {int} dropzoneNo the index of this drop zone in the form. 0, 1, ....\n * @constructor\n */\n function DropZoneManager(dropzoneNo) {\n this.dropzoneNo = dropzoneNo;\n this.svgEl = null;\n\n this.shape = Shapes.make(this.getShapeType(), this.getLabel());\n this.updateCoordinatesFromForm();\n }\n\n /**\n * Update the coordinates from a particular string.\n *\n * @param {SVGElement} [svg] the SVG element that is the preview.\n */\n DropZoneManager.prototype.updateCoordinatesFromForm = function(svg) {\n var coordinates = this.getCoordinates(),\n currentNumPoints = this.shape.getType() === 'polygon' && this.shape.points.length;\n if (this.shape.getCoordinates() === coordinates) {\n return;\n }\n if (!this.shape.parse(coordinates)) {\n // Invalid coordinates. Don't update the preview.\n return;\n }\n\n if (this.shape.getType() === 'polygon' && currentNumPoints !== this.shape.points.length) {\n // Polygon, and size has changed.\n var currentyActive = this.isActive();\n this.removeFromSvg();\n if (svg) {\n this.addToSvg(svg);\n if (currentyActive) {\n this.setActive();\n }\n }\n } else {\n // Simple update.\n this.updateSvgEl();\n }\n };\n\n /**\n * Update the label.\n */\n DropZoneManager.prototype.updateLabel = function() {\n var label = this.getLabel();\n if (this.shape.label !== label) {\n this.shape.label = label;\n this.updateSvgEl();\n }\n };\n\n /**\n * Handle if the type of shape has changed.\n *\n * @param {SVGElement} [svg] an SVG element to add this new shape to.\n */\n DropZoneManager.prototype.changeShape = function(svg) {\n var newShapeType = this.getShapeType(),\n currentyActive = this.isActive();\n\n if (newShapeType === this.shape.getType()) {\n return;\n }\n\n // It has really changed.\n this.removeFromSvg();\n this.shape = Shapes.getSimilar(newShapeType, this.shape);\n if (svg) {\n this.addToSvg(svg);\n if (currentyActive) {\n this.setActive();\n }\n }\n this.setCoordinatesInForm();\n };\n\n /**\n * Add this drop zone to an SVG graphic.\n *\n * @param {SVGElement} svg the SVG image to which to add this drop zone.\n */\n DropZoneManager.prototype.addToSvg = function(svg) {\n if (this.svgEl !== null) {\n throw new Error('this.svgEl already set');\n }\n this.svgEl = this.shape.makeSvg(svg);\n if (!this.svgEl) {\n return;\n }\n this.svgEl.setAttribute('class', 'dropzone');\n this.svgEl.setAttribute('data-dropzone-no', this.dropzoneNo);\n\n // Add handles.\n var handles = this.shape.getHandlePositions();\n if (handles === null) {\n return;\n }\n\n var moveHandle = Shapes.createSvgElement(this.svgEl, 'circle');\n moveHandle.setAttribute('cx', handles.moveHandle.x);\n moveHandle.setAttribute('cy', handles.moveHandle.y);\n moveHandle.setAttribute('r', 7);\n moveHandle.setAttribute('class', 'handle move');\n\n for (var i = 0; i < handles.editHandles.length; ++i) {\n this.makeEditHandle(i, handles.editHandles[i]);\n }\n };\n\n /**\n * Add a new edit handle.\n *\n * @param {int} index the handle index.\n * @param {Point} point the point at which to add the handle.\n */\n DropZoneManager.prototype.makeEditHandle = function(index, point) {\n var editHandle = Shapes.createSvgElement(this.svgEl, 'rect');\n editHandle.setAttribute('x', point.x - 6);\n editHandle.setAttribute('y', point.y - 6);\n editHandle.setAttribute('width', 11);\n editHandle.setAttribute('height', 11);\n editHandle.setAttribute('class', 'handle edit');\n editHandle.setAttribute('data-edit-handle-no', index);\n };\n\n /**\n * Remove this drop zone from an SVG image.\n */\n DropZoneManager.prototype.removeFromSvg = function() {\n if (this.svgEl !== null) {\n this.svgEl.parentNode.removeChild(this.svgEl);\n this.svgEl = null;\n }\n };\n\n /**\n * Update the shape of this drop zone (but not type) in an SVG image.\n */\n DropZoneManager.prototype.updateSvgEl = function() {\n if (this.svgEl === null) {\n return;\n }\n\n this.shape.updateSvg(this.svgEl);\n\n // Adjust handles.\n var handles = this.shape.getHandlePositions();\n if (handles === null) {\n return;\n }\n\n // Move handle.\n // The shape + its label are the first two children of svgEl.\n // Then come the move handle followed by the edit handles.\n this.svgEl.childNodes[2].setAttribute('cx', handles.moveHandle.x);\n this.svgEl.childNodes[2].setAttribute('cy', handles.moveHandle.y);\n\n // Edit handles.\n for (var i = 0; i < handles.editHandles.length; ++i) {\n this.svgEl.childNodes[3 + i].setAttribute('x', handles.editHandles[i].x - 6);\n this.svgEl.childNodes[3 + i].setAttribute('y', handles.editHandles[i].y - 6);\n }\n };\n\n /**\n * Find out of this drop zone is currently being edited.\n *\n * @return {boolean} true if it is.\n */\n DropZoneManager.prototype.isActive = function() {\n return this.svgEl !== null && this.svgEl.getAttribute('class').match(/\\bactive\\b/);\n };\n\n /**\n * Set this drop zone as being edited.\n */\n DropZoneManager.prototype.setActive = function() {\n // Move this one to last, so that it is always on top.\n // (Otherwise the handles may not be able to receive events.)\n var parent = this.svgEl.parentNode;\n parent.removeChild(this.svgEl);\n parent.appendChild(this.svgEl);\n this.svgEl.setAttribute('class', this.svgEl.getAttribute('class') + ' active');\n };\n\n /**\n * Set the coordinates in the form to match the current shape.\n */\n DropZoneManager.prototype.setCoordinatesInForm = function() {\n dragDropForm.form.setFormValue('drops', [this.dropzoneNo, 'coords'], this.shape.getCoordinates());\n };\n\n /**\n * Returns the coordinates for a drop zone from the text input in the form.\n * @returns {string} the coordinates.\n */\n DropZoneManager.prototype.getCoordinates = function() {\n return dragDropForm.form.getFormValue('drops', [this.dropzoneNo, 'coords']).replace(/\\s*/g, '');\n };\n\n /**\n * Returns the selected marker number from the dropdown in the form.\n * @returns {int} choice number.\n */\n DropZoneManager.prototype.getChoiceNo = function() {\n return dragDropForm.form.getFormValue('drops', [this.dropzoneNo, 'choice']);\n };\n\n /**\n * Returns the selected marker number from the dropdown in the form.\n * @returns {String} marker label text.\n */\n DropZoneManager.prototype.getLabel = function() {\n return dragDropForm.form.getMarkerText(this.getChoiceNo());\n };\n\n\n /**\n * Returns the selected type of shape in the form.\n * @returns {String} 'circle', 'rectangle' or 'polygon'.\n */\n DropZoneManager.prototype.getShapeType = function() {\n return dragDropForm.form.getFormValue('drops', [this.dropzoneNo, 'shape']);\n };\n\n /**\n * Start responding to dragging the move handle.\n * @param {Event} e Event object\n */\n DropZoneManager.prototype.handleMove = function(e) {\n var info = dragDrop.prepare(e);\n if (!info.start) {\n return;\n }\n\n var movingDropZone = this,\n lastX = info.x,\n lastY = info.y,\n dragProxy = this.makeDragProxy(info.x, info.y),\n bgImg = $('fieldset#id_previewareaheader .dropbackground'),\n maxX = bgImg.width(),\n maxY = bgImg.height();\n\n dragDrop.start(e, $(dragProxy), function(pageX, pageY) {\n movingDropZone.shape.move(pageX - lastX, pageY - lastY, maxX, maxY);\n lastX = pageX;\n lastY = pageY;\n movingDropZone.updateSvgEl();\n movingDropZone.setCoordinatesInForm();\n }, function() {\n document.body.removeChild(dragProxy);\n });\n };\n\n /**\n * Start responding to dragging the move handle.\n * @param {Event} e Event object\n * @param {int} handleIndex\n * @param {SVGElement} [svg] an SVG element to add this new shape to.\n */\n DropZoneManager.prototype.handleEdit = function(e, handleIndex, svg) {\n var info = dragDrop.prepare(e);\n if (!info.start) {\n return;\n }\n\n // For polygons, CTRL + drag adds a new point.\n if (this.shape.getType() === 'polygon' && (e.ctrlKey || e.metaKey)) {\n this.shape.addNewPointAfter(handleIndex);\n this.removeFromSvg();\n this.addToSvg(svg);\n this.setActive();\n }\n\n var changingDropZone = this,\n lastX = info.x,\n lastY = info.y,\n dragProxy = this.makeDragProxy(info.x, info.y),\n bgImg = $('fieldset#id_previewareaheader .dropbackground'),\n maxX = bgImg.width(),\n maxY = bgImg.height();\n\n dragDrop.start(e, $(dragProxy), function(pageX, pageY) {\n changingDropZone.shape.edit(handleIndex, pageX - lastX, pageY - lastY, maxX, maxY);\n lastX = pageX;\n lastY = pageY;\n changingDropZone.updateSvgEl();\n changingDropZone.setCoordinatesInForm();\n }, function() {\n document.body.removeChild(dragProxy);\n changingDropZone.shape.normalizeShape();\n changingDropZone.updateSvgEl();\n changingDropZone.setCoordinatesInForm();\n });\n };\n\n /**\n * Make an invisible drag proxy.\n *\n * @param {int} x x position .\n * @param {int} y y position.\n * @returns {HTMLElement} the drag proxy.\n */\n DropZoneManager.prototype.makeDragProxy = function(x, y) {\n var dragProxy = document.createElement('div');\n dragProxy.style.position = 'absolute';\n dragProxy.style.top = y + 'px';\n dragProxy.style.left = x + 'px';\n dragProxy.style.width = '1px';\n dragProxy.style.height = '1px';\n document.body.appendChild(dragProxy);\n return dragProxy;\n };\n\n /**\n * Singleton object for managing all the parts of the form.\n */\n var dragDropForm = {\n\n /**\n * @var {object} with properties width and height.\n */\n maxSizes: null, // Object containing maximum sizes for the background image.\n\n /**\n * @var {object} for interacting with the file pickers.\n */\n fp: null, // Object containing functions associated with the file picker.\n\n /**\n * @var {int} the number of drop-zones on the form.\n */\n noDropZones: null,\n\n /**\n * @var {DropZoneManager[]} the drop zones in the preview, indexed by drop zone number.\n */\n dropZones: [],\n\n /**\n * Initialise the form.\n *\n * @param {Object} maxBgimageSize object with two properties width and height.\n */\n init: function(maxBgimageSize) {\n dragDropForm.maxSizes = maxBgimageSize;\n dragDropForm.fp = dragDropForm.filePickers();\n dragDropForm.noDropZones = dragDropForm.form.getFormValue('nodropzone', []);\n dragDropForm.setupPreviewArea();\n dragDropForm.setOptionsForDragItemSelectors();\n dragDropForm.createShapes();\n dragDropForm.setupEventHandlers();\n dragDropForm.waitForFilePickerToInitialise();\n },\n\n /**\n * Add html for the preview area.\n */\n setupPreviewArea: function() {\n $('fieldset#id_previewareaheader div.fcontainer').append(\n '
' +\n '
' +\n ' ' +\n '
' +\n '
' +\n '
' +\n '
');\n },\n\n /**\n * When a new marker is added this function updates the Marker dropdown controls in Drop zones.\n */\n setOptionsForDragItemSelectors: function() {\n var dragItemsOptions = {'0': ''};\n var noItems = dragDropForm.form.getFormValue('noitems', []);\n var selectedValues = [];\n var selector;\n var i, label;\n for (i = 1; i <= noItems; i++) {\n label = dragDropForm.form.getMarkerText(i);\n if (label !== \"\") {\n // HTML escape the label.\n dragItemsOptions[i] = $('
').text(label).html();\n }\n }\n // Get all the currently selected drags for each drop.\n for (i = 0; i < dragDropForm.noDropZones; i++) {\n selector = $('#id_drops_' + i + '_choice');\n selectedValues[i] = Number(selector.val());\n }\n for (i = 0; i < dragDropForm.noDropZones; i++) {\n selector = $('#id_drops_' + i + '_choice');\n // Remove all options for drag choice.\n selector.find('option').remove();\n // And recreate the options.\n for (var value in dragItemsOptions) {\n value = Number(value);\n var option = '';\n selector.append(option);\n var optionnode = selector.find('option[value=\"' + value + '\"]');\n\n\n if (value === 0) {\n continue; // The 'no item' option is always selectable.\n }\n\n // Is this the currently selected value?\n if (value === selectedValues[i]) {\n optionnode.attr('selected', true);\n continue; // If it s selected, we must leave it enabled.\n }\n\n // Count how many times it is used, and if necessary, disable.\n var noofdrags = dragDropForm.form.getFormValue('drags', [value - 1, 'noofdrags']);\n if (Number(noofdrags) === 0) { // 'noofdrags === 0' means infinite.\n continue; // Nothing to check.\n }\n\n // Go through all selected values in drop downs.\n for (var k in selectedValues) {\n if (Number(selectedValues[k]) !== value) {\n continue;\n }\n\n // Count down 'noofdrags' and if reach zero then set disabled option for this drag item.\n if (Number(noofdrags) === 1) {\n optionnode.attr('disabled', true);\n break;\n } else {\n noofdrags--;\n }\n }\n }\n\n if (dragDropForm.dropZones.length > 0) {\n dragDropForm.dropZones[i].updateLabel();\n }\n }\n },\n\n /**\n * Create the shape representation of each dropZone.\n */\n createShapes: function() {\n for (var dropzoneNo = 0; dropzoneNo < dragDropForm.noDropZones; dropzoneNo++) {\n dragDropForm.dropZones[dropzoneNo] = new DropZoneManager(dropzoneNo);\n }\n },\n\n /**\n * Events linked to form actions.\n */\n setupEventHandlers: function() {\n // Changes to labels in the Markers section.\n $('fieldset#id_draggableitemheader').on('change input', 'input, select', function() {\n dragDropForm.setOptionsForDragItemSelectors();\n });\n\n // Changes to Drop zones section: shape, coordinates and marker.\n $('fieldset#id_dropzoneheader').on('change input', 'input, select', function(e) {\n var ids = e.currentTarget.name.match(/^drops\\[(\\d+)]\\[([a-z]*)]$/);\n if (!ids) {\n return;\n }\n\n var dropzoneNo = ids[1],\n inputType = ids[2],\n dropZone = dragDropForm.dropZones[dropzoneNo];\n\n switch (inputType) {\n case 'shape':\n dropZone.changeShape(dragDropForm.form.getSvg());\n break;\n\n case 'coords':\n dropZone.updateCoordinatesFromForm(dragDropForm.form.getSvg());\n break;\n\n case 'choice':\n dropZone.updateLabel();\n break;\n }\n });\n\n // Click to toggle graphical editing.\n var previewArea = $('fieldset#id_previewareaheader');\n previewArea.on('click', 'g.dropzone', function(e) {\n var dropzoneNo = $(e.currentTarget).data('dropzone-no'),\n currentlyActive = dragDropForm.dropZones[dropzoneNo].isActive();\n\n $(dragDropForm.form.getSvg()).find('.dropzone.active').removeClass('active');\n\n if (!currentlyActive) {\n dragDropForm.dropZones[dropzoneNo].setActive();\n }\n });\n\n // Drag start on a move handle.\n previewArea.on('mousedown touchstart', '.dropzone .handle.move', function(e) {\n var dropzoneNo = $(e.currentTarget).closest('g').data('dropzoneNo');\n\n dragDropForm.dropZones[dropzoneNo].handleMove(e);\n });\n\n // Drag start on a move handle.\n previewArea.on('mousedown touchstart', '.dropzone .handle.edit', function(e) {\n var dropzoneNo = $(e.currentTarget).closest('g').data('dropzoneNo'),\n handleIndex = e.currentTarget.getAttribute('data-edit-handle-no');\n\n dragDropForm.dropZones[dropzoneNo].handleEdit(e, handleIndex, dragDropForm.form.getSvg());\n });\n },\n\n /**\n * Prevents adding drop zones until the preview background image is ready to load.\n */\n waitForFilePickerToInitialise: function() {\n if (dragDropForm.fp.file('bgimage').href === null) {\n // It would be better to use an onload or onchange event rather than this timeout.\n // Unfortunately attempts to do this early are overwritten by filepicker during its loading.\n setTimeout(dragDropForm.waitForFilePickerToInitialise, 1000);\n return;\n }\n\n // From now on, when a new file gets loaded into the filepicker, update the preview.\n // This is not in the setupEventHandlers section as it needs to be delayed until\n // after filepicker's javascript has finished.\n $('form.mform').on('change', '#id_bgimage', dragDropForm.loadPreviewImage);\n\n dragDropForm.loadPreviewImage();\n },\n\n /**\n * Loads the preview background image.\n */\n loadPreviewImage: function() {\n $('fieldset#id_previewareaheader .dropbackground')\n .one('load', dragDropForm.afterPreviewImageLoaded)\n .attr('src', dragDropForm.fp.file('bgimage').href);\n },\n\n /**\n * Functions to run after background image loaded.\n */\n afterPreviewImageLoaded: function() {\n var bgImg = $('fieldset#id_previewareaheader .dropbackground');\n dragDropForm.constrainImageSize();\n // Place the dropzone area over the background image (adding one to account for the border).\n $('#ddm-dropzone').css('position', 'relative').css('top', (bgImg.height() + 1) * -1);\n $('#ddm-droparea').css('height', bgImg.height() + 20);\n dragDropForm.updateSvgDisplay();\n },\n\n /**\n * Limits the background image display size.\n */\n constrainImageSize: function() {\n var bgImg = $('fieldset#id_previewareaheader .dropbackground');\n var reduceby = Math.max(bgImg.width() / dragDropForm.maxSizes.width,\n bgImg.height() / dragDropForm.maxSizes.height);\n if (reduceby > 1) {\n bgImg.css('width', Math.floor(bgImg.width() / reduceby));\n }\n bgImg.addClass('constrained');\n },\n\n /**\n * Draws or re-draws all dropzones in the preview area based on form data.\n * Call this function when there is a change in the form data.\n */\n updateSvgDisplay: function() {\n var bgImg = $('fieldset#id_previewareaheader .dropbackground'),\n dropzoneNo;\n\n if (dragDropForm.form.getSvg()) {\n // Already exists, just need to be updated.\n for (dropzoneNo = 0; dropzoneNo < dragDropForm.noDropZones; dropzoneNo++) {\n dragDropForm.dropZones[dropzoneNo].updateSvgEl();\n }\n\n } else {\n // Create.\n $('#ddm-dropzone').html('');\n for (dropzoneNo = 0; dropzoneNo < dragDropForm.noDropZones; dropzoneNo++) {\n dragDropForm.dropZones[dropzoneNo].addToSvg(dragDropForm.form.getSvg());\n }\n }\n },\n\n /**\n * Helper to make it easy to work with form elements with names like \"drops[0][shape]\".\n */\n form: {\n /**\n * Returns the label text for a marker.\n * @param {int} markerNo\n * @returns {string} Marker text\n */\n getMarkerText: function(markerNo) {\n if (Number(markerNo) !== 0) {\n var label = dragDropForm.form.getFormValue('drags', [markerNo - 1, 'label']);\n return label.replace(new RegExp(\"^\\\\s*(.*)\\\\s*$\"), \"$1\");\n } else {\n return '';\n }\n },\n\n /**\n * Get the SVG element, if there is one, otherwise return null.\n *\n * @returns {SVGElement|null} the SVG element or null.\n */\n getSvg: function() {\n var svg = $('fieldset#id_previewareaheader svg');\n if (svg.length === 0) {\n return null;\n } else {\n return svg[0];\n }\n },\n\n toNameWithIndex: function(name, indexes) {\n var indexString = name;\n for (var i = 0; i < indexes.length; i++) {\n indexString = indexString + '[' + indexes[i] + ']';\n }\n return indexString;\n },\n\n getEl: function(name, indexes) {\n var form = $('form.mform')[0];\n return form.elements[this.toNameWithIndex(name, indexes)];\n },\n\n /**\n * Helper to get the value of a form elements with name like \"drops[0][shape]\".\n *\n * @param {String} name the base name, e.g. 'drops'.\n * @param {String[]} indexes the indexes, e.g. ['0', 'shape'].\n * @return {String} the value of that field.\n */\n getFormValue: function(name, indexes) {\n var el = this.getEl(name, indexes);\n if (el.type === 'checkbox') {\n return el.checked;\n } else {\n return el.value;\n }\n },\n\n /**\n * Helper to get the value of a form elements with name like \"drops[0][shape]\".\n *\n * @param {String} name the base name, e.g. 'drops'.\n * @param {String[]} indexes the indexes, e.g. ['0', 'shape'].\n * @param {String} value the value to set.\n */\n setFormValue: function(name, indexes, value) {\n var el = this.getEl(name, indexes);\n if (el.type === 'checkbox') {\n el.checked = value;\n } else {\n el.value = value;\n }\n }\n },\n\n /**\n * Utility to get the file name and url from the filepicker.\n * @returns {Object} object containing functions {file, name}\n */\n filePickers: function() {\n var draftItemIdsToName;\n var nameToParentNode;\n if (draftItemIdsToName === undefined) {\n draftItemIdsToName = {};\n nameToParentNode = {};\n $('form.mform input.filepickerhidden').each(function(key, filepicker) {\n draftItemIdsToName[filepicker.value] = filepicker.name;\n nameToParentNode[filepicker.name] = filepicker.parentNode;\n });\n }\n return {\n file: function(name) {\n var fileAnchor = $(nameToParentNode[name]).find('div.filepicker-filelist a');\n if (fileAnchor.length) {\n return {href: fileAnchor.get(0).href, name: fileAnchor.get(0).innerHTML};\n } else {\n return {href: null, name: null};\n }\n },\n name: function(draftitemid) {\n return draftItemIdsToName[draftitemid];\n }\n };\n }\n };\n\n /**\n * @alias module:qtype_ddmarker/form\n */\n return {\n /**\n * Initialise the form javascript features.\n * @param {Object} maxBgimageSize object with two properties: width and height.\n */\n init: dragDropForm.init\n };\n});\n"],"file":"form.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/form.js"],"names":["define","$","dragDrop","Shapes","DropZoneManager","dropzoneNo","svgEl","shape","make","getShapeType","getLabel","updateCoordinatesFromForm","prototype","svg","coordinates","getCoordinates","currentNumPoints","getType","points","length","parse","currentyActive","isActive","removeFromSvg","addToSvg","setActive","updateSvgEl","setCoordinatesInForm","updateLabel","label","changeShape","newShapeType","getSimilar","Error","makeSvg","setAttribute","handles","getHandlePositions","moveHandle","createSvgElement","x","y","i","editHandles","makeEditHandle","index","point","editHandle","parentNode","removeChild","updateSvg","childNodes","getAttribute","match","parent","appendChild","dragDropForm","form","setFormValue","getFormValue","replace","getChoiceNo","getMarkerText","handleMove","e","info","prepare","start","movingDropZone","lastX","lastY","dragProxy","makeDragProxy","bgImg","maxX","width","maxY","height","pageX","pageY","move","document","body","handleEdit","handleIndex","ctrlKey","metaKey","addNewPointAfter","changingDropZone","edit","normalizeShape","createElement","style","position","top","left","maxSizes","fp","noDropZones","dropZones","init","maxBgimageSize","filePickers","setupPreviewArea","setOptionsForDragItemSelectors","createShapes","setupEventHandlers","waitForFilePickerToInitialise","append","dragItemsOptions","noItems","selectedValues","selector","text","html","val","find","remove","value","option","optionnode","attr","noofdrags","k","on","ids","currentTarget","name","inputType","dropZone","getSvg","previewArea","data","currentlyActive","removeClass","closest","file","href","setTimeout","loadPreviewImage","one","afterPreviewImageLoaded","constrainImageSize","css","updateSvgDisplay","reduceby","Math","max","floor","addClass","outerWidth","outerHeight","markerNo","toNameWithIndex","indexes","indexString","getEl","elements","el","type","checked","draftItemIdsToName","nameToParentNode","each","key","filepicker","fileAnchor","get","innerHTML","draftitemid"],"mappings":"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","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 .\n\n/**\n * This class provides the enhancements to the drag-drop marker editing form.\n *\n * @package qtype_ddmarker\n * @subpackage form\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes'], function($, dragDrop, Shapes) {\n\n \"use strict\";\n\n /**\n * Create the manager object that deals with keeping everything synchronised for one drop zone.\n *\n * @param {int} dropzoneNo the index of this drop zone in the form. 0, 1, ....\n * @constructor\n */\n function DropZoneManager(dropzoneNo) {\n this.dropzoneNo = dropzoneNo;\n this.svgEl = null;\n\n this.shape = Shapes.make(this.getShapeType(), this.getLabel());\n this.updateCoordinatesFromForm();\n }\n\n /**\n * Update the coordinates from a particular string.\n *\n * @param {SVGElement} [svg] the SVG element that is the preview.\n */\n DropZoneManager.prototype.updateCoordinatesFromForm = function(svg) {\n var coordinates = this.getCoordinates(),\n currentNumPoints = this.shape.getType() === 'polygon' && this.shape.points.length;\n if (this.shape.getCoordinates() === coordinates) {\n return;\n }\n // We don't need to scale the shape for editing form.\n if (!this.shape.parse(coordinates, 1)) {\n // Invalid coordinates. Don't update the preview.\n return;\n }\n\n if (this.shape.getType() === 'polygon' && currentNumPoints !== this.shape.points.length) {\n // Polygon, and size has changed.\n var currentyActive = this.isActive();\n this.removeFromSvg();\n if (svg) {\n this.addToSvg(svg);\n if (currentyActive) {\n this.setActive();\n }\n }\n } else {\n // Simple update.\n this.updateSvgEl();\n }\n // Update the rounded coordinates if needed.\n this.setCoordinatesInForm();\n };\n\n /**\n * Update the label.\n */\n DropZoneManager.prototype.updateLabel = function() {\n var label = this.getLabel();\n if (this.shape.label !== label) {\n this.shape.label = label;\n this.updateSvgEl();\n }\n };\n\n /**\n * Handle if the type of shape has changed.\n *\n * @param {SVGElement} [svg] an SVG element to add this new shape to.\n */\n DropZoneManager.prototype.changeShape = function(svg) {\n var newShapeType = this.getShapeType(),\n currentyActive = this.isActive();\n\n if (newShapeType === this.shape.getType()) {\n return;\n }\n\n // It has really changed.\n this.removeFromSvg();\n this.shape = Shapes.getSimilar(newShapeType, this.shape);\n if (svg) {\n this.addToSvg(svg);\n if (currentyActive) {\n this.setActive();\n }\n }\n this.setCoordinatesInForm();\n };\n\n /**\n * Add this drop zone to an SVG graphic.\n *\n * @param {SVGElement} svg the SVG image to which to add this drop zone.\n */\n DropZoneManager.prototype.addToSvg = function(svg) {\n if (this.svgEl !== null) {\n throw new Error('this.svgEl already set');\n }\n this.svgEl = this.shape.makeSvg(svg);\n if (!this.svgEl) {\n return;\n }\n this.svgEl.setAttribute('class', 'dropzone');\n this.svgEl.setAttribute('data-dropzone-no', this.dropzoneNo);\n\n // Add handles.\n var handles = this.shape.getHandlePositions();\n if (handles === null) {\n return;\n }\n\n var moveHandle = Shapes.createSvgElement(this.svgEl, 'circle');\n moveHandle.setAttribute('cx', handles.moveHandle.x);\n moveHandle.setAttribute('cy', handles.moveHandle.y);\n moveHandle.setAttribute('r', 7);\n moveHandle.setAttribute('class', 'handle move');\n\n for (var i = 0; i < handles.editHandles.length; ++i) {\n this.makeEditHandle(i, handles.editHandles[i]);\n }\n };\n\n /**\n * Add a new edit handle.\n *\n * @param {int} index the handle index.\n * @param {Point} point the point at which to add the handle.\n */\n DropZoneManager.prototype.makeEditHandle = function(index, point) {\n var editHandle = Shapes.createSvgElement(this.svgEl, 'rect');\n editHandle.setAttribute('x', point.x - 6);\n editHandle.setAttribute('y', point.y - 6);\n editHandle.setAttribute('width', 11);\n editHandle.setAttribute('height', 11);\n editHandle.setAttribute('class', 'handle edit');\n editHandle.setAttribute('data-edit-handle-no', index);\n };\n\n /**\n * Remove this drop zone from an SVG image.\n */\n DropZoneManager.prototype.removeFromSvg = function() {\n if (this.svgEl !== null) {\n this.svgEl.parentNode.removeChild(this.svgEl);\n this.svgEl = null;\n }\n };\n\n /**\n * Update the shape of this drop zone (but not type) in an SVG image.\n */\n DropZoneManager.prototype.updateSvgEl = function() {\n if (this.svgEl === null) {\n return;\n }\n\n this.shape.updateSvg(this.svgEl);\n\n // Adjust handles.\n var handles = this.shape.getHandlePositions();\n if (handles === null) {\n return;\n }\n\n // Move handle.\n // The shape + its label are the first two children of svgEl.\n // Then come the move handle followed by the edit handles.\n this.svgEl.childNodes[2].setAttribute('cx', handles.moveHandle.x);\n this.svgEl.childNodes[2].setAttribute('cy', handles.moveHandle.y);\n\n // Edit handles.\n for (var i = 0; i < handles.editHandles.length; ++i) {\n this.svgEl.childNodes[3 + i].setAttribute('x', handles.editHandles[i].x - 6);\n this.svgEl.childNodes[3 + i].setAttribute('y', handles.editHandles[i].y - 6);\n }\n };\n\n /**\n * Find out of this drop zone is currently being edited.\n *\n * @return {boolean} true if it is.\n */\n DropZoneManager.prototype.isActive = function() {\n return this.svgEl !== null && this.svgEl.getAttribute('class').match(/\\bactive\\b/);\n };\n\n /**\n * Set this drop zone as being edited.\n */\n DropZoneManager.prototype.setActive = function() {\n // Move this one to last, so that it is always on top.\n // (Otherwise the handles may not be able to receive events.)\n var parent = this.svgEl.parentNode;\n parent.removeChild(this.svgEl);\n parent.appendChild(this.svgEl);\n this.svgEl.setAttribute('class', this.svgEl.getAttribute('class') + ' active');\n };\n\n /**\n * Set the coordinates in the form to match the current shape.\n */\n DropZoneManager.prototype.setCoordinatesInForm = function() {\n dragDropForm.form.setFormValue('drops', [this.dropzoneNo, 'coords'], this.shape.getCoordinates());\n };\n\n /**\n * Returns the coordinates for a drop zone from the text input in the form.\n * @returns {string} the coordinates.\n */\n DropZoneManager.prototype.getCoordinates = function() {\n return dragDropForm.form.getFormValue('drops', [this.dropzoneNo, 'coords']).replace(/\\s*/g, '');\n };\n\n /**\n * Returns the selected marker number from the dropdown in the form.\n * @returns {int} choice number.\n */\n DropZoneManager.prototype.getChoiceNo = function() {\n return dragDropForm.form.getFormValue('drops', [this.dropzoneNo, 'choice']);\n };\n\n /**\n * Returns the selected marker number from the dropdown in the form.\n * @returns {String} marker label text.\n */\n DropZoneManager.prototype.getLabel = function() {\n return dragDropForm.form.getMarkerText(this.getChoiceNo());\n };\n\n\n /**\n * Returns the selected type of shape in the form.\n * @returns {String} 'circle', 'rectangle' or 'polygon'.\n */\n DropZoneManager.prototype.getShapeType = function() {\n return dragDropForm.form.getFormValue('drops', [this.dropzoneNo, 'shape']);\n };\n\n /**\n * Start responding to dragging the move handle.\n * @param {Event} e Event object\n */\n DropZoneManager.prototype.handleMove = function(e) {\n var info = dragDrop.prepare(e);\n if (!info.start) {\n return;\n }\n\n var movingDropZone = this,\n lastX = info.x,\n lastY = info.y,\n dragProxy = this.makeDragProxy(info.x, info.y),\n bgImg = $('fieldset#id_previewareaheader .dropbackground'),\n maxX = bgImg.width(),\n maxY = bgImg.height();\n\n dragDrop.start(e, $(dragProxy), function(pageX, pageY) {\n movingDropZone.shape.move(pageX - lastX, pageY - lastY, maxX, maxY);\n lastX = pageX;\n lastY = pageY;\n movingDropZone.updateSvgEl();\n movingDropZone.setCoordinatesInForm();\n }, function() {\n document.body.removeChild(dragProxy);\n });\n };\n\n /**\n * Start responding to dragging the move handle.\n * @param {Event} e Event object\n * @param {int} handleIndex\n * @param {SVGElement} [svg] an SVG element to add this new shape to.\n */\n DropZoneManager.prototype.handleEdit = function(e, handleIndex, svg) {\n var info = dragDrop.prepare(e);\n if (!info.start) {\n return;\n }\n\n // For polygons, CTRL + drag adds a new point.\n if (this.shape.getType() === 'polygon' && (e.ctrlKey || e.metaKey)) {\n this.shape.addNewPointAfter(handleIndex);\n this.removeFromSvg();\n this.addToSvg(svg);\n this.setActive();\n }\n\n var changingDropZone = this,\n lastX = info.x,\n lastY = info.y,\n dragProxy = this.makeDragProxy(info.x, info.y),\n bgImg = $('fieldset#id_previewareaheader .dropbackground'),\n maxX = bgImg.width(),\n maxY = bgImg.height();\n\n dragDrop.start(e, $(dragProxy), function(pageX, pageY) {\n changingDropZone.shape.edit(handleIndex, pageX - lastX, pageY - lastY, maxX, maxY);\n lastX = pageX;\n lastY = pageY;\n changingDropZone.updateSvgEl();\n changingDropZone.setCoordinatesInForm();\n }, function() {\n document.body.removeChild(dragProxy);\n changingDropZone.shape.normalizeShape();\n changingDropZone.updateSvgEl();\n changingDropZone.setCoordinatesInForm();\n });\n };\n\n /**\n * Make an invisible drag proxy.\n *\n * @param {int} x x position .\n * @param {int} y y position.\n * @returns {HTMLElement} the drag proxy.\n */\n DropZoneManager.prototype.makeDragProxy = function(x, y) {\n var dragProxy = document.createElement('div');\n dragProxy.style.position = 'absolute';\n dragProxy.style.top = y + 'px';\n dragProxy.style.left = x + 'px';\n dragProxy.style.width = '1px';\n dragProxy.style.height = '1px';\n document.body.appendChild(dragProxy);\n return dragProxy;\n };\n\n /**\n * Singleton object for managing all the parts of the form.\n */\n var dragDropForm = {\n\n /**\n * @var {object} with properties width and height.\n */\n maxSizes: null, // Object containing maximum sizes for the background image.\n\n /**\n * @var {object} for interacting with the file pickers.\n */\n fp: null, // Object containing functions associated with the file picker.\n\n /**\n * @var {int} the number of drop-zones on the form.\n */\n noDropZones: null,\n\n /**\n * @var {DropZoneManager[]} the drop zones in the preview, indexed by drop zone number.\n */\n dropZones: [],\n\n /**\n * Initialise the form.\n *\n * @param {Object} maxBgimageSize object with two properties width and height.\n */\n init: function(maxBgimageSize) {\n dragDropForm.maxSizes = maxBgimageSize;\n dragDropForm.fp = dragDropForm.filePickers();\n dragDropForm.noDropZones = dragDropForm.form.getFormValue('nodropzone', []);\n dragDropForm.setupPreviewArea();\n dragDropForm.setOptionsForDragItemSelectors();\n dragDropForm.createShapes();\n dragDropForm.setupEventHandlers();\n dragDropForm.waitForFilePickerToInitialise();\n },\n\n /**\n * Add html for the preview area.\n */\n setupPreviewArea: function() {\n $('fieldset#id_previewareaheader div.fcontainer').append(\n '
' +\n '
' +\n ' ' +\n '
' +\n '
' +\n '
' +\n '
');\n },\n\n /**\n * When a new marker is added this function updates the Marker dropdown controls in Drop zones.\n */\n setOptionsForDragItemSelectors: function() {\n var dragItemsOptions = {'0': ''};\n var noItems = dragDropForm.form.getFormValue('noitems', []);\n var selectedValues = [];\n var selector;\n var i, label;\n for (i = 1; i <= noItems; i++) {\n label = dragDropForm.form.getMarkerText(i);\n if (label !== \"\") {\n // HTML escape the label.\n dragItemsOptions[i] = $('
').text(label).html();\n }\n }\n // Get all the currently selected drags for each drop.\n for (i = 0; i < dragDropForm.noDropZones; i++) {\n selector = $('#id_drops_' + i + '_choice');\n selectedValues[i] = Number(selector.val());\n }\n for (i = 0; i < dragDropForm.noDropZones; i++) {\n selector = $('#id_drops_' + i + '_choice');\n // Remove all options for drag choice.\n selector.find('option').remove();\n // And recreate the options.\n for (var value in dragItemsOptions) {\n value = Number(value);\n var option = '';\n selector.append(option);\n var optionnode = selector.find('option[value=\"' + value + '\"]');\n\n\n if (value === 0) {\n continue; // The 'no item' option is always selectable.\n }\n\n // Is this the currently selected value?\n if (value === selectedValues[i]) {\n optionnode.attr('selected', true);\n continue; // If it s selected, we must leave it enabled.\n }\n\n // Count how many times it is used, and if necessary, disable.\n var noofdrags = dragDropForm.form.getFormValue('drags', [value - 1, 'noofdrags']);\n if (Number(noofdrags) === 0) { // 'noofdrags === 0' means infinite.\n continue; // Nothing to check.\n }\n\n // Go through all selected values in drop downs.\n for (var k in selectedValues) {\n if (Number(selectedValues[k]) !== value) {\n continue;\n }\n\n // Count down 'noofdrags' and if reach zero then set disabled option for this drag item.\n if (Number(noofdrags) === 1) {\n optionnode.attr('disabled', true);\n break;\n } else {\n noofdrags--;\n }\n }\n }\n\n if (dragDropForm.dropZones.length > 0) {\n dragDropForm.dropZones[i].updateLabel();\n }\n }\n },\n\n /**\n * Create the shape representation of each dropZone.\n */\n createShapes: function() {\n for (var dropzoneNo = 0; dropzoneNo < dragDropForm.noDropZones; dropzoneNo++) {\n dragDropForm.dropZones[dropzoneNo] = new DropZoneManager(dropzoneNo);\n }\n },\n\n /**\n * Events linked to form actions.\n */\n setupEventHandlers: function() {\n // Changes to labels in the Markers section.\n $('fieldset#id_draggableitemheader').on('change input', 'input, select', function() {\n dragDropForm.setOptionsForDragItemSelectors();\n });\n\n // Changes to Drop zones section: shape, coordinates and marker.\n $('fieldset#id_dropzoneheader').on('change input', 'input, select', function(e) {\n var ids = e.currentTarget.name.match(/^drops\\[(\\d+)]\\[([a-z]*)]$/);\n if (!ids) {\n return;\n }\n\n var dropzoneNo = ids[1],\n inputType = ids[2],\n dropZone = dragDropForm.dropZones[dropzoneNo];\n\n switch (inputType) {\n case 'shape':\n dropZone.changeShape(dragDropForm.form.getSvg());\n break;\n\n case 'coords':\n dropZone.updateCoordinatesFromForm(dragDropForm.form.getSvg());\n break;\n\n case 'choice':\n dropZone.updateLabel();\n break;\n }\n });\n\n // Click to toggle graphical editing.\n var previewArea = $('fieldset#id_previewareaheader');\n previewArea.on('click', 'g.dropzone', function(e) {\n var dropzoneNo = $(e.currentTarget).data('dropzone-no'),\n currentlyActive = dragDropForm.dropZones[dropzoneNo].isActive();\n\n $(dragDropForm.form.getSvg()).find('.dropzone.active').removeClass('active');\n\n if (!currentlyActive) {\n dragDropForm.dropZones[dropzoneNo].setActive();\n }\n });\n\n // Drag start on a move handle.\n previewArea.on('mousedown touchstart', '.dropzone .handle.move', function(e) {\n var dropzoneNo = $(e.currentTarget).closest('g').data('dropzoneNo');\n\n dragDropForm.dropZones[dropzoneNo].handleMove(e);\n });\n\n // Drag start on a move handle.\n previewArea.on('mousedown touchstart', '.dropzone .handle.edit', function(e) {\n var dropzoneNo = $(e.currentTarget).closest('g').data('dropzoneNo'),\n handleIndex = e.currentTarget.getAttribute('data-edit-handle-no');\n\n dragDropForm.dropZones[dropzoneNo].handleEdit(e, handleIndex, dragDropForm.form.getSvg());\n });\n },\n\n /**\n * Prevents adding drop zones until the preview background image is ready to load.\n */\n waitForFilePickerToInitialise: function() {\n if (dragDropForm.fp.file('bgimage').href === null) {\n // It would be better to use an onload or onchange event rather than this timeout.\n // Unfortunately attempts to do this early are overwritten by filepicker during its loading.\n setTimeout(dragDropForm.waitForFilePickerToInitialise, 1000);\n return;\n }\n\n // From now on, when a new file gets loaded into the filepicker, update the preview.\n // This is not in the setupEventHandlers section as it needs to be delayed until\n // after filepicker's javascript has finished.\n $('form.mform').on('change', '#id_bgimage', dragDropForm.loadPreviewImage);\n\n dragDropForm.loadPreviewImage();\n },\n\n /**\n * Loads the preview background image.\n */\n loadPreviewImage: function() {\n $('fieldset#id_previewareaheader .dropbackground')\n .one('load', dragDropForm.afterPreviewImageLoaded)\n .attr('src', dragDropForm.fp.file('bgimage').href);\n },\n\n /**\n * Functions to run after background image loaded.\n */\n afterPreviewImageLoaded: function() {\n var bgImg = $('fieldset#id_previewareaheader .dropbackground');\n dragDropForm.constrainImageSize();\n // Place the dropzone area over the background image (adding one to account for the border).\n $('#ddm-dropzone').css('position', 'relative').css('top', (bgImg.height() + 1) * -1);\n $('#ddm-droparea').css('height', bgImg.height() + 20);\n dragDropForm.updateSvgDisplay();\n },\n\n /**\n * Limits the background image display size.\n */\n constrainImageSize: function() {\n var bgImg = $('fieldset#id_previewareaheader .dropbackground');\n var reduceby = Math.max(bgImg.width() / dragDropForm.maxSizes.width,\n bgImg.height() / dragDropForm.maxSizes.height);\n if (reduceby > 1) {\n bgImg.css('width', Math.floor(bgImg.width() / reduceby));\n }\n bgImg.addClass('constrained');\n },\n\n /**\n * Draws or re-draws all dropzones in the preview area based on form data.\n * Call this function when there is a change in the form data.\n */\n updateSvgDisplay: function() {\n var bgImg = $('fieldset#id_previewareaheader .dropbackground'),\n dropzoneNo;\n\n if (dragDropForm.form.getSvg()) {\n // Already exists, just need to be updated.\n for (dropzoneNo = 0; dropzoneNo < dragDropForm.noDropZones; dropzoneNo++) {\n dragDropForm.dropZones[dropzoneNo].updateSvgEl();\n }\n\n } else {\n // Create.\n $('#ddm-dropzone').html('');\n for (dropzoneNo = 0; dropzoneNo < dragDropForm.noDropZones; dropzoneNo++) {\n dragDropForm.dropZones[dropzoneNo].addToSvg(dragDropForm.form.getSvg());\n }\n }\n },\n\n /**\n * Helper to make it easy to work with form elements with names like \"drops[0][shape]\".\n */\n form: {\n /**\n * Returns the label text for a marker.\n * @param {int} markerNo\n * @returns {string} Marker text\n */\n getMarkerText: function(markerNo) {\n if (Number(markerNo) !== 0) {\n var label = dragDropForm.form.getFormValue('drags', [markerNo - 1, 'label']);\n return label.replace(new RegExp(\"^\\\\s*(.*)\\\\s*$\"), \"$1\");\n } else {\n return '';\n }\n },\n\n /**\n * Get the SVG element, if there is one, otherwise return null.\n *\n * @returns {SVGElement|null} the SVG element or null.\n */\n getSvg: function() {\n var svg = $('fieldset#id_previewareaheader svg');\n if (svg.length === 0) {\n return null;\n } else {\n return svg[0];\n }\n },\n\n toNameWithIndex: function(name, indexes) {\n var indexString = name;\n for (var i = 0; i < indexes.length; i++) {\n indexString = indexString + '[' + indexes[i] + ']';\n }\n return indexString;\n },\n\n getEl: function(name, indexes) {\n var form = $('form.mform')[0];\n return form.elements[this.toNameWithIndex(name, indexes)];\n },\n\n /**\n * Helper to get the value of a form elements with name like \"drops[0][shape]\".\n *\n * @param {String} name the base name, e.g. 'drops'.\n * @param {String[]} indexes the indexes, e.g. ['0', 'shape'].\n * @return {String} the value of that field.\n */\n getFormValue: function(name, indexes) {\n var el = this.getEl(name, indexes);\n if (el.type === 'checkbox') {\n return el.checked;\n } else {\n return el.value;\n }\n },\n\n /**\n * Helper to get the value of a form elements with name like \"drops[0][shape]\".\n *\n * @param {String} name the base name, e.g. 'drops'.\n * @param {String[]} indexes the indexes, e.g. ['0', 'shape'].\n * @param {String} value the value to set.\n */\n setFormValue: function(name, indexes, value) {\n var el = this.getEl(name, indexes);\n if (el.type === 'checkbox') {\n el.checked = value;\n } else {\n el.value = value;\n }\n }\n },\n\n /**\n * Utility to get the file name and url from the filepicker.\n * @returns {Object} object containing functions {file, name}\n */\n filePickers: function() {\n var draftItemIdsToName;\n var nameToParentNode;\n if (draftItemIdsToName === undefined) {\n draftItemIdsToName = {};\n nameToParentNode = {};\n $('form.mform input.filepickerhidden').each(function(key, filepicker) {\n draftItemIdsToName[filepicker.value] = filepicker.name;\n nameToParentNode[filepicker.name] = filepicker.parentNode;\n });\n }\n return {\n file: function(name) {\n var fileAnchor = $(nameToParentNode[name]).find('div.filepicker-filelist a');\n if (fileAnchor.length) {\n return {href: fileAnchor.get(0).href, name: fileAnchor.get(0).innerHTML};\n } else {\n return {href: null, name: null};\n }\n },\n name: function(draftitemid) {\n return draftItemIdsToName[draftitemid];\n }\n };\n }\n };\n\n /**\n * @alias module:qtype_ddmarker/form\n */\n return {\n /**\n * Initialise the form javascript features.\n * @param {Object} maxBgimageSize object with two properties: width and height.\n */\n init: dragDropForm.init\n };\n});\n"],"file":"form.min.js"} \ No newline at end of file diff --git a/question/type/ddmarker/amd/build/question.min.js b/question/type/ddmarker/amd/build/question.min.js index a44cd930069..4144b19b8a9 100644 --- a/question/type/ddmarker/amd/build/question.min.js +++ b/question/type/ddmarker/amd/build/question.min.js @@ -1,2 +1,2 @@ -define ("qtype_ddmarker/question",["jquery","core/dragdrop","qtype_ddmarker/shapes","core/key_codes"],function(a,b,c,d){"use strict";function e(a,b,c,d){this.containerId=a;this.visibleDropZones=d;if(c){this.getRoot().addClass("qtype_ddmarker-readonly")}this.loadImage(b)}e.prototype.loadImage=function(a){var b=this;this.getRoot().find(".dropbackground").one("load",function(){if(0");var b=this.getRoot().find("svg.dropzones");b.css("position","absolute");for(var c=0,d=0,e;d"+e.markertext+"")}var h=f.makeSvg(a[0]);h.setAttribute("class","dropzone "+d)};e.prototype.repositionDropZones=function(){var a=this.getRoot().find("svg.dropzones");if(0===a.length){return}var b=this.convertToWindowXY(new c.Point(-1,0));a.offset({left:b.x,top:b.y});for(var d=0,e;d");for(var b=this.getRoot().find("svg.dropzones"),c=0,d=0,e;d"+e.markertext+"");var i=this.getRoot().find("div.ddarea div.markertexts span.markertext"+b);if(i.length){var j=f.getHandlePositions(),k=j.moveHandle.x-i.outerWidth()/2-4,l=j.moveHandle.y-i.outerHeight()/2;i.css("left",k).css("top",l);i.data("originX",i.position().left/h).data("originY",i.position().top/h);this.handleElementScale(i,"center")}}var m=f.makeSvg(a[0]);m.setAttribute("class","dropzone "+d);this.shapes[this.shapes.length]=f;this.shapeSVGs[this.shapeSVGs.length]=m};e.prototype.repositionDrags=function(){var b=this.getRoot(),c=this;b.find("div.draghomes .marker").not(".dragplaceholder").each(function(b,c){a(c).addClass("unneeded")});b.find("input.choices").each(function(a,b){var d=c.getChoiceNoFromElement(b),e=c.getCoords(b);if(e.length){var f=c.getRoot().find(".draghomes span.marker.choice"+d).not(".dragplaceholder");f.remove();for(var g=0,h;g=c.left&&a.x=c.top&&a.y.\n\n/**\n * Question class for drag and drop marker question type, used to support the question and preview pages.\n *\n * @package qtype_ddmarker\n * @subpackage question\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], function($, dragDrop, Shapes, keys) {\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 {String} bgImgUrl the URL of the background image.\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, bgImgUrl, readOnly, visibleDropZones) {\n this.containerId = containerId;\n this.visibleDropZones = visibleDropZones;\n if (readOnly) {\n this.getRoot().addClass('qtype_ddmarker-readonly');\n }\n this.loadImage(bgImgUrl);\n }\n\n /**\n * Load the background image is loaded, then do the rest of the display.\n *\n * @param {String} bgImgUrl the URL of the background image.\n */\n DragDropMarkersQuestion.prototype.loadImage = function(bgImgUrl) {\n var thisQ = this;\n this.getRoot().find('.dropbackground')\n .one('load', function() {\n if (thisQ.visibleDropZones.length > 0) {\n thisQ.drawDropzones();\n }\n thisQ.repositionDrags();\n })\n .attr('src', bgImgUrl)\n .css({'border': '1px solid #000', 'max-width': 'none'});\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 var bgImage = this.getRoot().find('img.dropbackground');\n\n this.getRoot().find('div.dropzones').html('');\n var svg = this.getRoot().find('svg.dropzones');\n svg.css('position', 'absolute');\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 * 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 if (!shape.parse(dropZone.coords)) {\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('' +\n dropZone.markertext + '');\n }\n\n var shapeSVG = shape.makeSvg(svg[0]);\n shapeSVG.setAttribute('class', 'dropzone ' + colourClass);\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.repositionDropZones = function() {\n var svg = this.getRoot().find('svg.dropzones');\n if (svg.length === 0) {\n return;\n }\n var bgPosition = this.convertToWindowXY(new Shapes.Point(-1, 0));\n svg.offset({'left': bgPosition.x, 'top': bgPosition.y});\n\n for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) {\n var markerspan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo);\n if (markerspan.length === 0) {\n continue;\n }\n var dropZone = this.visibleDropZones[dropZoneNo],\n shape = Shapes.make(dropZone.shape, '');\n if (!shape.parse(dropZone.coords)) {\n continue;\n }\n var handles = shape.getHandlePositions(),\n textPos = this.convertToWindowXY(handles.moveHandle.offset(\n -markerspan.outerWidth() / 2, -markerspan.outerHeight() / 2));\n markerspan.offset({'left': textPos.x - 4, 'top': textPos.y});\n }\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.dragitems .dragitem').each(function(key, item) {\n $(item).addClass('unneeded');\n });\n\n root.find('input.choices').each(function(key, input) {\n var choiceNo = thisQ.getChoiceNoFromElement(input),\n coords = thisQ.getCoords(input),\n dragHome = thisQ.dragHome(choiceNo);\n for (var i = 0; i < coords.length; i++) {\n var drag = thisQ.dragItem(choiceNo, i);\n if (!drag.length || drag.hasClass('beingdragged')) {\n drag = thisQ.cloneNewDragItem(dragHome, i);\n } else {\n drag.removeClass('unneeded');\n }\n drag.offset({'left': coords[i].x, 'top': coords[i].y});\n }\n });\n\n root.find('div.dragitems .dragitem').each(function(key, itm) {\n var item = $(itm);\n if (item.hasClass('unneeded') && !item.hasClass('beingdragged')) {\n item.remove();\n }\n });\n\n this.repositionDropZones();\n\n var bgImage = this.bgImage(),\n bgPosition = bgImage.offset();\n bgImage.data('prev-top', bgPosition.top).data('prev-left', bgPosition.left);\n };\n\n /**\n * Determine what drag items need to be shown and\n * return coords of all drag items except any that are currently being dragged\n * based on contents of hidden inputs and whether drags are 'infinite' or how many\n * drags should be shown.\n *\n * @param {jQuery} inputNode\n * @returns {Point[]} coordinates of however many copies of the drag item should be shown.\n */\n DragDropMarkersQuestion.prototype.getCoords = function(inputNode) {\n var root = this.getRoot(),\n choiceNo = this.getChoiceNoFromElement(inputNode),\n noOfDrags = Number(this.getClassnameNumericSuffix(inputNode, 'noofdrags')),\n dragging = root.find('span.dragitem.beingdragged.choice' + choiceNo).length > 0,\n coords = [],\n val = $(inputNode).val();\n if (val !== '') {\n var coordsStrings = val.split(';');\n for (var i = 0; i < coordsStrings.length; i++) {\n coords[i] = this.convertToWindowXY(Shapes.Point.parse(coordsStrings[i]));\n }\n }\n var displayeddrags = coords.length + (dragging ? 1 : 0);\n if ($(inputNode).hasClass('infinite') || (displayeddrags < noOfDrags)) {\n coords[coords.length] = this.dragHomeXY(choiceNo);\n }\n return coords;\n };\n\n /**\n * Converts the relative x and y position coordinates into\n * absolute x and y position coordinates.\n *\n * @param {Point} point relative to the background image.\n * @returns {Point} point relative to the page.\n */\n DragDropMarkersQuestion.prototype.convertToWindowXY = function(point) {\n var bgImage = this.bgImage();\n // The +1 seems rather odd, but seems to give the best results in\n // the three main browsers at a range of zoom levels.\n // (Its due to the 1px border around the image, that shifts the\n // image pixels by 1 down and to the left.)\n return point.offset(bgImage.offset().left + 1, bgImage.offset().top + 1);\n };\n\n /**\n * Utility function converting window coordinates to relative to the\n * background image coordinates.\n *\n * @param {Point} point relative to the page.\n * @returns {Point} point relative to the background image.\n */\n DragDropMarkersQuestion.prototype.convertToBgImgXY = function(point) {\n var bgImage = this.bgImage();\n return point.offset(-bgImage.offset().left - 1, -bgImage.offset().top - 1);\n };\n\n /**\n * Is the point within the background image?\n *\n * @param {Point} point relative to the BG image.\n * @return {boolean} true it they are.\n */\n DragDropMarkersQuestion.prototype.coordsInBgImg = function(point) {\n var bgImage = this.bgImage();\n return point.x > 0 && point.x <= bgImage.width() &&\n point.y > 0 && point.y <= bgImage.height();\n };\n\n /**\n * Returns coordinates for the home position of a choice.\n *\n * @param {Number} choiceNo\n * @returns {Point} coordinates\n */\n DragDropMarkersQuestion.prototype.dragHomeXY = function(choiceNo) {\n var dragItemHome = this.dragHome(choiceNo);\n return new Shapes.Point(dragItemHome.offset().left, dragItemHome.offset().top);\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 /**\n * Return the DOM node for this choice's home position.\n * @param {Number} choiceNo\n * @returns {jQuery} containing the home.\n */\n DragDropMarkersQuestion.prototype.dragHome = function(choiceNo) {\n return this.getRoot().find('div.dragitems span.draghome.choice' + choiceNo);\n };\n\n /**\n * Return the DOM node for a particular instance of a particular choice.\n * @param {Number} choiceNo\n * @param {Number} itemNo\n * @returns {jQuery} containing the item.\n */\n DragDropMarkersQuestion.prototype.dragItem = function(choiceNo, itemNo) {\n return this.getRoot().find('div.dragitems span.dragitem.choice' + choiceNo + '.item' + itemNo);\n };\n\n /**\n * Create a draggable copy of the drag item.\n *\n * @param {jQuery} dragHome to clone\n * @param {Number} itemNo new item number\n * @return {jQuery} drag\n */\n DragDropMarkersQuestion.prototype.cloneNewDragItem = function(dragHome, itemNo) {\n var drag = dragHome.clone(true);\n drag.removeClass('draghome').addClass('dragitem').addClass('item' + itemNo);\n dragHome.after(drag);\n drag.attr('tabIndex', 0);\n return drag;\n };\n\n DragDropMarkersQuestion.prototype.handleDragStart = function(e) {\n var thisQ = this,\n dragged = $(e.target).closest('.dragitem');\n\n var info = dragDrop.prepare(e);\n if (!info.start) {\n return;\n }\n\n dragged.addClass('beingdragged');\n dragDrop.start(e, dragged, function() {\n void (1); // Nothing to do, but we need a function.\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 dragged.removeClass('beingdragged');\n var choiceNo = this.getChoiceNoFromElement(dragged);\n this.saveCoordsForChoice(choiceNo, dragged);\n this.repositionDrags();\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 * @param {jQuery} dropped the choice that was dropped here.\n */\n DragDropMarkersQuestion.prototype.saveCoordsForChoice = function(choiceNo, dropped) {\n var coords = [],\n numItems = this.getRoot().find('span.dragitem.choice' + choiceNo).length,\n bgImgXY,\n addme = true;\n\n // Re-build the coords array based on data in the ddform inputs.\n // While long winded and unnecessary if there is only one drop item\n // for a choice, it does account for moving any one of several drop items\n // within a choice that have already been placed.\n for (var i = 0; i <= numItems; i++) {\n var drag = this.dragItem(choiceNo, i);\n if (drag.length === 0) {\n continue;\n }\n\n if (!drag.hasClass('beingdragged')) {\n bgImgXY = this.convertToBgImgXY(new Shapes.Point(drag.offset().left, drag.offset().top));\n if (this.coordsInBgImg(bgImgXY)) {\n coords[coords.length] = bgImgXY;\n }\n }\n\n if (dropped && dropped.length !== 0 && (dropped[0].innerText === drag[0].innerText)) {\n addme = false;\n }\n }\n\n // If dropped has been passed it is because a new item has been dropped onto the background image\n // so add its coordinates to the array.\n if (addme) {\n bgImgXY = this.convertToBgImgXY(new Shapes.Point(dropped.offset().left, dropped.offset().top));\n if (this.coordsInBgImg(bgImgXY)) {\n coords[coords.length] = bgImgXY;\n }\n }\n\n this.getRoot().find('input.choice' + choiceNo).val(coords.join(';'));\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('.dragitem'),\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 } else {\n point = this.dragHomeXY(choiceNo);\n }\n drag.offset({'left': point.x, 'top': point.y});\n this.saveCoordsForChoice(choiceNo, drag);\n this.repositionDrags();\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 this.repositionDrags();\n };\n\n /**\n * Check to see if the background image has moved. If so, refresh the layout.\n */\n DragDropMarkersQuestion.prototype.fixLayoutIfBackgroundMoved = function() {\n var bgImage = this.bgImage(),\n bgPosition = bgImage.offset(),\n prevTop = bgImage.data('prev-top'),\n prevLeft = bgImage.data('prev-left');\n if (prevLeft === undefined || prevTop === undefined) {\n // Question is not set up yet. Nothing to do.\n return;\n }\n if (prevTop === bgPosition.top && prevLeft === bgPosition.left) {\n // Things have not moved.\n return;\n }\n // We need to reposition things.\n this.repositionDrags();\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} 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 {String} bgImgUrl URL fo the background image.\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, bgImgUrl, readOnly, visibleDropZones) {\n questionManager.questions[containerId] =\n new DragDropMarkersQuestion(containerId, bgImgUrl, readOnly, visibleDropZones);\n if (!questionManager.eventHandlersInitialised) {\n questionManager.setupEventHandlers();\n questionManager.eventHandlersInitialised = true;\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').on('mousedown touchstart',\n '.que.ddmarker:not(.qtype_ddmarker-readonly) div.dragitems .dragitem',\n questionManager.handleDragStart)\n .on('keydown keypress',\n '.que.ddmarker:not(.qtype_ddmarker-readonly) div.dragitems .dragitem',\n questionManager.handleKeyPress);\n $(window).on('resize', questionManager.handleWindowResize);\n setTimeout(questionManager.fixLayoutIfThingsMoved, 100);\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 */\n handleWindowResize: function() {\n for (var containerId in questionManager.questions) {\n if (questionManager.questions.hasOwnProperty(containerId)) {\n questionManager.questions[containerId].handleResize();\n }\n }\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 for (var containerId in questionManager.questions) {\n if (questionManager.questions.hasOwnProperty(containerId)) {\n questionManager.questions[containerId].fixLayoutIfBackgroundMoved();\n }\n }\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(questionManager.fixLayoutIfThingsMoved, 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 /**\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"],"file":"question.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/question.js"],"names":["define","$","dragDrop","Shapes","keys","DragDropMarkersQuestion","containerId","readOnly","visibleDropZones","thisQ","shapes","shapeSVGs","isPrinting","getRoot","addClass","cloneDrags","repositionDrags","drawDropzones","prototype","length","bgImage","find","html","outerWidth","outerHeight","svg","nextColourIndex","dropZoneNo","colourClass","addDropzone","dropZone","shape","make","existingmarkertext","bgRatio","parse","coords","markertext","remove","append","markerspan","handles","getHandlePositions","positionLeft","moveHandle","x","positionTop","y","css","data","position","left","top","handleElementScale","shapeSVG","makeSvg","setAttribute","root","not","each","key","item","input","choiceNo","getChoiceNoFromElement","getCoords","drag","i","dragInDrop","clone","sendDragToDrop","getDragClone","cloneDragIfNeeded","inputNode","val","coordsStrings","split","convertToWindowXY","Point","point","offset","convertToBgImgXY","coordsInBgImg","bgPosition","width","height","document","getElementById","handleDragStart","e","dragged","target","closest","info","prepare","start","placed","hasClass","hiddenDrag","dragEnd","dragXY","bgImgXY","sendDragHome","removeDragIfNeeded","saveCoordsForChoice","items","thiQ","join","handleKeyPress","keyCode","arrowLeft","arrowRight","arrowDown","arrowUp","space","escape","preventDefault","constrainToBgImg","focus","windowxy","bgImg","Math","max","min","node","getClassnameNumericSuffix","prefix","classes","attr","classesarr","index","patt1","RegExp","test","patt2","match","exec","handleResize","parseFloat","originCoords","updateSvg","markerSpan","draghome","placeHolder","removeClass","getDragNo","before","dropArea","after","isScaling","getInput","noOfDrags","displayedDragsInDropArea","displayedDragsInDragHomes","dragclone","displayeddrags","first","bgImgNaturalWidth","get","naturalWidth","bgImgClientWidth","element","type","questionManager","eventHandlersInitialised","isKeyboardNavigation","questions","init","setupEventHandlers","on","handleKeyboardFocus","window","handleWindowResize","addEventListener","setTimeout","fixLayoutIfThingsMoved","question","getQuestionForEvent","hasOwnProperty","isNavigating","currentTarget"],"mappings":"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","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 .\n\n/**\n * Question class for drag and drop marker question type, used to support the question and preview pages.\n *\n * @package qtype_ddmarker\n * @subpackage question\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], function($, dragDrop, Shapes, keys) {\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 if (readOnly) {\n this.getRoot().addClass('qtype_ddmarker-readonly');\n }\n thisQ.cloneDrags();\n thisQ.repositionDrags();\n thisQ.drawDropzones();\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('');\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('' +\n dropZone.markertext + '');\n var markerspan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo);\n if (markerspan.length) {\n var handles = shape.getHandlePositions();\n var positionLeft = handles.moveHandle.x - (markerspan.outerWidth() / 2) - 4;\n var positionTop = handles.moveHandle.y - (markerspan.outerHeight() / 2);\n markerspan\n .css('left', positionLeft)\n .css('top', positionTop);\n markerspan\n .data('originX', markerspan.position().left / bgRatio)\n .data('originY', markerspan.position().top / bgRatio);\n this.handleElementScale(markerspan, 'center');\n }\n }\n\n var shapeSVG = shape.makeSvg(svg[0]);\n shapeSVG.setAttribute('class', 'dropzone ' + colourClass);\n\n this.shapes[this.shapes.length] = shape;\n this.shapeSVGs[this.shapeSVGs.length] = shapeSVG;\n };\n\n /**\n * Draws the drag items on the page (and drop zones if required).\n * The idea is to re-draw all the drags and drops whenever there is a change\n * like a widow resize or an item dropped in place.\n */\n DragDropMarkersQuestion.prototype.repositionDrags = function() {\n var root = this.getRoot(),\n thisQ = this;\n\n root.find('div.draghomes .marker').not('.dragplaceholder').each(function(key, item) {\n $(item).addClass('unneeded');\n });\n\n root.find('input.choices').each(function(key, input) {\n var choiceNo = thisQ.getChoiceNoFromElement(input),\n coords = thisQ.getCoords(input);\n if (coords.length) {\n var drag = thisQ.getRoot().find('.draghomes' + ' span.marker' + '.choice' + choiceNo).not('.dragplaceholder');\n drag.remove();\n for (var i = 0; i < coords.length; i++) {\n var dragInDrop = drag.clone();\n dragInDrop.data('pagex', coords[i].x).data('pagey', coords[i].y);\n thisQ.sendDragToDrop(dragInDrop, false);\n }\n thisQ.getDragClone(drag).addClass('active');\n thisQ.cloneDragIfNeeded(drag);\n }\n });\n };\n\n /**\n * Determine what drag items need to be shown and\n * return coords of all drag items except any that are currently being dragged\n * based on contents of hidden inputs and whether drags are 'infinite' or how many\n * drags should be shown.\n *\n * @param {jQuery} inputNode\n * @returns {Point[]} coordinates of however many copies of the drag item should be shown.\n */\n DragDropMarkersQuestion.prototype.getCoords = function(inputNode) {\n var coords = [],\n val = $(inputNode).val();\n if (val !== '') {\n var coordsStrings = val.split(';');\n for (var i = 0; i < coordsStrings.length; i++) {\n coords[i] = this.convertToWindowXY(Shapes.Point.parse(coordsStrings[i]));\n }\n }\n return coords;\n };\n\n /**\n * Converts the relative x and y position coordinates into\n * absolute x and y position coordinates.\n *\n * @param {Point} point relative to the background image.\n * @returns {Point} point relative to the page.\n */\n DragDropMarkersQuestion.prototype.convertToWindowXY = function(point) {\n var bgImage = this.bgImage();\n // The +1 seems rather odd, but seems to give the best results in\n // the three main browsers at a range of zoom levels.\n // (Its due to the 1px border around the image, that shifts the\n // image pixels by 1 down and to the left.)\n return point.offset(bgImage.offset().left + 1, bgImage.offset().top + 1);\n };\n\n /**\n * Utility function converting window coordinates to relative to the\n * background image coordinates.\n *\n * @param {Point} point relative to the page.\n * @returns {Point} point relative to the background image.\n */\n DragDropMarkersQuestion.prototype.convertToBgImgXY = function(point) {\n var bgImage = this.bgImage();\n return point.offset(-bgImage.offset().left - 1, -bgImage.offset().top - 1);\n };\n\n /**\n * Is the point within the background image?\n *\n * @param {Point} point relative to the BG image.\n * @return {boolean} true it they are.\n */\n DragDropMarkersQuestion.prototype.coordsInBgImg = function(point) {\n var bgImage = this.bgImage();\n var bgPosition = bgImage.offset();\n\n return point.x >= bgPosition.left && point.x < bgPosition.left + bgImage.width()\n && point.y >= bgPosition.top && point.y < bgPosition.top + bgImage.height();\n };\n\n /**\n * Get the outer div for this question.\n * @returns {jQuery} containing that div.\n */\n DragDropMarkersQuestion.prototype.getRoot = function() {\n return $(document.getElementById(this.containerId));\n };\n\n /**\n * Get the img that is the background image.\n * @returns {jQuery} containing that img.\n */\n DragDropMarkersQuestion.prototype.bgImage = function() {\n return this.getRoot().find('img.dropbackground');\n };\n\n DragDropMarkersQuestion.prototype.handleDragStart = function(e) {\n var thisQ = this,\n dragged = $(e.target).closest('.marker');\n\n var info = dragDrop.prepare(e);\n if (!info.start) {\n return;\n }\n\n dragged.addClass('beingdragged').css('transform', '');\n\n var placed = !dragged.hasClass('unneeded');\n if (!placed) {\n var hiddenDrag = thisQ.getDragClone(dragged);\n if (hiddenDrag.length) {\n hiddenDrag.addClass('active');\n dragged.offset(hiddenDrag.offset());\n }\n }\n\n dragDrop.start(e, dragged, function() {\n void (1);\n }, function(x, y, dragged) {\n thisQ.dragEnd(dragged);\n });\n };\n\n /**\n * Functionality at the end of a drag drop.\n * @param {jQuery} dragged the marker that was dragged.\n */\n DragDropMarkersQuestion.prototype.dragEnd = function(dragged) {\n var placed = false,\n choiceNo = this.getChoiceNoFromElement(dragged),\n bgRatio = this.bgRatio(),\n dragXY;\n\n dragged.data('pagex', dragged.offset().left).data('pagey', dragged.offset().top);\n dragXY = new Shapes.Point(dragged.data('pagex'), dragged.data('pagey'));\n if (this.coordsInBgImg(dragXY)) {\n this.sendDragToDrop(dragged, true);\n placed = true;\n\n // It seems that the dragdrop sometimes leaves the drag\n // one pixel out of position. Put it in exactly the right place.\n var bgImgXY = this.convertToBgImgXY(dragXY);\n bgImgXY = new Shapes.Point(bgImgXY.x / bgRatio, bgImgXY.y / bgRatio);\n dragged.data('originX', bgImgXY.x).data('originY', bgImgXY.y);\n }\n\n if (!placed) {\n this.sendDragHome(dragged);\n this.removeDragIfNeeded(dragged);\n } else {\n this.cloneDragIfNeeded(dragged);\n }\n\n this.saveCoordsForChoice(choiceNo);\n };\n\n /**\n * Save the coordinates for a dropped item in the form field.\n * @param {Number} choiceNo which copy of the choice this was.\n */\n DragDropMarkersQuestion.prototype.saveCoordsForChoice = function(choiceNo) {\n var coords = [],\n items = this.getRoot().find('div.droparea span.marker.choice' + choiceNo),\n thiQ = this,\n bgRatio = this.bgRatio();\n\n if (items.length) {\n items.each(function() {\n var drag = $(this);\n if (!drag.hasClass('beingdragged')) {\n var dragXY = new Shapes.Point(drag.data('pagex'), drag.data('pagey'));\n if (thiQ.coordsInBgImg(dragXY)) {\n var bgImgXY = thiQ.convertToBgImgXY(dragXY);\n bgImgXY = new Shapes.Point(bgImgXY.x / bgRatio, bgImgXY.y / bgRatio);\n coords[coords.length] = bgImgXY;\n }\n }\n });\n }\n\n this.getRoot().find('input.choice' + choiceNo).val(coords.join(';'));\n };\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 choice' +\n thisQ.getChoiceNoFromElement(drag) + ' dragno' + thisQ.getDragNo(drag) + ' 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 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) + '.dragno' + this.getDragNo(drag) + '.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 */\n DragDropMarkersQuestion.prototype.sendDragToDrop = function(drag, isScaling) {\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 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) + '.dragno' + this.getDragNo(drag)).length,\n displayedDragsInDragHomes = this.getRoot().find('div.draghomes .marker.choice' +\n this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').length;\n\n if (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 }\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 displayeddrags = this.getRoot().find('div.draghomes .marker.choice' +\n this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').length;\n if (displayeddrags > 1) {\n this.getRoot().find('div.draghomes .marker.choice' +\n this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').first().remove();\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 * 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 * {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 },\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('mousedown touchstart',\n '.que.ddmarker:not(.qtype_ddmarker-readonly) div.draghomes .marker', questionManager.handleDragStart)\n .on('mousedown touchstart',\n '.que.ddmarker:not(.qtype_ddmarker-readonly) div.droparea .marker', questionManager.handleDragStart)\n .on('keydown keypress',\n '.que.ddmarker:not(.qtype_ddmarker-readonly) div.draghomes .marker', questionManager.handleKeyPress)\n .on('keydown keypress',\n '.que.ddmarker:not(.qtype_ddmarker-readonly) div.droparea .marker', questionManager.handleKeyPress)\n .on('focusin',\n '.que.ddmarker:not(.qtype_ddmarker-readonly) div.draghomes .marker', function(e) {\n questionManager.handleKeyboardFocus(e, true);\n })\n .on('focusin',\n '.que.ddmarker:not(.qtype_ddmarker-readonly) div.droparea .marker', function(e) {\n questionManager.handleKeyboardFocus(e, true);\n })\n .on('focusout',\n '.que.ddmarker:not(.qtype_ddmarker-readonly) div.draghomes .marker', function(e) {\n questionManager.handleKeyboardFocus(e, false);\n })\n .on('focusout',\n '.que.ddmarker:not(.qtype_ddmarker-readonly) div.droparea .marker', function(e) {\n questionManager.handleKeyboardFocus(e, false);\n });\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 * 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 /**\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"],"file":"question.min.js"} \ No newline at end of file diff --git a/question/type/ddmarker/amd/build/shapes.min.js b/question/type/ddmarker/amd/build/shapes.min.js index 521fd2fc508..fe4a6fff071 100644 --- a/question/type/ddmarker/amd/build/shapes.min.js +++ b/question/type/ddmarker/amd/build/shapes.min.js @@ -1,2 +1,2 @@ -define ("qtype_ddmarker/shapes",function(){"use strict";function a(a,b){this.x=a;this.y=b}a.prototype.toString=function(){return this.x+","+this.y};a.prototype.move=function(a,b){this.x+=a;this.y+=b};a.prototype.offset=function(b,c){if(b instanceof a){c=b.y;b=b.x}return new a(this.x+b,this.y+c)};a.parse=function(b){var c=b.split(",");if(2!==c.length){throw new Error(b+" is not a valid point")}return new a(Math.round(c[0]),Math.round(c[1]))};function b(b,c,d){this.label=b;this.centre=new a(c||0,d||0)}b.prototype.getType=function(){throw new Error("Not implemented.")};b.prototype.getCoordinates=function(){throw new Error("Not implemented.")};b.prototype.parse=function(){throw new Error("Not implemented.")};b.prototype.move=function(){};b.prototype.edit=function(){};b.prototype.normalizeShape=function(){};b.prototype.makeSvg=function(){throw new Error("Not implemented.")};b.prototype.updateSvg=function(){};b.prototype.makeSimilarCircle=function(){throw new Error("Not implemented.")};b.prototype.makeSimilarRectangle=function(){throw new Error("Not implemented.")};b.prototype.makeSimilarPolygon=function(){throw new Error("Not implemented.")};b.prototype.getHandlePositions=function(){return null};function c(a,c,d,e){c=c||15;d=d||15;b.call(this,a,c,d);this.radius=e||15}c.prototype=new b;c.prototype.getType=function(){return"circle"};c.prototype.getCoordinates=function(){return this.centre+";"+Math.abs(this.radius)};c.prototype.makeSvg=function(a){var b=h(a,"circle");this.updateSvg(b);return b};c.prototype.updateSvg=function(a){a.childNodes[0].setAttribute("cx",this.centre.x);a.childNodes[0].setAttribute("cy",this.centre.y);a.childNodes[0].setAttribute("r",Math.abs(this.radius));a.childNodes[1].setAttribute("x",this.centre.x);a.childNodes[1].setAttribute("y",this.centre.y+15);a.childNodes[1].textContent=this.label};c.prototype.parse=function(b){if(!b.match(/^\d+,\d+;\d+$/)){return!1}var c=b.split(";");this.centre=a.parse(c[0]);this.radius=Math.round(c[1]);return!0};c.prototype.move=function(a,b,c,d){this.centre.move(a,b);if(this.centre.xc-this.radius){this.centre.x=c-this.radius}if(this.centre.yd-this.radius){this.centre.y=d-this.radius}};c.prototype.edit=function(a,b,c,d,e){this.radius+=b;var f=Math.min(this.centre.x,this.centre.y,d-this.centre.x,e-this.centre.y);if(this.radius>f){this.radius=f}if(this.radius<-f){this.radius=-f}};c.prototype.normalizeShape=function(){this.radius=Math.abs(this.radius)};c.prototype.makeSimilarRectangle=function(){return new d(this.label,this.centre.x-this.radius,this.centre.y-this.radius,2*this.radius,2*this.radius)};c.prototype.makeSimilarPolygon=function(){return new e(this.label,[this.centre.offset(-this.radius,-this.radius),this.centre.offset(-this.radius,this.radius),this.centre.offset(this.radius,this.radius),this.centre.offset(this.radius,-this.radius)])};c.prototype.getHandlePositions=function(){return{moveHandle:this.centre,editHandles:[this.centre.offset(this.radius,0)]}};function d(a,c,d,e,f){b.call(this,a,c,d);this.width=e||30;this.height=f||30}d.prototype=new b;d.prototype.getType=function(){return"rectangle"};d.prototype.getCoordinates=function(){return this.centre+";"+this.width+","+this.height};d.prototype.makeSvg=function(a){var b=h(a,"rect");this.updateSvg(b);return b};d.prototype.updateSvg=function(a){if(0<=this.width){a.childNodes[0].setAttribute("x",this.centre.x);a.childNodes[0].setAttribute("width",this.width)}else{a.childNodes[0].setAttribute("x",this.centre.x+this.width);a.childNodes[0].setAttribute("width",-this.width)}if(0<=this.height){a.childNodes[0].setAttribute("y",this.centre.y);a.childNodes[0].setAttribute("height",this.height)}else{a.childNodes[0].setAttribute("y",this.centre.y+this.height);a.childNodes[0].setAttribute("height",-this.height)}a.childNodes[1].setAttribute("x",this.centre.x+this.width/2);a.childNodes[1].setAttribute("y",this.centre.y+this.height/2+15);a.childNodes[1].textContent=this.label};d.prototype.parse=function(b){if(!b.match(/^\d+,\d+;\d+,\d+$/)){return!1}var c=b.split(";");this.centre=a.parse(c[0]);var d=a.parse(c[1]);this.width=d.x;this.height=d.y;return!0};d.prototype.move=function(a,b,c,d){this.centre.move(a,b);if(0>this.centre.x){this.centre.x=0}if(this.centre.x>c-this.width){this.centre.x=c-this.width}if(0>this.centre.y){this.centre.y=0}if(this.centre.y>d-this.height){this.centre.y=d-this.height}};d.prototype.edit=function(a,b,c,d,e){this.width+=b;this.height+=c;if(this.width<-this.centre.x){this.width=-this.centre.x}if(this.width>d-this.centre.x){this.width=d-this.centre.x}if(this.height<-this.centre.y){this.height=-this.centre.y}if(this.height>e-this.centre.y){this.height=e-this.centre.y}};d.prototype.normalizeShape=function(){if(0>this.width){this.centre.x+=this.width;this.width=-this.width}if(0>this.height){this.centre.y+=this.height;this.height=-this.height}};d.prototype.makeSimilarCircle=function(){return new c(this.label,Math.round(this.centre.x+this.width/2),Math.round(this.centre.y+this.height/2),Math.round((this.width+this.height)/4))};d.prototype.makeSimilarPolygon=function(){return new e(this.label,[this.centre,this.centre.offset(0,this.height),this.centre.offset(this.width,this.height),this.centre.offset(this.width,0)])};d.prototype.getHandlePositions=function(){return{moveHandle:this.centre.offset(this.width/2,this.height/2),editHandles:[this.centre.offset(this.width,this.height)]}};function e(c,d){b.call(this,c,0,0);this.points=d?d.slice():[new a(10,10),new a(40,10),new a(10,40)];this.normalizeShape()}e.prototype=new b;e.prototype.getType=function(){return"polygon"};e.prototype.getCoordinates=function(){for(var a="",b=0;bc-f){this.centre.x=c-f}if(this.centre.y<-g){this.centre.y=-g}if(this.centre.y>d-h){this.centre.y=d-h}};e.prototype.edit=function(a,b,c,d,e){this.points[a].move(b,c);if(this.points[a].x<-this.centre.x){this.points[a].x=-this.centre.x}if(this.points[a].x>d-this.centre.x){this.points[a].x=d-this.centre.x}if(this.points[a].y<-this.centre.y){this.points[a].y=-this.centre.y}if(this.points[a].y>e-this.centre.y){this.points[a].y=e-this.centre.y}};e.prototype.addNewPointAfter=function(b){this.points.splice(b,0,new a(this.points[b].x,this.points[b].y))};e.prototype.normalizeShape=function(){var a,b=0,c=0;if(0===this.points.length){return}for(a=0;ac-this.radius){this.centre.x=c-this.radius}if(this.centre.yd-this.radius){this.centre.y=d-this.radius}};c.prototype.edit=function(a,b,c,d,e){this.radius+=b;var f=Math.min(this.centre.x,this.centre.y,d-this.centre.x,e-this.centre.y);if(this.radius>f){this.radius=f}if(this.radius<-f){this.radius=-f}};c.prototype.normalizeShape=function(){this.radius=Math.abs(this.radius)};c.prototype.makeSimilarRectangle=function(){return new d(this.label,this.centre.x-this.radius,this.centre.y-this.radius,2*this.radius,2*this.radius)};c.prototype.makeSimilarPolygon=function(){return new e(this.label,[this.centre.offset(-this.radius,-this.radius),this.centre.offset(-this.radius,this.radius),this.centre.offset(this.radius,this.radius),this.centre.offset(this.radius,-this.radius)])};c.prototype.getHandlePositions=function(){return{moveHandle:this.centre,editHandles:[this.centre.offset(this.radius,0)]}};function d(a,c,d,e,f){b.call(this,a,c,d);this.width=e||30;this.height=f||30}d.prototype=new b;d.prototype.getType=function(){return"rectangle"};d.prototype.getCoordinates=function(){return this.centre+";"+this.width+","+this.height};d.prototype.makeSvg=function(a){var b=h(a,"rect");this.updateSvg(b);return b};d.prototype.updateSvg=function(a){if(0<=this.width){a.childNodes[0].setAttribute("x",this.centre.x);a.childNodes[0].setAttribute("width",this.width)}else{a.childNodes[0].setAttribute("x",this.centre.x+this.width);a.childNodes[0].setAttribute("width",-this.width)}if(0<=this.height){a.childNodes[0].setAttribute("y",this.centre.y);a.childNodes[0].setAttribute("height",this.height)}else{a.childNodes[0].setAttribute("y",this.centre.y+this.height);a.childNodes[0].setAttribute("height",-this.height)}a.childNodes[1].setAttribute("x",this.centre.x+this.width/2);a.childNodes[1].setAttribute("y",this.centre.y+this.height/2+15);a.childNodes[1].textContent=this.label};d.prototype.parse=function(b,c){if(!b.match(/^\d+(\.\d+)?,\d+(\.\d+)?;\d+(\.\d+)?,\d+(\.\d+)?$/)){return!1}var d=b.split(";");this.centre=a.parse(d[0]);this.centre.x=this.centre.x*parseFloat(c);this.centre.y=this.centre.y*parseFloat(c);var e=a.parse(d[1]);this.width=e.x*parseFloat(c);this.height=e.y*parseFloat(c);return!0};d.prototype.move=function(a,b,c,d){this.centre.move(a,b);if(0>this.centre.x){this.centre.x=0}if(this.centre.x>c-this.width){this.centre.x=c-this.width}if(0>this.centre.y){this.centre.y=0}if(this.centre.y>d-this.height){this.centre.y=d-this.height}};d.prototype.edit=function(a,b,c,d,e){this.width+=b;this.height+=c;if(this.width<-this.centre.x){this.width=-this.centre.x}if(this.width>d-this.centre.x){this.width=d-this.centre.x}if(this.height<-this.centre.y){this.height=-this.centre.y}if(this.height>e-this.centre.y){this.height=e-this.centre.y}};d.prototype.normalizeShape=function(){if(0>this.width){this.centre.x+=this.width;this.width=-this.width}if(0>this.height){this.centre.y+=this.height;this.height=-this.height}};d.prototype.makeSimilarCircle=function(){return new c(this.label,Math.round(this.centre.x+this.width/2),Math.round(this.centre.y+this.height/2),Math.round((this.width+this.height)/4))};d.prototype.makeSimilarPolygon=function(){return new e(this.label,[this.centre,this.centre.offset(0,this.height),this.centre.offset(this.width,this.height),this.centre.offset(this.width,0)])};d.prototype.getHandlePositions=function(){return{moveHandle:this.centre.offset(this.width/2,this.height/2),editHandles:[this.centre.offset(this.width,this.height)]}};function e(c,d){b.call(this,c,0,0);this.points=d?d.slice():[new a(10,10),new a(40,10),new a(10,40)];this.normalizeShape();this.ratio=1}e.prototype=new b;e.prototype.getType=function(){return"polygon"};e.prototype.getCoordinates=function(){for(var a="",b=0;bc-f){this.centre.x=c-f}if(this.centre.y<-g){this.centre.y=-g}if(this.centre.y>d-h){this.centre.y=d-h}};e.prototype.edit=function(a,b,c,d,e){this.points[a].move(b,c);if(this.points[a].x<-this.centre.x){this.points[a].x=-this.centre.x}if(this.points[a].x>d-this.centre.x){this.points[a].x=d-this.centre.x}if(this.points[a].y<-this.centre.y){this.points[a].y=-this.centre.y}if(this.points[a].y>e-this.centre.y){this.points[a].y=e-this.centre.y}};e.prototype.addNewPointAfter=function(b){this.points.splice(b,0,new a(this.points[b].x,this.points[b].y))};e.prototype.normalizeShape=function(){var a,b=0,c=0;if(0===this.points.length){return}for(a=0;a.\n\n/* eslint max-depth: [\"error\", 8] */\n\n/**\n * Library of classes for handling simple shapes.\n *\n * These classes can represent shapes, let you alter them, can go to and from a string\n * representation, and can give you an SVG representation.\n *\n * @package qtype_ddmarker\n * @subpackage shapes\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(function() {\n\n \"use strict\";\n\n /**\n * A point, with x and y coordinates.\n *\n * @param {int} x centre X.\n * @param {int} y centre Y.\n * @constructor\n */\n function Point(x, y) {\n this.x = x;\n this.y = y;\n }\n\n /**\n * Standard toString method.\n * @returns {string} \"x;y\";\n */\n Point.prototype.toString = function() {\n return this.x + ',' + this.y;\n };\n\n /**\n * Move a point\n * @param {int} dx x offset\n * @param {int} dy y offset\n */\n Point.prototype.move = function(dx, dy) {\n this.x += dx;\n this.y += dy;\n };\n\n /**\n * Return a new point that is a certain position relative to this one.\n *\n * @param {(int|Point)} offsetX if a point, offset by this points coordinates, else and int x offset.\n * @param {int} [offsetY] used if offsetX is an int, the corresponding y offset.\n * @return {Point} the new point.\n */\n Point.prototype.offset = function(offsetX, offsetY) {\n if (offsetX instanceof Point) {\n offsetY = offsetX.y;\n offsetX = offsetX.x;\n }\n return new Point(this.x + offsetX, this.y + offsetY);\n };\n\n /**\n * Make a point from the string representation.\n *\n * @param {String} coordinates \"x,y\".\n * @return {Point} the point. Throws an exception if input is not valid.\n */\n Point.parse = function(coordinates) {\n var bits = coordinates.split(',');\n if (bits.length !== 2) {\n throw new Error(coordinates + ' is not a valid point');\n }\n return new Point(Math.round(bits[0]), Math.round(bits[1]));\n };\n\n\n /**\n * Shape constructor. Abstract class to represent the different types of drop zone shapes.\n *\n * @param {String} [label] name of this area.\n * @param {int} [x] centre X.\n * @param {int} [y] centre Y.\n * @constructor\n */\n function Shape(label, x, y) {\n this.label = label;\n this.centre = new Point(x || 0, y || 0);\n }\n\n /**\n * Get the type of shape.\n *\n * @return {String} 'circle', 'rectangle' or 'polygon';\n */\n Shape.prototype.getType = function() {\n throw new Error('Not implemented.');\n };\n\n /**\n * Get the string representation of this shape.\n *\n * @return {String} coordinates as they need to be typed into the form.\n */\n Shape.prototype.getCoordinates = function() {\n throw new Error('Not implemented.');\n };\n\n /**\n * Update the shape from the string representation.\n *\n * @param {String} coordinates in the form returned by getCoordinates.\n * @return {boolean} true if the string could be parsed and the shape updated, else false.\n */\n Shape.prototype.parse = function(coordinates) {\n void (coordinates);\n throw new Error('Not implemented.');\n };\n\n /**\n * Move the entire shape by this offset.\n *\n * @param {int} dx x offset.\n * @param {int} dy y offset.\n * @param {int} maxX ensure that after editing, the shape lies between 0 and maxX on the x-axis.\n * @param {int} maxY ensure that after editing, the shape lies between 0 and maxX on the y-axis.\n */\n Shape.prototype.move = function(dx, dy, maxX, maxY) {\n void (maxY);\n };\n\n /**\n * Move one of the edit handles by this offset.\n *\n * @param {int} handleIndex which handle was moved.\n * @param {int} dx x offset.\n * @param {int} dy y offset.\n * @param {int} maxX ensure that after editing, the shape lies between 0 and maxX on the x-axis.\n * @param {int} maxY ensure that after editing, the shape lies between 0 and maxX on the y-axis.\n */\n Shape.prototype.edit = function(handleIndex, dx, dy, maxX, maxY) {\n void (maxY);\n };\n\n /**\n * Update the properties of this shape after a sequence of edits.\n *\n * For example make sure the circle radius is positive, of the polygon centre is centred.\n */\n Shape.prototype.normalizeShape = function() {\n void (1); // To make CiBoT happy.\n };\n\n /**\n * Get the string representation of this shape.\n *\n * @param {SVGElement} svg the SVG graphic to add this shape to.\n * @return {SVGElement} SVG representation of this shape.\n */\n Shape.prototype.makeSvg = function(svg) {\n void (svg);\n throw new Error('Not implemented.');\n };\n\n /**\n * Update the SVG representation of this shape.\n *\n * @param {SVGElement} svgEl the SVG representation of this shape.\n */\n Shape.prototype.updateSvg = function(svgEl) {\n void (svgEl);\n };\n\n /**\n * Make a circle similar to this shape.\n *\n * @return {Circle} a circle that is about the same size and position as this shape.\n */\n Shape.prototype.makeSimilarCircle = function() {\n throw new Error('Not implemented.');\n };\n\n /**\n * Make a rectangle similar to this shape.\n *\n * @return {Rectangle} a rectangle that is about the same size and position as this shape.\n */\n Shape.prototype.makeSimilarRectangle = function() {\n throw new Error('Not implemented.');\n };\n\n /**\n * Make a polygon similar to this shape.\n *\n * @return {Polygon} a polygon that is about the same size and position as this shape.\n */\n Shape.prototype.makeSimilarPolygon = function() {\n throw new Error('Not implemented.');\n };\n\n /**\n * Get the handles that should be offered to edit this shape, or null if not appropriate.\n *\n * @return {[Object]} with properties moveHandle {Point} and editHandles {Point[]}\n */\n Shape.prototype.getHandlePositions = function() {\n return null;\n };\n\n\n /**\n * A shape that is a circle.\n *\n * @param {String} label name of this area.\n * @param {int} [x] centre X.\n * @param {int} [y] centre Y.\n * @param {int} [radius] radius.\n * @constructor\n */\n function Circle(label, x, y, radius) {\n x = x || 15;\n y = y || 15;\n Shape.call(this, label, x, y);\n this.radius = radius || 15;\n }\n Circle.prototype = new Shape();\n\n Circle.prototype.getType = function() {\n return 'circle';\n };\n\n Circle.prototype.getCoordinates = function() {\n return this.centre + ';' + Math.abs(this.radius);\n };\n\n Circle.prototype.makeSvg = function(svg) {\n var svgEl = createSvgShapeGroup(svg, 'circle');\n this.updateSvg(svgEl);\n return svgEl;\n };\n\n Circle.prototype.updateSvg = function(svgEl) {\n svgEl.childNodes[0].setAttribute('cx', this.centre.x);\n svgEl.childNodes[0].setAttribute('cy', this.centre.y);\n svgEl.childNodes[0].setAttribute('r', Math.abs(this.radius));\n svgEl.childNodes[1].setAttribute('x', this.centre.x);\n svgEl.childNodes[1].setAttribute('y', this.centre.y + 15);\n svgEl.childNodes[1].textContent = this.label;\n };\n\n Circle.prototype.parse = function(coordinates) {\n if (!coordinates.match(/^\\d+,\\d+;\\d+$/)) {\n return false;\n }\n\n var bits = coordinates.split(';');\n this.centre = Point.parse(bits[0]);\n this.radius = Math.round(bits[1]);\n return true;\n };\n\n Circle.prototype.move = function(dx, dy, maxX, maxY) {\n this.centre.move(dx, dy);\n if (this.centre.x < this.radius) {\n this.centre.x = this.radius;\n }\n if (this.centre.x > maxX - this.radius) {\n this.centre.x = maxX - this.radius;\n }\n if (this.centre.y < this.radius) {\n this.centre.y = this.radius;\n }\n if (this.centre.y > maxY - this.radius) {\n this.centre.y = maxY - this.radius;\n }\n };\n\n Circle.prototype.edit = function(handleIndex, dx, dy, maxX, maxY) {\n this.radius += dx;\n var limit = Math.min(this.centre.x, this.centre.y, maxX - this.centre.x, maxY - this.centre.y);\n if (this.radius > limit) {\n this.radius = limit;\n }\n if (this.radius < -limit) {\n this.radius = -limit;\n }\n };\n\n /**\n * Update the properties of this shape after a sequence of edits.\n *\n * For example make sure the circle radius is positive, of the polygon centre is centred.\n */\n Circle.prototype.normalizeShape = function() {\n this.radius = Math.abs(this.radius);\n };\n\n Circle.prototype.makeSimilarRectangle = function() {\n return new Rectangle(this.label,\n this.centre.x - this.radius, this.centre.y - this.radius,\n this.radius * 2, this.radius * 2);\n };\n\n Circle.prototype.makeSimilarPolygon = function() {\n // We make a similar square, so if you go to and from Rectangle afterwards, it is loss-less.\n return new Polygon(this.label, [\n this.centre.offset(-this.radius, -this.radius), this.centre.offset(-this.radius, this.radius),\n this.centre.offset(this.radius, this.radius), this.centre.offset(this.radius, -this.radius)]);\n };\n\n Circle.prototype.getHandlePositions = function() {\n return {\n moveHandle: this.centre,\n editHandles: [this.centre.offset(this.radius, 0)]\n };\n };\n\n\n /**\n * A shape that is a rectangle.\n *\n * @param {String} label name of this area.\n * @param {int} [x] top left X.\n * @param {int} [y] top left Y.\n * @param {int} [width] width.\n * @param {int} [height] height.\n * @constructor\n */\n function Rectangle(label, x, y, width, height) {\n Shape.call(this, label, x, y);\n this.width = width || 30;\n this.height = height || 30;\n }\n Rectangle.prototype = new Shape();\n\n Rectangle.prototype.getType = function() {\n return 'rectangle';\n };\n\n Rectangle.prototype.getCoordinates = function() {\n return this.centre + ';' + this.width + ',' + this.height;\n };\n\n Rectangle.prototype.makeSvg = function(svg) {\n var svgEl = createSvgShapeGroup(svg, 'rect');\n this.updateSvg(svgEl);\n return svgEl;\n };\n\n Rectangle.prototype.updateSvg = function(svgEl) {\n if (this.width >= 0) {\n svgEl.childNodes[0].setAttribute('x', this.centre.x);\n svgEl.childNodes[0].setAttribute('width', this.width);\n } else {\n svgEl.childNodes[0].setAttribute('x', this.centre.x + this.width);\n svgEl.childNodes[0].setAttribute('width', -this.width);\n }\n if (this.height >= 0) {\n svgEl.childNodes[0].setAttribute('y', this.centre.y);\n svgEl.childNodes[0].setAttribute('height', this.height);\n } else {\n svgEl.childNodes[0].setAttribute('y', this.centre.y + this.height);\n svgEl.childNodes[0].setAttribute('height', -this.height);\n }\n\n svgEl.childNodes[1].setAttribute('x', this.centre.x + this.width / 2);\n svgEl.childNodes[1].setAttribute('y', this.centre.y + this.height / 2 + 15);\n svgEl.childNodes[1].textContent = this.label;\n };\n\n Rectangle.prototype.parse = function(coordinates) {\n if (!coordinates.match(/^\\d+,\\d+;\\d+,\\d+$/)) {\n return false;\n }\n\n var bits = coordinates.split(';');\n this.centre = Point.parse(bits[0]);\n var size = Point.parse(bits[1]);\n this.width = size.x;\n this.height = size.y;\n return true;\n };\n\n Rectangle.prototype.move = function(dx, dy, maxX, maxY) {\n this.centre.move(dx, dy);\n if (this.centre.x < 0) {\n this.centre.x = 0;\n }\n if (this.centre.x > maxX - this.width) {\n this.centre.x = maxX - this.width;\n }\n if (this.centre.y < 0) {\n this.centre.y = 0;\n }\n if (this.centre.y > maxY - this.height) {\n this.centre.y = maxY - this.height;\n }\n };\n\n Rectangle.prototype.edit = function(handleIndex, dx, dy, maxX, maxY) {\n this.width += dx;\n this.height += dy;\n if (this.width < -this.centre.x) {\n this.width = -this.centre.x;\n }\n if (this.width > maxX - this.centre.x) {\n this.width = maxX - this.centre.x;\n }\n if (this.height < -this.centre.y) {\n this.height = -this.centre.y;\n }\n if (this.height > maxY - this.centre.y) {\n this.height = maxY - this.centre.y;\n }\n };\n\n /**\n * Update the properties of this shape after a sequence of edits.\n *\n * For example make sure the circle radius is positive, of the polygon centre is centred.\n */\n Rectangle.prototype.normalizeShape = function() {\n if (this.width < 0) {\n this.centre.x += this.width;\n this.width = -this.width;\n }\n if (this.height < 0) {\n this.centre.y += this.height;\n this.height = -this.height;\n }\n };\n\n Rectangle.prototype.makeSimilarCircle = function() {\n return new Circle(this.label,\n Math.round(this.centre.x + this.width / 2),\n Math.round(this.centre.y + this.height / 2),\n Math.round((this.width + this.height) / 4));\n };\n\n Rectangle.prototype.makeSimilarPolygon = function() {\n return new Polygon(this.label, [\n this.centre, this.centre.offset(0, this.height),\n this.centre.offset(this.width, this.height), this.centre.offset(this.width, 0)]);\n };\n\n Rectangle.prototype.getHandlePositions = function() {\n return {\n moveHandle: this.centre.offset(this.width / 2, this.height / 2),\n editHandles: [this.centre.offset(this.width, this.height)]\n };\n };\n\n\n /**\n * A shape that is a polygon.\n *\n * @param {String} label name of this area.\n * @param {Point[]} [points] position of the vertices relative to (centreX, centreY).\n * each object in the array should have two\n * @constructor\n */\n function Polygon(label, points) {\n Shape.call(this, label, 0, 0);\n this.points = points ? points.slice() : [new Point(10, 10), new Point(40, 10), new Point(10, 40)];\n this.normalizeShape();\n }\n Polygon.prototype = new Shape();\n\n Polygon.prototype.getType = function() {\n return 'polygon';\n };\n\n Polygon.prototype.getCoordinates = function() {\n var coordinates = '';\n for (var i = 0; i < this.points.length; i++) {\n coordinates += this.centre.offset(this.points[i]) + ';';\n }\n return coordinates.slice(0, coordinates.length - 1); // Strip off the last ';'.\n };\n\n Polygon.prototype.makeSvg = function(svg) {\n var svgEl = createSvgShapeGroup(svg, 'polygon');\n this.updateSvg(svgEl);\n return svgEl;\n };\n\n Polygon.prototype.updateSvg = function(svgEl) {\n svgEl.childNodes[0].setAttribute('points', this.getCoordinates().replace(/[,;]/g, ' '));\n svgEl.childNodes[1].setAttribute('x', this.centre.x);\n svgEl.childNodes[1].setAttribute('y', this.centre.y + 15);\n svgEl.childNodes[1].textContent = this.label;\n };\n\n Polygon.prototype.parse = function(coordinates) {\n if (!coordinates.match(/^\\d+,\\d+(?:;\\d+,\\d+)*$/)) {\n return false;\n }\n\n var bits = coordinates.split(';');\n var points = [];\n for (var i = 0; i < bits.length; i++) {\n points.push(Point.parse(bits[i]));\n }\n\n this.points = points;\n this.centre.x = 0;\n this.centre.y = 0;\n this.normalizeShape();\n\n return true;\n };\n\n Polygon.prototype.move = function(dx, dy, maxX, maxY) {\n this.centre.move(dx, dy);\n var bbXMin = maxX,\n bbXMax = 0,\n bbYMin = maxY,\n bbYMax = 0;\n // Computer centre.\n for (var i = 0; i < this.points.length; i++) {\n bbXMin = Math.min(bbXMin, this.points[i].x);\n bbXMax = Math.max(bbXMax, this.points[i].x);\n bbYMin = Math.min(bbYMin, this.points[i].y);\n bbYMax = Math.max(bbYMax, this.points[i].y);\n }\n if (this.centre.x < -bbXMin) {\n this.centre.x = -bbXMin;\n }\n if (this.centre.x > maxX - bbXMax) {\n this.centre.x = maxX - bbXMax;\n }\n if (this.centre.y < -bbYMin) {\n this.centre.y = -bbYMin;\n }\n if (this.centre.y > maxY - bbYMax) {\n this.centre.y = maxY - bbYMax;\n }\n };\n\n Polygon.prototype.edit = function(handleIndex, dx, dy, maxX, maxY) {\n this.points[handleIndex].move(dx, dy);\n if (this.points[handleIndex].x < -this.centre.x) {\n this.points[handleIndex].x = -this.centre.x;\n }\n if (this.points[handleIndex].x > maxX - this.centre.x) {\n this.points[handleIndex].x = maxX - this.centre.x;\n }\n if (this.points[handleIndex].y < -this.centre.y) {\n this.points[handleIndex].y = -this.centre.y;\n }\n if (this.points[handleIndex].y > maxY - this.centre.y) {\n this.points[handleIndex].y = maxY - this.centre.y;\n }\n };\n\n /**\n * Add a new point after the given point, with the same co-ordinates.\n *\n * This does not automatically normalise.\n *\n * @param {int} pointIndex the index of the vertex after which to insert this new one.\n */\n Polygon.prototype.addNewPointAfter = function(pointIndex) {\n this.points.splice(pointIndex, 0,\n new Point(this.points[pointIndex].x, this.points[pointIndex].y));\n };\n\n Polygon.prototype.normalizeShape = function() {\n var i,\n x = 0,\n y = 0;\n\n if (this.points.length === 0) {\n return;\n }\n\n // Computer centre.\n for (i = 0; i < this.points.length; i++) {\n x += this.points[i].x;\n y += this.points[i].y;\n }\n x = Math.round(x / this.points.length);\n y = Math.round(y / this.points.length);\n\n if (x === 0 && y === 0) {\n return;\n }\n\n for (i = 0; i < this.points.length; i++) {\n this.points[i].move(-x, -y);\n }\n this.centre.move(x, y);\n };\n\n Polygon.prototype.makeSimilarCircle = function() {\n return this.makeSimilarRectangle().makeSimilarCircle();\n };\n\n Polygon.prototype.makeSimilarRectangle = function() {\n var p,\n minX = 0,\n maxX = 0,\n minY = 0,\n maxY = 0;\n for (var i = 0; i < this.points.length; i++) {\n p = this.points[i];\n minX = Math.min(minX, p.x);\n maxX = Math.max(maxX, p.x);\n minY = Math.min(minY, p.y);\n maxY = Math.max(maxY, p.y);\n }\n return new Rectangle(this.label,\n this.centre.x + minX, this.centre.y + minY,\n Math.max(maxX - minX, 10), Math.max(maxY - minY, 10));\n };\n\n Polygon.prototype.getHandlePositions = function() {\n var editHandles = [];\n for (var i = 0; i < this.points.length; i++) {\n editHandles.push(this.points[i].offset(this.centre.x, this.centre.y));\n }\n\n return {\n moveHandle: this.centre,\n editHandles: editHandles\n };\n };\n\n\n /**\n * Not a shape (null object pattern).\n *\n * @param {String} label name of this area.\n * @constructor\n */\n function NullShape(label) {\n Shape.call(this, label);\n }\n NullShape.prototype = new Shape();\n\n NullShape.prototype.getType = function() {\n return 'null';\n };\n\n NullShape.prototype.getCoordinates = function() {\n return '';\n };\n\n NullShape.prototype.makeSvg = function(svg) {\n void (svg);\n return null;\n };\n\n NullShape.prototype.updateSvg = function(svgEl) {\n void (svgEl);\n };\n\n NullShape.prototype.parse = function(coordinates) {\n void (coordinates);\n return false;\n };\n\n NullShape.prototype.makeSimilarCircle = function() {\n return new Circle(this.label);\n };\n\n NullShape.prototype.makeSimilarRectangle = function() {\n return new Rectangle(this.label);\n };\n\n NullShape.prototype.makeSimilarPolygon = function() {\n return new Polygon(this.label);\n };\n\n\n /**\n * Make a new SVG DOM element as a child of svg.\n *\n * @param {SVGElement} svg the parent node.\n * @param {String} tagName the tag name.\n * @return {SVGElement} the newly created node.\n */\n function createSvgElement(svg, tagName) {\n var svgEl = svg.ownerDocument.createElementNS('http://www.w3.org/2000/svg', tagName);\n svg.appendChild(svgEl);\n return svgEl;\n }\n\n /**\n * Make a group SVG DOM elements containing a shape of the given type as first child,\n * and a text label as the second child.\n *\n * @param {SVGElement} svg the parent node.\n * @param {String} tagName the tag name.\n * @return {SVGElement} the newly created g element.\n */\n function createSvgShapeGroup(svg, tagName) {\n var svgEl = createSvgElement(svg, 'g');\n createSvgElement(svgEl, tagName).setAttribute('class', 'shape');\n createSvgElement(svgEl, 'text').setAttribute('class', 'shapeLabel');\n return svgEl;\n }\n\n /**\n * @alias module:qtype_ddmarker/shapes\n */\n return {\n /**\n * A point, with x and y coordinates.\n *\n * @param {int} x centre X.\n * @param {int} y centre Y.\n * @constructor\n */\n Point: Point,\n\n /**\n * A point, with x and y coordinates.\n *\n * @param {int} x centre X.\n * @param {int} y centre Y.\n * @constructor\n */\n Shape: Shape,\n\n /**\n * A shape that is a circle.\n *\n * @param {String} label name of this area.\n * @param {int} [x] centre X.\n * @param {int} [y] centre Y.\n * @param {int} [radius] radius.\n * @constructor\n */\n Circle: Circle,\n\n /**\n * A shape that is a rectangle.\n *\n * @param {String} label name of this area.\n * @param {int} [x] top left X.\n * @param {int} [y] top left Y.\n * @param {int} [width] width.\n * @param {int} [height] height.\n * @constructor\n */\n Rectangle: Rectangle,\n\n /**\n * A shape that is a polygon.\n *\n * @param {String} label name of this area.\n * @param {Point[]} [points] position of the vertices relative to (centreX, centreY).\n * each object in the array should have two\n * @constructor\n */\n Polygon: Polygon,\n\n /**\n * Not a shape (null object pattern).\n *\n * @param {String} label name of this area.\n * @constructor\n */\n NullShape: NullShape,\n\n /**\n * Make a new SVG DOM element as a child of svg.\n *\n * @param {SVGElement} svg the parent node.\n * @param {String} tagName the tag name.\n * @return {SVGElement} the newly created node.\n */\n createSvgElement: createSvgElement,\n\n /**\n * Make a shape of the given type.\n *\n * @param {String} shapeType\n * @param {String} label\n * @return {Shape} the requested shape.\n */\n make: function(shapeType, label) {\n switch (shapeType) {\n case 'circle':\n return new Circle(label);\n case 'rectangle':\n return new Rectangle(label);\n case 'polygon':\n return new Polygon(label);\n default:\n return new NullShape(label);\n }\n },\n\n /**\n * Make a shape of the given type that is similar to the shape of the original type.\n *\n * @param {String} shapeType the new type of shape to make\n * @param {Shape} shape the shape to copy\n * @return {Shape} the similar shape of a different type.\n */\n getSimilar: function(shapeType, shape) {\n if (shapeType === shape.getType()) {\n return shape;\n }\n switch (shapeType) {\n case 'circle':\n return shape.makeSimilarCircle();\n case 'rectangle':\n return shape.makeSimilarRectangle();\n case 'polygon':\n return shape.makeSimilarPolygon();\n default:\n return new NullShape(shape.label);\n }\n }\n };\n});\n"],"file":"shapes.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/shapes.js"],"names":["define","Point","x","y","prototype","toString","move","dx","dy","offset","offsetX","offsetY","parse","coordinates","bits","split","length","Error","Math","round","Shape","label","centre","getType","getCoordinates","ratio","edit","normalizeShape","makeSvg","updateSvg","makeSimilarCircle","makeSimilarRectangle","makeSimilarPolygon","getHandlePositions","Circle","radius","call","abs","svg","svgEl","createSvgShapeGroup","childNodes","setAttribute","textContent","match","parseFloat","maxX","maxY","handleIndex","limit","min","Rectangle","Polygon","moveHandle","editHandles","width","height","size","points","slice","i","replace","push","bbXMin","bbXMax","bbYMin","bbYMax","max","addNewPointAfter","pointIndex","splice","p","minX","minY","NullShape","createSvgElement","tagName","ownerDocument","createElementNS","appendChild","make","shapeType","getSimilar","shape"],"mappings":"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","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 .\n\n/* eslint max-depth: [\"error\", 8] */\n\n/**\n * Library of classes for handling simple shapes.\n *\n * These classes can represent shapes, let you alter them, can go to and from a string\n * representation, and can give you an SVG representation.\n *\n * @package qtype_ddmarker\n * @subpackage shapes\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(function() {\n\n \"use strict\";\n\n /**\n * A point, with x and y coordinates.\n *\n * @param {int} x centre X.\n * @param {int} y centre Y.\n * @constructor\n */\n function Point(x, y) {\n this.x = x;\n this.y = y;\n }\n\n /**\n * Standard toString method.\n * @returns {string} \"x;y\";\n */\n Point.prototype.toString = function() {\n return this.x + ',' + this.y;\n };\n\n /**\n * Move a point\n * @param {int} dx x offset\n * @param {int} dy y offset\n */\n Point.prototype.move = function(dx, dy) {\n this.x += dx;\n this.y += dy;\n };\n\n /**\n * Return a new point that is a certain position relative to this one.\n *\n * @param {(int|Point)} offsetX if a point, offset by this points coordinates, else and int x offset.\n * @param {int} [offsetY] used if offsetX is an int, the corresponding y offset.\n * @return {Point} the new point.\n */\n Point.prototype.offset = function(offsetX, offsetY) {\n if (offsetX instanceof Point) {\n offsetY = offsetX.y;\n offsetX = offsetX.x;\n }\n return new Point(this.x + offsetX, this.y + offsetY);\n };\n\n /**\n * Make a point from the string representation.\n *\n * @param {String} coordinates \"x,y\".\n * @return {Point} the point. Throws an exception if input is not valid.\n */\n Point.parse = function(coordinates) {\n var bits = coordinates.split(',');\n if (bits.length !== 2) {\n throw new Error(coordinates + ' is not a valid point');\n }\n return new Point(Math.round(bits[0]), Math.round(bits[1]));\n };\n\n\n /**\n * Shape constructor. Abstract class to represent the different types of drop zone shapes.\n *\n * @param {String} [label] name of this area.\n * @param {int} [x] centre X.\n * @param {int} [y] centre Y.\n * @constructor\n */\n function Shape(label, x, y) {\n this.label = label;\n this.centre = new Point(x || 0, y || 0);\n }\n\n /**\n * Get the type of shape.\n *\n * @return {String} 'circle', 'rectangle' or 'polygon';\n */\n Shape.prototype.getType = function() {\n throw new Error('Not implemented.');\n };\n\n /**\n * Get the string representation of this shape.\n *\n * @return {String} coordinates as they need to be typed into the form.\n */\n Shape.prototype.getCoordinates = function() {\n throw new Error('Not implemented.');\n };\n\n /**\n * Update the shape from the string representation.\n *\n * @param {String} coordinates in the form returned by getCoordinates.\n * @param {number} ratio Ratio to scale.\n * @return {boolean} true if the string could be parsed and the shape updated, else false.\n */\n Shape.prototype.parse = function(coordinates, ratio) {\n void (coordinates, ratio);\n throw new Error('Not implemented.');\n };\n\n /**\n * Move the entire shape by this offset.\n *\n * @param {int} dx x offset.\n * @param {int} dy y offset.\n * @param {int} maxX ensure that after editing, the shape lies between 0 and maxX on the x-axis.\n * @param {int} maxY ensure that after editing, the shape lies between 0 and maxX on the y-axis.\n */\n Shape.prototype.move = function(dx, dy, maxX, maxY) {\n void (maxY);\n };\n\n /**\n * Move one of the edit handles by this offset.\n *\n * @param {int} handleIndex which handle was moved.\n * @param {int} dx x offset.\n * @param {int} dy y offset.\n * @param {int} maxX ensure that after editing, the shape lies between 0 and maxX on the x-axis.\n * @param {int} maxY ensure that after editing, the shape lies between 0 and maxX on the y-axis.\n */\n Shape.prototype.edit = function(handleIndex, dx, dy, maxX, maxY) {\n void (maxY);\n };\n\n /**\n * Update the properties of this shape after a sequence of edits.\n *\n * For example make sure the circle radius is positive, of the polygon centre is centred.\n */\n Shape.prototype.normalizeShape = function() {\n void (1); // To make CiBoT happy.\n };\n\n /**\n * Get the string representation of this shape.\n *\n * @param {SVGElement} svg the SVG graphic to add this shape to.\n * @return {SVGElement} SVG representation of this shape.\n */\n Shape.prototype.makeSvg = function(svg) {\n void (svg);\n throw new Error('Not implemented.');\n };\n\n /**\n * Update the SVG representation of this shape.\n *\n * @param {SVGElement} svgEl the SVG representation of this shape.\n */\n Shape.prototype.updateSvg = function(svgEl) {\n void (svgEl);\n };\n\n /**\n * Make a circle similar to this shape.\n *\n * @return {Circle} a circle that is about the same size and position as this shape.\n */\n Shape.prototype.makeSimilarCircle = function() {\n throw new Error('Not implemented.');\n };\n\n /**\n * Make a rectangle similar to this shape.\n *\n * @return {Rectangle} a rectangle that is about the same size and position as this shape.\n */\n Shape.prototype.makeSimilarRectangle = function() {\n throw new Error('Not implemented.');\n };\n\n /**\n * Make a polygon similar to this shape.\n *\n * @return {Polygon} a polygon that is about the same size and position as this shape.\n */\n Shape.prototype.makeSimilarPolygon = function() {\n throw new Error('Not implemented.');\n };\n\n /**\n * Get the handles that should be offered to edit this shape, or null if not appropriate.\n *\n * @return {[Object]} with properties moveHandle {Point} and editHandles {Point[]}\n */\n Shape.prototype.getHandlePositions = function() {\n return null;\n };\n\n\n /**\n * A shape that is a circle.\n *\n * @param {String} label name of this area.\n * @param {int} [x] centre X.\n * @param {int} [y] centre Y.\n * @param {int} [radius] radius.\n * @constructor\n */\n function Circle(label, x, y, radius) {\n x = x || 15;\n y = y || 15;\n Shape.call(this, label, x, y);\n this.radius = radius || 15;\n }\n Circle.prototype = new Shape();\n\n Circle.prototype.getType = function() {\n return 'circle';\n };\n\n Circle.prototype.getCoordinates = function() {\n return this.centre + ';' + Math.abs(this.radius);\n };\n\n Circle.prototype.makeSvg = function(svg) {\n var svgEl = createSvgShapeGroup(svg, 'circle');\n this.updateSvg(svgEl);\n return svgEl;\n };\n\n Circle.prototype.updateSvg = function(svgEl) {\n svgEl.childNodes[0].setAttribute('cx', this.centre.x);\n svgEl.childNodes[0].setAttribute('cy', this.centre.y);\n svgEl.childNodes[0].setAttribute('r', Math.abs(this.radius));\n svgEl.childNodes[1].setAttribute('x', this.centre.x);\n svgEl.childNodes[1].setAttribute('y', this.centre.y + 15);\n svgEl.childNodes[1].textContent = this.label;\n };\n\n Circle.prototype.parse = function(coordinates, ratio) {\n if (!coordinates.match(/^\\d+(\\.\\d+)?,\\d+(\\.\\d+)?;\\d+(\\.\\d+)?$/)) {\n return false;\n }\n\n var bits = coordinates.split(';');\n this.centre = Point.parse(bits[0]);\n this.centre.x = this.centre.x * parseFloat(ratio);\n this.centre.y = this.centre.y * parseFloat(ratio);\n this.radius = Math.round(bits[1]) * parseFloat(ratio);\n return true;\n };\n\n Circle.prototype.move = function(dx, dy, maxX, maxY) {\n this.centre.move(dx, dy);\n if (this.centre.x < this.radius) {\n this.centre.x = this.radius;\n }\n if (this.centre.x > maxX - this.radius) {\n this.centre.x = maxX - this.radius;\n }\n if (this.centre.y < this.radius) {\n this.centre.y = this.radius;\n }\n if (this.centre.y > maxY - this.radius) {\n this.centre.y = maxY - this.radius;\n }\n };\n\n Circle.prototype.edit = function(handleIndex, dx, dy, maxX, maxY) {\n this.radius += dx;\n var limit = Math.min(this.centre.x, this.centre.y, maxX - this.centre.x, maxY - this.centre.y);\n if (this.radius > limit) {\n this.radius = limit;\n }\n if (this.radius < -limit) {\n this.radius = -limit;\n }\n };\n\n /**\n * Update the properties of this shape after a sequence of edits.\n *\n * For example make sure the circle radius is positive, of the polygon centre is centred.\n */\n Circle.prototype.normalizeShape = function() {\n this.radius = Math.abs(this.radius);\n };\n\n Circle.prototype.makeSimilarRectangle = function() {\n return new Rectangle(this.label,\n this.centre.x - this.radius, this.centre.y - this.radius,\n this.radius * 2, this.radius * 2);\n };\n\n Circle.prototype.makeSimilarPolygon = function() {\n // We make a similar square, so if you go to and from Rectangle afterwards, it is loss-less.\n return new Polygon(this.label, [\n this.centre.offset(-this.radius, -this.radius), this.centre.offset(-this.radius, this.radius),\n this.centre.offset(this.radius, this.radius), this.centre.offset(this.radius, -this.radius)]);\n };\n\n Circle.prototype.getHandlePositions = function() {\n return {\n moveHandle: this.centre,\n editHandles: [this.centre.offset(this.radius, 0)]\n };\n };\n\n\n /**\n * A shape that is a rectangle.\n *\n * @param {String} label name of this area.\n * @param {int} [x] top left X.\n * @param {int} [y] top left Y.\n * @param {int} [width] width.\n * @param {int} [height] height.\n * @constructor\n */\n function Rectangle(label, x, y, width, height) {\n Shape.call(this, label, x, y);\n this.width = width || 30;\n this.height = height || 30;\n }\n Rectangle.prototype = new Shape();\n\n Rectangle.prototype.getType = function() {\n return 'rectangle';\n };\n\n Rectangle.prototype.getCoordinates = function() {\n return this.centre + ';' + this.width + ',' + this.height;\n };\n\n Rectangle.prototype.makeSvg = function(svg) {\n var svgEl = createSvgShapeGroup(svg, 'rect');\n this.updateSvg(svgEl);\n return svgEl;\n };\n\n Rectangle.prototype.updateSvg = function(svgEl) {\n if (this.width >= 0) {\n svgEl.childNodes[0].setAttribute('x', this.centre.x);\n svgEl.childNodes[0].setAttribute('width', this.width);\n } else {\n svgEl.childNodes[0].setAttribute('x', this.centre.x + this.width);\n svgEl.childNodes[0].setAttribute('width', -this.width);\n }\n if (this.height >= 0) {\n svgEl.childNodes[0].setAttribute('y', this.centre.y);\n svgEl.childNodes[0].setAttribute('height', this.height);\n } else {\n svgEl.childNodes[0].setAttribute('y', this.centre.y + this.height);\n svgEl.childNodes[0].setAttribute('height', -this.height);\n }\n\n svgEl.childNodes[1].setAttribute('x', this.centre.x + this.width / 2);\n svgEl.childNodes[1].setAttribute('y', this.centre.y + this.height / 2 + 15);\n svgEl.childNodes[1].textContent = this.label;\n };\n\n Rectangle.prototype.parse = function(coordinates, ratio) {\n if (!coordinates.match(/^\\d+(\\.\\d+)?,\\d+(\\.\\d+)?;\\d+(\\.\\d+)?,\\d+(\\.\\d+)?$/)) {\n return false;\n }\n\n var bits = coordinates.split(';');\n this.centre = Point.parse(bits[0]);\n this.centre.x = this.centre.x * parseFloat(ratio);\n this.centre.y = this.centre.y * parseFloat(ratio);\n var size = Point.parse(bits[1]);\n this.width = size.x * parseFloat(ratio);\n this.height = size.y * parseFloat(ratio);\n return true;\n };\n\n Rectangle.prototype.move = function(dx, dy, maxX, maxY) {\n this.centre.move(dx, dy);\n if (this.centre.x < 0) {\n this.centre.x = 0;\n }\n if (this.centre.x > maxX - this.width) {\n this.centre.x = maxX - this.width;\n }\n if (this.centre.y < 0) {\n this.centre.y = 0;\n }\n if (this.centre.y > maxY - this.height) {\n this.centre.y = maxY - this.height;\n }\n };\n\n Rectangle.prototype.edit = function(handleIndex, dx, dy, maxX, maxY) {\n this.width += dx;\n this.height += dy;\n if (this.width < -this.centre.x) {\n this.width = -this.centre.x;\n }\n if (this.width > maxX - this.centre.x) {\n this.width = maxX - this.centre.x;\n }\n if (this.height < -this.centre.y) {\n this.height = -this.centre.y;\n }\n if (this.height > maxY - this.centre.y) {\n this.height = maxY - this.centre.y;\n }\n };\n\n /**\n * Update the properties of this shape after a sequence of edits.\n *\n * For example make sure the circle radius is positive, of the polygon centre is centred.\n */\n Rectangle.prototype.normalizeShape = function() {\n if (this.width < 0) {\n this.centre.x += this.width;\n this.width = -this.width;\n }\n if (this.height < 0) {\n this.centre.y += this.height;\n this.height = -this.height;\n }\n };\n\n Rectangle.prototype.makeSimilarCircle = function() {\n return new Circle(this.label,\n Math.round(this.centre.x + this.width / 2),\n Math.round(this.centre.y + this.height / 2),\n Math.round((this.width + this.height) / 4));\n };\n\n Rectangle.prototype.makeSimilarPolygon = function() {\n return new Polygon(this.label, [\n this.centre, this.centre.offset(0, this.height),\n this.centre.offset(this.width, this.height), this.centre.offset(this.width, 0)]);\n };\n\n Rectangle.prototype.getHandlePositions = function() {\n return {\n moveHandle: this.centre.offset(this.width / 2, this.height / 2),\n editHandles: [this.centre.offset(this.width, this.height)]\n };\n };\n\n\n /**\n * A shape that is a polygon.\n *\n * @param {String} label name of this area.\n * @param {Point[]} [points] position of the vertices relative to (centreX, centreY).\n * each object in the array should have two\n * @constructor\n */\n function Polygon(label, points) {\n Shape.call(this, label, 0, 0);\n this.points = points ? points.slice() : [new Point(10, 10), new Point(40, 10), new Point(10, 40)];\n this.normalizeShape();\n this.ratio = 1;\n }\n Polygon.prototype = new Shape();\n\n Polygon.prototype.getType = function() {\n return 'polygon';\n };\n\n Polygon.prototype.getCoordinates = function() {\n var coordinates = '';\n for (var i = 0; i < this.points.length; i++) {\n coordinates += this.centre.offset(this.points[i]) + ';';\n }\n return coordinates.slice(0, coordinates.length - 1); // Strip off the last ';'.\n };\n\n Polygon.prototype.makeSvg = function(svg) {\n var svgEl = createSvgShapeGroup(svg, 'polygon');\n this.updateSvg(svgEl);\n return svgEl;\n };\n\n Polygon.prototype.updateSvg = function(svgEl) {\n svgEl.childNodes[0].setAttribute('points', this.getCoordinates().replace(/[,;]/g, ' '));\n svgEl.childNodes[0].setAttribute('transform', 'scale(' + parseFloat(this.ratio) + ')');\n svgEl.childNodes[1].setAttribute('x', this.centre.x);\n svgEl.childNodes[1].setAttribute('y', this.centre.y + 15);\n svgEl.childNodes[1].textContent = this.label;\n };\n\n Polygon.prototype.parse = function(coordinates, ratio) {\n if (!coordinates.match(/^\\d+(\\.\\d+)?,\\d+(\\.\\d+)?(?:;\\d+(\\.\\d+)?,\\d+(\\.\\d+)?)*$/)) {\n return false;\n }\n\n var bits = coordinates.split(';');\n var points = [];\n for (var i = 0; i < bits.length; i++) {\n points.push(Point.parse(bits[i]));\n }\n\n this.points = points;\n this.centre.x = 0;\n this.centre.y = 0;\n this.ratio = ratio;\n this.normalizeShape();\n\n return true;\n };\n\n Polygon.prototype.move = function(dx, dy, maxX, maxY) {\n this.centre.move(dx, dy);\n var bbXMin = maxX,\n bbXMax = 0,\n bbYMin = maxY,\n bbYMax = 0;\n // Computer centre.\n for (var i = 0; i < this.points.length; i++) {\n bbXMin = Math.min(bbXMin, this.points[i].x);\n bbXMax = Math.max(bbXMax, this.points[i].x);\n bbYMin = Math.min(bbYMin, this.points[i].y);\n bbYMax = Math.max(bbYMax, this.points[i].y);\n }\n if (this.centre.x < -bbXMin) {\n this.centre.x = -bbXMin;\n }\n if (this.centre.x > maxX - bbXMax) {\n this.centre.x = maxX - bbXMax;\n }\n if (this.centre.y < -bbYMin) {\n this.centre.y = -bbYMin;\n }\n if (this.centre.y > maxY - bbYMax) {\n this.centre.y = maxY - bbYMax;\n }\n };\n\n Polygon.prototype.edit = function(handleIndex, dx, dy, maxX, maxY) {\n this.points[handleIndex].move(dx, dy);\n if (this.points[handleIndex].x < -this.centre.x) {\n this.points[handleIndex].x = -this.centre.x;\n }\n if (this.points[handleIndex].x > maxX - this.centre.x) {\n this.points[handleIndex].x = maxX - this.centre.x;\n }\n if (this.points[handleIndex].y < -this.centre.y) {\n this.points[handleIndex].y = -this.centre.y;\n }\n if (this.points[handleIndex].y > maxY - this.centre.y) {\n this.points[handleIndex].y = maxY - this.centre.y;\n }\n };\n\n /**\n * Add a new point after the given point, with the same co-ordinates.\n *\n * This does not automatically normalise.\n *\n * @param {int} pointIndex the index of the vertex after which to insert this new one.\n */\n Polygon.prototype.addNewPointAfter = function(pointIndex) {\n this.points.splice(pointIndex, 0,\n new Point(this.points[pointIndex].x, this.points[pointIndex].y));\n };\n\n Polygon.prototype.normalizeShape = function() {\n var i,\n x = 0,\n y = 0;\n\n if (this.points.length === 0) {\n return;\n }\n\n // Computer centre.\n for (i = 0; i < this.points.length; i++) {\n x += this.points[i].x;\n y += this.points[i].y;\n }\n x = Math.round(x / this.points.length);\n y = Math.round(y / this.points.length);\n\n if (x === 0 && y === 0) {\n return;\n }\n\n for (i = 0; i < this.points.length; i++) {\n this.points[i].move(-x, -y);\n }\n this.centre.move(x, y);\n };\n\n Polygon.prototype.makeSimilarCircle = function() {\n return this.makeSimilarRectangle().makeSimilarCircle();\n };\n\n Polygon.prototype.makeSimilarRectangle = function() {\n var p,\n minX = 0,\n maxX = 0,\n minY = 0,\n maxY = 0;\n for (var i = 0; i < this.points.length; i++) {\n p = this.points[i];\n minX = Math.min(minX, p.x);\n maxX = Math.max(maxX, p.x);\n minY = Math.min(minY, p.y);\n maxY = Math.max(maxY, p.y);\n }\n return new Rectangle(this.label,\n this.centre.x + minX, this.centre.y + minY,\n Math.max(maxX - minX, 10), Math.max(maxY - minY, 10));\n };\n\n Polygon.prototype.getHandlePositions = function() {\n var editHandles = [];\n for (var i = 0; i < this.points.length; i++) {\n editHandles.push(this.points[i].offset(this.centre.x, this.centre.y));\n }\n\n this.centre.x = this.centre.x * parseFloat(this.ratio);\n this.centre.y = this.centre.y * parseFloat(this.ratio);\n\n return {\n moveHandle: this.centre,\n editHandles: editHandles\n };\n };\n\n\n /**\n * Not a shape (null object pattern).\n *\n * @param {String} label name of this area.\n * @constructor\n */\n function NullShape(label) {\n Shape.call(this, label);\n }\n NullShape.prototype = new Shape();\n\n NullShape.prototype.getType = function() {\n return 'null';\n };\n\n NullShape.prototype.getCoordinates = function() {\n return '';\n };\n\n NullShape.prototype.makeSvg = function(svg) {\n void (svg);\n return null;\n };\n\n NullShape.prototype.updateSvg = function(svgEl) {\n void (svgEl);\n };\n\n NullShape.prototype.parse = function(coordinates) {\n void (coordinates);\n return false;\n };\n\n NullShape.prototype.makeSimilarCircle = function() {\n return new Circle(this.label);\n };\n\n NullShape.prototype.makeSimilarRectangle = function() {\n return new Rectangle(this.label);\n };\n\n NullShape.prototype.makeSimilarPolygon = function() {\n return new Polygon(this.label);\n };\n\n\n /**\n * Make a new SVG DOM element as a child of svg.\n *\n * @param {SVGElement} svg the parent node.\n * @param {String} tagName the tag name.\n * @return {SVGElement} the newly created node.\n */\n function createSvgElement(svg, tagName) {\n var svgEl = svg.ownerDocument.createElementNS('http://www.w3.org/2000/svg', tagName);\n svg.appendChild(svgEl);\n return svgEl;\n }\n\n /**\n * Make a group SVG DOM elements containing a shape of the given type as first child,\n * and a text label as the second child.\n *\n * @param {SVGElement} svg the parent node.\n * @param {String} tagName the tag name.\n * @return {SVGElement} the newly created g element.\n */\n function createSvgShapeGroup(svg, tagName) {\n var svgEl = createSvgElement(svg, 'g');\n createSvgElement(svgEl, tagName).setAttribute('class', 'shape');\n createSvgElement(svgEl, 'text').setAttribute('class', 'shapeLabel');\n return svgEl;\n }\n\n /**\n * @alias module:qtype_ddmarker/shapes\n */\n return {\n /**\n * A point, with x and y coordinates.\n *\n * @param {int} x centre X.\n * @param {int} y centre Y.\n * @constructor\n */\n Point: Point,\n\n /**\n * A point, with x and y coordinates.\n *\n * @param {int} x centre X.\n * @param {int} y centre Y.\n * @constructor\n */\n Shape: Shape,\n\n /**\n * A shape that is a circle.\n *\n * @param {String} label name of this area.\n * @param {int} [x] centre X.\n * @param {int} [y] centre Y.\n * @param {int} [radius] radius.\n * @constructor\n */\n Circle: Circle,\n\n /**\n * A shape that is a rectangle.\n *\n * @param {String} label name of this area.\n * @param {int} [x] top left X.\n * @param {int} [y] top left Y.\n * @param {int} [width] width.\n * @param {int} [height] height.\n * @constructor\n */\n Rectangle: Rectangle,\n\n /**\n * A shape that is a polygon.\n *\n * @param {String} label name of this area.\n * @param {Point[]} [points] position of the vertices relative to (centreX, centreY).\n * each object in the array should have two\n * @constructor\n */\n Polygon: Polygon,\n\n /**\n * Not a shape (null object pattern).\n *\n * @param {String} label name of this area.\n * @constructor\n */\n NullShape: NullShape,\n\n /**\n * Make a new SVG DOM element as a child of svg.\n *\n * @param {SVGElement} svg the parent node.\n * @param {String} tagName the tag name.\n * @return {SVGElement} the newly created node.\n */\n createSvgElement: createSvgElement,\n\n /**\n * Make a shape of the given type.\n *\n * @param {String} shapeType\n * @param {String} label\n * @return {Shape} the requested shape.\n */\n make: function(shapeType, label) {\n switch (shapeType) {\n case 'circle':\n return new Circle(label);\n case 'rectangle':\n return new Rectangle(label);\n case 'polygon':\n return new Polygon(label);\n default:\n return new NullShape(label);\n }\n },\n\n /**\n * Make a shape of the given type that is similar to the shape of the original type.\n *\n * @param {String} shapeType the new type of shape to make\n * @param {Shape} shape the shape to copy\n * @return {Shape} the similar shape of a different type.\n */\n getSimilar: function(shapeType, shape) {\n if (shapeType === shape.getType()) {\n return shape;\n }\n switch (shapeType) {\n case 'circle':\n return shape.makeSimilarCircle();\n case 'rectangle':\n return shape.makeSimilarRectangle();\n case 'polygon':\n return shape.makeSimilarPolygon();\n default:\n return new NullShape(shape.label);\n }\n }\n };\n});\n"],"file":"shapes.min.js"} \ No newline at end of file diff --git a/question/type/ddmarker/amd/src/form.js b/question/type/ddmarker/amd/src/form.js index c2951e84643..cb8b6178326 100644 --- a/question/type/ddmarker/amd/src/form.js +++ b/question/type/ddmarker/amd/src/form.js @@ -51,7 +51,8 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes'], function($, dragDro if (this.shape.getCoordinates() === coordinates) { return; } - if (!this.shape.parse(coordinates)) { + // We don't need to scale the shape for editing form. + if (!this.shape.parse(coordinates, 1)) { // Invalid coordinates. Don't update the preview. return; } @@ -70,6 +71,8 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes'], function($, dragDro // Simple update. this.updateSvgEl(); } + // Update the rounded coordinates if needed. + this.setCoordinatesInForm(); }; /** diff --git a/question/type/ddmarker/amd/src/question.js b/question/type/ddmarker/amd/src/question.js index ae812d8066a..7dfa8ffdb57 100644 --- a/question/type/ddmarker/amd/src/question.js +++ b/question/type/ddmarker/amd/src/question.js @@ -30,56 +30,44 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f * Object to handle one drag-drop markers question. * * @param {String} containerId id of the outer div for this question. - * @param {String} bgImgUrl the URL of the background image. * @param {boolean} readOnly whether the question is being displayed read-only. * @param {Object[]} visibleDropZones the geometry of any drop-zones to show. * Objects have fields shape, coords and markertext. * @constructor */ - function DragDropMarkersQuestion(containerId, bgImgUrl, readOnly, visibleDropZones) { + function DragDropMarkersQuestion(containerId, readOnly, visibleDropZones) { + var thisQ = this; this.containerId = containerId; this.visibleDropZones = visibleDropZones; + this.shapes = []; + this.shapeSVGs = []; + this.isPrinting = false; if (readOnly) { this.getRoot().addClass('qtype_ddmarker-readonly'); } - this.loadImage(bgImgUrl); + thisQ.cloneDrags(); + thisQ.repositionDrags(); + thisQ.drawDropzones(); } - /** - * Load the background image is loaded, then do the rest of the display. - * - * @param {String} bgImgUrl the URL of the background image. - */ - DragDropMarkersQuestion.prototype.loadImage = function(bgImgUrl) { - var thisQ = this; - this.getRoot().find('.dropbackground') - .one('load', function() { - if (thisQ.visibleDropZones.length > 0) { - thisQ.drawDropzones(); - } - thisQ.repositionDrags(); - }) - .attr('src', bgImgUrl) - .css({'border': '1px solid #000', 'max-width': 'none'}); - }; - /** * Draws the svg shapes of any drop zones that should be visible for feedback purposes. */ DragDropMarkersQuestion.prototype.drawDropzones = function() { - var bgImage = this.getRoot().find('img.dropbackground'); + if (this.visibleDropZones.length > 0) { + var bgImage = this.bgImage(); - this.getRoot().find('div.dropzones').html(''); - var svg = this.getRoot().find('svg.dropzones'); - svg.css('position', 'absolute'); + this.getRoot().find('div.dropzones').html(''); + var svg = this.getRoot().find('svg.dropzones'); - var nextColourIndex = 0; - for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) { - var colourClass = 'color' + nextColourIndex; - nextColourIndex = (nextColourIndex + 1) % 8; - this.addDropzone(svg, dropZoneNo, colourClass); + var nextColourIndex = 0; + for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) { + var colourClass = 'color' + nextColourIndex; + nextColourIndex = (nextColourIndex + 1) % 8; + this.addDropzone(svg, dropZoneNo, colourClass); + } } }; @@ -93,8 +81,9 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f DragDropMarkersQuestion.prototype.addDropzone = function(svg, dropZoneNo, colourClass) { var dropZone = this.visibleDropZones[dropZoneNo], shape = Shapes.make(dropZone.shape, ''), - existingmarkertext; - if (!shape.parse(dropZone.coords)) { + existingmarkertext, + bgRatio = this.bgRatio(); + if (!shape.parse(dropZone.coords, bgRatio)) { return; } @@ -109,40 +98,26 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f var classnames = 'markertext markertext' + dropZoneNo; this.getRoot().find('div.markertexts').append('' + dropZone.markertext + ''); + var markerspan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo); + if (markerspan.length) { + var handles = shape.getHandlePositions(); + var positionLeft = handles.moveHandle.x - (markerspan.outerWidth() / 2) - 4; + var positionTop = handles.moveHandle.y - (markerspan.outerHeight() / 2); + markerspan + .css('left', positionLeft) + .css('top', positionTop); + markerspan + .data('originX', markerspan.position().left / bgRatio) + .data('originY', markerspan.position().top / bgRatio); + this.handleElementScale(markerspan, 'center'); + } } var shapeSVG = shape.makeSvg(svg[0]); shapeSVG.setAttribute('class', 'dropzone ' + colourClass); - }; - /** - * Draws the drag items on the page (and drop zones if required). - * The idea is to re-draw all the drags and drops whenever there is a change - * like a widow resize or an item dropped in place. - */ - DragDropMarkersQuestion.prototype.repositionDropZones = function() { - var svg = this.getRoot().find('svg.dropzones'); - if (svg.length === 0) { - return; - } - var bgPosition = this.convertToWindowXY(new Shapes.Point(-1, 0)); - svg.offset({'left': bgPosition.x, 'top': bgPosition.y}); - - for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) { - var markerspan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo); - if (markerspan.length === 0) { - continue; - } - var dropZone = this.visibleDropZones[dropZoneNo], - shape = Shapes.make(dropZone.shape, ''); - if (!shape.parse(dropZone.coords)) { - continue; - } - var handles = shape.getHandlePositions(), - textPos = this.convertToWindowXY(handles.moveHandle.offset( - -markerspan.outerWidth() / 2, -markerspan.outerHeight() / 2)); - markerspan.offset({'left': textPos.x - 4, 'top': textPos.y}); - } + this.shapes[this.shapes.length] = shape; + this.shapeSVGs[this.shapeSVGs.length] = shapeSVG; }; /** @@ -154,37 +129,25 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f var root = this.getRoot(), thisQ = this; - root.find('div.dragitems .dragitem').each(function(key, item) { + root.find('div.draghomes .marker').not('.dragplaceholder').each(function(key, item) { $(item).addClass('unneeded'); }); root.find('input.choices').each(function(key, input) { var choiceNo = thisQ.getChoiceNoFromElement(input), - coords = thisQ.getCoords(input), - dragHome = thisQ.dragHome(choiceNo); - for (var i = 0; i < coords.length; i++) { - var drag = thisQ.dragItem(choiceNo, i); - if (!drag.length || drag.hasClass('beingdragged')) { - drag = thisQ.cloneNewDragItem(dragHome, i); - } else { - drag.removeClass('unneeded'); + coords = thisQ.getCoords(input); + if (coords.length) { + var drag = thisQ.getRoot().find('.draghomes' + ' span.marker' + '.choice' + choiceNo).not('.dragplaceholder'); + drag.remove(); + for (var i = 0; i < coords.length; i++) { + var dragInDrop = drag.clone(); + dragInDrop.data('pagex', coords[i].x).data('pagey', coords[i].y); + thisQ.sendDragToDrop(dragInDrop, false); } - drag.offset({'left': coords[i].x, 'top': coords[i].y}); + thisQ.getDragClone(drag).addClass('active'); + thisQ.cloneDragIfNeeded(drag); } }); - - root.find('div.dragitems .dragitem').each(function(key, itm) { - var item = $(itm); - if (item.hasClass('unneeded') && !item.hasClass('beingdragged')) { - item.remove(); - } - }); - - this.repositionDropZones(); - - var bgImage = this.bgImage(), - bgPosition = bgImage.offset(); - bgImage.data('prev-top', bgPosition.top).data('prev-left', bgPosition.left); }; /** @@ -197,11 +160,7 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f * @returns {Point[]} coordinates of however many copies of the drag item should be shown. */ DragDropMarkersQuestion.prototype.getCoords = function(inputNode) { - var root = this.getRoot(), - choiceNo = this.getChoiceNoFromElement(inputNode), - noOfDrags = Number(this.getClassnameNumericSuffix(inputNode, 'noofdrags')), - dragging = root.find('span.dragitem.beingdragged.choice' + choiceNo).length > 0, - coords = [], + var coords = [], val = $(inputNode).val(); if (val !== '') { var coordsStrings = val.split(';'); @@ -209,10 +168,6 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f coords[i] = this.convertToWindowXY(Shapes.Point.parse(coordsStrings[i])); } } - var displayeddrags = coords.length + (dragging ? 1 : 0); - if ($(inputNode).hasClass('infinite') || (displayeddrags < noOfDrags)) { - coords[coords.length] = this.dragHomeXY(choiceNo); - } return coords; }; @@ -252,19 +207,10 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f */ DragDropMarkersQuestion.prototype.coordsInBgImg = function(point) { var bgImage = this.bgImage(); - return point.x > 0 && point.x <= bgImage.width() && - point.y > 0 && point.y <= bgImage.height(); - }; + var bgPosition = bgImage.offset(); - /** - * Returns coordinates for the home position of a choice. - * - * @param {Number} choiceNo - * @returns {Point} coordinates - */ - DragDropMarkersQuestion.prototype.dragHomeXY = function(choiceNo) { - var dragItemHome = this.dragHome(choiceNo); - return new Shapes.Point(dragItemHome.offset().left, dragItemHome.offset().top); + return point.x >= bgPosition.left && point.x < bgPosition.left + bgImage.width() + && point.y >= bgPosition.top && point.y < bgPosition.top + bgImage.height(); }; /** @@ -283,52 +229,28 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f return this.getRoot().find('img.dropbackground'); }; - /** - * Return the DOM node for this choice's home position. - * @param {Number} choiceNo - * @returns {jQuery} containing the home. - */ - DragDropMarkersQuestion.prototype.dragHome = function(choiceNo) { - return this.getRoot().find('div.dragitems span.draghome.choice' + choiceNo); - }; - - /** - * Return the DOM node for a particular instance of a particular choice. - * @param {Number} choiceNo - * @param {Number} itemNo - * @returns {jQuery} containing the item. - */ - DragDropMarkersQuestion.prototype.dragItem = function(choiceNo, itemNo) { - return this.getRoot().find('div.dragitems span.dragitem.choice' + choiceNo + '.item' + itemNo); - }; - - /** - * Create a draggable copy of the drag item. - * - * @param {jQuery} dragHome to clone - * @param {Number} itemNo new item number - * @return {jQuery} drag - */ - DragDropMarkersQuestion.prototype.cloneNewDragItem = function(dragHome, itemNo) { - var drag = dragHome.clone(true); - drag.removeClass('draghome').addClass('dragitem').addClass('item' + itemNo); - dragHome.after(drag); - drag.attr('tabIndex', 0); - return drag; - }; - DragDropMarkersQuestion.prototype.handleDragStart = function(e) { var thisQ = this, - dragged = $(e.target).closest('.dragitem'); + dragged = $(e.target).closest('.marker'); var info = dragDrop.prepare(e); if (!info.start) { return; } - dragged.addClass('beingdragged'); + dragged.addClass('beingdragged').css('transform', ''); + + var placed = !dragged.hasClass('unneeded'); + if (!placed) { + var hiddenDrag = thisQ.getDragClone(dragged); + if (hiddenDrag.length) { + hiddenDrag.addClass('active'); + dragged.offset(hiddenDrag.offset()); + } + } + dragDrop.start(e, dragged, function() { - void (1); // Nothing to do, but we need a function. + void (1); }, function(x, y, dragged) { thisQ.dragEnd(dragged); }); @@ -339,52 +261,56 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f * @param {jQuery} dragged the marker that was dragged. */ DragDropMarkersQuestion.prototype.dragEnd = function(dragged) { - dragged.removeClass('beingdragged'); - var choiceNo = this.getChoiceNoFromElement(dragged); - this.saveCoordsForChoice(choiceNo, dragged); - this.repositionDrags(); + var placed = false, + choiceNo = this.getChoiceNoFromElement(dragged), + bgRatio = this.bgRatio(), + dragXY; + + dragged.data('pagex', dragged.offset().left).data('pagey', dragged.offset().top); + dragXY = new Shapes.Point(dragged.data('pagex'), dragged.data('pagey')); + if (this.coordsInBgImg(dragXY)) { + this.sendDragToDrop(dragged, true); + placed = true; + + // It seems that the dragdrop sometimes leaves the drag + // one pixel out of position. Put it in exactly the right place. + var bgImgXY = this.convertToBgImgXY(dragXY); + bgImgXY = new Shapes.Point(bgImgXY.x / bgRatio, bgImgXY.y / bgRatio); + dragged.data('originX', bgImgXY.x).data('originY', bgImgXY.y); + } + + if (!placed) { + this.sendDragHome(dragged); + this.removeDragIfNeeded(dragged); + } else { + this.cloneDragIfNeeded(dragged); + } + + this.saveCoordsForChoice(choiceNo); }; /** * Save the coordinates for a dropped item in the form field. * @param {Number} choiceNo which copy of the choice this was. - * @param {jQuery} dropped the choice that was dropped here. */ - DragDropMarkersQuestion.prototype.saveCoordsForChoice = function(choiceNo, dropped) { + DragDropMarkersQuestion.prototype.saveCoordsForChoice = function(choiceNo) { var coords = [], - numItems = this.getRoot().find('span.dragitem.choice' + choiceNo).length, - bgImgXY, - addme = true; + items = this.getRoot().find('div.droparea span.marker.choice' + choiceNo), + thiQ = this, + bgRatio = this.bgRatio(); - // Re-build the coords array based on data in the ddform inputs. - // While long winded and unnecessary if there is only one drop item - // for a choice, it does account for moving any one of several drop items - // within a choice that have already been placed. - for (var i = 0; i <= numItems; i++) { - var drag = this.dragItem(choiceNo, i); - if (drag.length === 0) { - continue; - } - - if (!drag.hasClass('beingdragged')) { - bgImgXY = this.convertToBgImgXY(new Shapes.Point(drag.offset().left, drag.offset().top)); - if (this.coordsInBgImg(bgImgXY)) { - coords[coords.length] = bgImgXY; + if (items.length) { + items.each(function() { + var drag = $(this); + if (!drag.hasClass('beingdragged')) { + var dragXY = new Shapes.Point(drag.data('pagex'), drag.data('pagey')); + if (thiQ.coordsInBgImg(dragXY)) { + var bgImgXY = thiQ.convertToBgImgXY(dragXY); + bgImgXY = new Shapes.Point(bgImgXY.x / bgRatio, bgImgXY.y / bgRatio); + coords[coords.length] = bgImgXY; + } } - } - - if (dropped && dropped.length !== 0 && (dropped[0].innerText === drag[0].innerText)) { - addme = false; - } - } - - // If dropped has been passed it is because a new item has been dropped onto the background image - // so add its coordinates to the array. - if (addme) { - bgImgXY = this.convertToBgImgXY(new Shapes.Point(dropped.offset().left, dropped.offset().top)); - if (this.coordsInBgImg(bgImgXY)) { - coords[coords.length] = bgImgXY; - } + }); } this.getRoot().find('input.choice' + choiceNo).val(coords.join(';')); @@ -395,7 +321,7 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f * @param {KeyboardEvent} e */ DragDropMarkersQuestion.prototype.handleKeyPress = function(e) { - var drag = $(e.target).closest('.dragitem'), + var drag = $(e.target).closest('.marker'), point = new Shapes.Point(drag.offset().left, drag.offset().top), choiceNo = this.getChoiceNoFromElement(drag); @@ -427,12 +353,28 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f if (point !== null) { point = this.constrainToBgImg(point); + drag.offset({'left': point.x, 'top': point.y}); + drag.data('pagex', drag.offset().left).data('pagey', drag.offset().top); + var dragXY = this.convertToBgImgXY(new Shapes.Point(drag.data('pagex'), drag.data('pagey'))); + drag.data('originX', dragXY.x / this.bgRatio()).data('originY', dragXY.y / this.bgRatio()); + if (this.coordsInBgImg(new Shapes.Point(drag.offset().left, drag.offset().top))) { + if (drag.hasClass('unneeded')) { + this.sendDragToDrop(drag, true); + var hiddenDrag = this.getDragClone(drag); + if (hiddenDrag.length) { + hiddenDrag.addClass('active'); + } + this.cloneDragIfNeeded(drag); + } + } } else { - point = this.dragHomeXY(choiceNo); + drag.css('left', '').css('top', ''); + drag.data('pagex', drag.offset().left).data('pagey', drag.offset().top); + this.sendDragHome(drag); + this.removeDragIfNeeded(drag); } - drag.offset({'left': point.x, 'top': point.y}); - this.saveCoordsForChoice(choiceNo, drag); - this.repositionDrags(); + drag.focus(); + this.saveCoordsForChoice(choiceNo); }; /** @@ -488,27 +430,204 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f * Handle when the window is resized. */ DragDropMarkersQuestion.prototype.handleResize = function() { - this.repositionDrags(); + var thisQ = this, + bgRatio = this.bgRatio(); + if (this.isPrinting) { + bgRatio = 1; + } + + this.getRoot().find('div.droparea .marker').not('.beingdragged').each(function(key, drag) { + $(drag) + .css('left', parseFloat($(drag).data('originX')) * parseFloat(bgRatio)) + .css('top', parseFloat($(drag).data('originY')) * parseFloat(bgRatio)); + thisQ.handleElementScale(drag, 'left top'); + }); + + this.getRoot().find('div.droparea svg.dropzones') + .width(this.bgImage().width()) + .height(this.bgImage().height()); + + for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) { + var dropZone = thisQ.visibleDropZones[dropZoneNo]; + var originCoords = dropZone.coords; + var shape = thisQ.shapes[dropZoneNo]; + var shapeSVG = thisQ.shapeSVGs[dropZoneNo]; + shape.parse(originCoords, bgRatio); + shape.updateSvg(shapeSVG); + + var handles = shape.getHandlePositions(); + var markerSpan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo); + markerSpan + .css('left', handles.moveHandle.x - (markerSpan.outerWidth() / 2) - 4) + .css('top', handles.moveHandle.y - (markerSpan.outerHeight() / 2)); + thisQ.handleElementScale(markerSpan, 'center'); + } }; /** - * Check to see if the background image has moved. If so, refresh the layout. + * Clone the drag. */ - DragDropMarkersQuestion.prototype.fixLayoutIfBackgroundMoved = function() { - var bgImage = this.bgImage(), - bgPosition = bgImage.offset(), - prevTop = bgImage.data('prev-top'), - prevLeft = bgImage.data('prev-left'); - if (prevLeft === undefined || prevTop === undefined) { - // Question is not set up yet. Nothing to do. - return; + DragDropMarkersQuestion.prototype.cloneDrags = function() { + var thisQ = this; + this.getRoot().find('div.draghomes span.marker').each(function(index, draghome) { + var drag = $(draghome); + var placeHolder = drag.clone(); + placeHolder.removeClass(); + placeHolder.addClass('marker choice' + + thisQ.getChoiceNoFromElement(drag) + ' dragno' + thisQ.getDragNo(drag) + ' dragplaceholder'); + drag.before(placeHolder); + }); + }; + + /** + * Get the drag number of a drag. + * + * @param {jQuery} drag the drag. + * @returns {Number} the drag number. + */ + DragDropMarkersQuestion.prototype.getDragNo = function(drag) { + return this.getClassnameNumericSuffix(drag, 'dragno'); + }; + + /** + * Get drag clone for a given drag. + * + * @param {jQuery} drag the drag. + * @returns {jQuery} the drag's clone. + */ + DragDropMarkersQuestion.prototype.getDragClone = function(drag) { + return this.getRoot().find('.draghomes' + ' span.marker' + + '.choice' + this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag) + '.dragplaceholder'); + }; + + /** + * Get the drop area element. + * @returns {jQuery} droparea element. + */ + DragDropMarkersQuestion.prototype.dropArea = function() { + return this.getRoot().find('div.droparea'); + }; + + /** + * Animate a drag back to its home. + * + * @param {jQuery} drag the item being moved. + */ + DragDropMarkersQuestion.prototype.sendDragHome = function(drag) { + drag.removeClass('beingdragged') + .addClass('unneeded') + .css('top', '') + .css('left', '') + .css('transform', ''); + var placeHolder = this.getDragClone(drag); + placeHolder.after(drag); + placeHolder.removeClass('active'); + }; + + /** + * Animate a drag item into a given place. + * + * @param {jQuery} drag the item to place. + * @param {boolean} isScaling Scaling or not + */ + DragDropMarkersQuestion.prototype.sendDragToDrop = function(drag, isScaling) { + var dropArea = this.dropArea(), + bgRatio = this.bgRatio(); + drag.removeClass('beingdragged').removeClass('unneeded'); + var dragXY = this.convertToBgImgXY(new Shapes.Point(drag.data('pagex'), drag.data('pagey'))); + if (isScaling) { + drag.data('originX', dragXY.x / bgRatio).data('originY', dragXY.y / bgRatio); + drag.css('left', dragXY.x).css('top', dragXY.y); + } else { + drag.data('originX', dragXY.x).data('originY', dragXY.y); + drag.css('left', dragXY.x * bgRatio).css('top', dragXY.y * bgRatio); } - if (prevTop === bgPosition.top && prevLeft === bgPosition.left) { - // Things have not moved. - return; + dropArea.append(drag); + this.handleElementScale(drag, 'left top'); + }; + + /** + * Clone the drag at the draghome area if needed. + * + * @param {jQuery} drag the item to place. + */ + DragDropMarkersQuestion.prototype.cloneDragIfNeeded = function(drag) { + var inputNode = this.getInput(drag), + noOfDrags = Number(this.getClassnameNumericSuffix(inputNode, 'noofdrags')), + displayedDragsInDropArea = this.getRoot().find('div.droparea .marker.choice' + + this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).length, + displayedDragsInDragHomes = this.getRoot().find('div.draghomes .marker.choice' + + this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').length; + + if (displayedDragsInDropArea < noOfDrags && displayedDragsInDragHomes === 0) { + var dragclone = drag.clone(); + dragclone.addClass('unneeded') + .css('top', '') + .css('left', '') + .css('transform', ''); + this.getDragClone(drag) + .removeClass('active') + .after(dragclone); } - // We need to reposition things. - this.repositionDrags(); + }; + + /** + * Remove the clone drag at the draghome area if needed. + * + * @param {jQuery} drag the item to place. + */ + DragDropMarkersQuestion.prototype.removeDragIfNeeded = function(drag) { + var displayeddrags = this.getRoot().find('div.draghomes .marker.choice' + + this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').length; + if (displayeddrags > 1) { + this.getRoot().find('div.draghomes .marker.choice' + + this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').first().remove(); + } + }; + + /** + * Get the input belong to drag. + * + * @param {jQuery} drag the item to place. + * @returns {jQuery} input element. + */ + DragDropMarkersQuestion.prototype.getInput = function(drag) { + var choiceNo = this.getChoiceNoFromElement(drag); + return this.getRoot().find('input.choices.choice' + choiceNo); + }; + + /** + * Return the background ratio. + * + * @returns {number} Background ratio. + */ + DragDropMarkersQuestion.prototype.bgRatio = function() { + var bgImg = this.bgImage(); + var bgImgNaturalWidth = bgImg.get(0).naturalWidth; + var bgImgClientWidth = bgImg.width(); + + return bgImgClientWidth / bgImgNaturalWidth; + }; + + /** + * Scale the drag if needed. + * + * @param {jQuery} element the item to place. + * @param {String} type scaling type + */ + DragDropMarkersQuestion.prototype.handleElementScale = function(element, type) { + var bgRatio = parseFloat(this.bgRatio()); + if (this.isPrinting) { + bgRatio = 1; + } + $(element).css({ + '-webkit-transform': 'scale(' + bgRatio + ')', + '-moz-transform': 'scale(' + bgRatio + ')', + '-ms-transform': 'scale(' + bgRatio + ')', + '-o-transform': 'scale(' + bgRatio + ')', + 'transform': 'scale(' + bgRatio + ')', + 'transform-origin': type + }); }; /** @@ -524,6 +643,16 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f */ eventHandlersInitialised: false, + /** + * {boolean} is printing or not. + */ + isPrinting: false, + + /** + * {boolean} is keyboard navigation. + */ + isKeyboardNavigation: false, + /** * {Object} all the questions on this page, indexed by containerId (id on the .que div). */ @@ -533,13 +662,12 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f * Initialise one question. * * @param {String} containerId the id of the div.que that contains this question. - * @param {String} bgImgUrl URL fo the background image. * @param {boolean} readOnly whether the question is read-only. * @param {Object[]} visibleDropZones data on any drop zones to draw as part of the feedback. */ - init: function(containerId, bgImgUrl, readOnly, visibleDropZones) { + init: function(containerId, readOnly, visibleDropZones) { questionManager.questions[containerId] = - new DragDropMarkersQuestion(containerId, bgImgUrl, readOnly, visibleDropZones); + new DragDropMarkersQuestion(containerId, readOnly, visibleDropZones); if (!questionManager.eventHandlersInitialised) { questionManager.setupEventHandlers(); questionManager.eventHandlersInitialised = true; @@ -550,14 +678,45 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f * Set up the event handlers that make this question type work. (Done once per page.) */ setupEventHandlers: function() { - $('body').on('mousedown touchstart', - '.que.ddmarker:not(.qtype_ddmarker-readonly) div.dragitems .dragitem', - questionManager.handleDragStart) + $('body') + .on('mousedown touchstart', + '.que.ddmarker:not(.qtype_ddmarker-readonly) div.draghomes .marker', questionManager.handleDragStart) + .on('mousedown touchstart', + '.que.ddmarker:not(.qtype_ddmarker-readonly) div.droparea .marker', questionManager.handleDragStart) .on('keydown keypress', - '.que.ddmarker:not(.qtype_ddmarker-readonly) div.dragitems .dragitem', - questionManager.handleKeyPress); - $(window).on('resize', questionManager.handleWindowResize); - setTimeout(questionManager.fixLayoutIfThingsMoved, 100); + '.que.ddmarker:not(.qtype_ddmarker-readonly) div.draghomes .marker', questionManager.handleKeyPress) + .on('keydown keypress', + '.que.ddmarker:not(.qtype_ddmarker-readonly) div.droparea .marker', questionManager.handleKeyPress) + .on('focusin', + '.que.ddmarker:not(.qtype_ddmarker-readonly) div.draghomes .marker', function(e) { + questionManager.handleKeyboardFocus(e, true); + }) + .on('focusin', + '.que.ddmarker:not(.qtype_ddmarker-readonly) div.droparea .marker', function(e) { + questionManager.handleKeyboardFocus(e, true); + }) + .on('focusout', + '.que.ddmarker:not(.qtype_ddmarker-readonly) div.draghomes .marker', function(e) { + questionManager.handleKeyboardFocus(e, false); + }) + .on('focusout', + '.que.ddmarker:not(.qtype_ddmarker-readonly) div.droparea .marker', function(e) { + questionManager.handleKeyboardFocus(e, false); + }); + $(window).on('resize', function() { + questionManager.handleWindowResize(false); + }); + window.addEventListener('beforeprint', function() { + questionManager.isPrinting = true; + questionManager.handleWindowResize(questionManager.isPrinting); + }); + window.addEventListener('afterprint', function() { + questionManager.isPrinting = false; + questionManager.handleWindowResize(questionManager.isPrinting); + }); + setTimeout(function() { + questionManager.fixLayoutIfThingsMoved(); + }, 100); }, /** @@ -585,31 +744,41 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f /** * Handle when the window is resized. + * @param {boolean} isPrinting */ - handleWindowResize: function() { + handleWindowResize: function(isPrinting) { for (var containerId in questionManager.questions) { if (questionManager.questions.hasOwnProperty(containerId)) { + questionManager.questions[containerId].isPrinting = isPrinting; questionManager.questions[containerId].handleResize(); } } }, + /** + * Handle focus lost events on markers. + * @param {Event} e + * @param {boolean} isNavigating + */ + handleKeyboardFocus: function(e, isNavigating) { + questionManager.isKeyboardNavigation = isNavigating; + }, + /** * Sometimes, despite our best efforts, things change in a way that cannot * be specifically caught (e.g. dock expanding or collapsing in Boost). * Therefore, we need to periodically check everything is in the right position. */ fixLayoutIfThingsMoved: function() { - for (var containerId in questionManager.questions) { - if (questionManager.questions.hasOwnProperty(containerId)) { - questionManager.questions[containerId].fixLayoutIfBackgroundMoved(); - } + if (!questionManager.isKeyboardNavigation) { + this.handleWindowResize(questionManager.isPrinting); } - // We use setTimeout after finishing work, rather than setInterval, // in case positioning things is slow. We want 100 ms gap // between executions, not what setInterval does. - setTimeout(questionManager.fixLayoutIfThingsMoved, 100); + setTimeout(function() { + questionManager.fixLayoutIfThingsMoved(questionManager.isPrinting); + }, 100); }, /** diff --git a/question/type/ddmarker/amd/src/shapes.js b/question/type/ddmarker/amd/src/shapes.js index dec8c0945e9..5fd765eb6e3 100644 --- a/question/type/ddmarker/amd/src/shapes.js +++ b/question/type/ddmarker/amd/src/shapes.js @@ -126,10 +126,11 @@ define(function() { * Update the shape from the string representation. * * @param {String} coordinates in the form returned by getCoordinates. + * @param {number} ratio Ratio to scale. * @return {boolean} true if the string could be parsed and the shape updated, else false. */ - Shape.prototype.parse = function(coordinates) { - void (coordinates); + Shape.prototype.parse = function(coordinates, ratio) { + void (coordinates, ratio); throw new Error('Not implemented.'); }; @@ -264,14 +265,16 @@ define(function() { svgEl.childNodes[1].textContent = this.label; }; - Circle.prototype.parse = function(coordinates) { - if (!coordinates.match(/^\d+,\d+;\d+$/)) { + Circle.prototype.parse = function(coordinates, ratio) { + if (!coordinates.match(/^\d+(\.\d+)?,\d+(\.\d+)?;\d+(\.\d+)?$/)) { return false; } var bits = coordinates.split(';'); this.centre = Point.parse(bits[0]); - this.radius = Math.round(bits[1]); + this.centre.x = this.centre.x * parseFloat(ratio); + this.centre.y = this.centre.y * parseFloat(ratio); + this.radius = Math.round(bits[1]) * parseFloat(ratio); return true; }; @@ -384,16 +387,18 @@ define(function() { svgEl.childNodes[1].textContent = this.label; }; - Rectangle.prototype.parse = function(coordinates) { - if (!coordinates.match(/^\d+,\d+;\d+,\d+$/)) { + Rectangle.prototype.parse = function(coordinates, ratio) { + if (!coordinates.match(/^\d+(\.\d+)?,\d+(\.\d+)?;\d+(\.\d+)?,\d+(\.\d+)?$/)) { return false; } var bits = coordinates.split(';'); this.centre = Point.parse(bits[0]); + this.centre.x = this.centre.x * parseFloat(ratio); + this.centre.y = this.centre.y * parseFloat(ratio); var size = Point.parse(bits[1]); - this.width = size.x; - this.height = size.y; + this.width = size.x * parseFloat(ratio); + this.height = size.y * parseFloat(ratio); return true; }; @@ -479,6 +484,7 @@ define(function() { Shape.call(this, label, 0, 0); this.points = points ? points.slice() : [new Point(10, 10), new Point(40, 10), new Point(10, 40)]; this.normalizeShape(); + this.ratio = 1; } Polygon.prototype = new Shape(); @@ -502,13 +508,14 @@ define(function() { Polygon.prototype.updateSvg = function(svgEl) { svgEl.childNodes[0].setAttribute('points', this.getCoordinates().replace(/[,;]/g, ' ')); + svgEl.childNodes[0].setAttribute('transform', 'scale(' + parseFloat(this.ratio) + ')'); svgEl.childNodes[1].setAttribute('x', this.centre.x); svgEl.childNodes[1].setAttribute('y', this.centre.y + 15); svgEl.childNodes[1].textContent = this.label; }; - Polygon.prototype.parse = function(coordinates) { - if (!coordinates.match(/^\d+,\d+(?:;\d+,\d+)*$/)) { + Polygon.prototype.parse = function(coordinates, ratio) { + if (!coordinates.match(/^\d+(\.\d+)?,\d+(\.\d+)?(?:;\d+(\.\d+)?,\d+(\.\d+)?)*$/)) { return false; } @@ -521,6 +528,7 @@ define(function() { this.points = points; this.centre.x = 0; this.centre.y = 0; + this.ratio = ratio; this.normalizeShape(); return true; @@ -636,6 +644,9 @@ define(function() { editHandles.push(this.points[i].offset(this.centre.x, this.centre.y)); } + this.centre.x = this.centre.x * parseFloat(this.ratio); + this.centre.y = this.centre.y * parseFloat(this.ratio); + return { moveHandle: this.centre, editHandles: editHandles diff --git a/question/type/ddmarker/renderer.php b/question/type/ddmarker/renderer.php index 3bf692d7368..180faa59c68 100644 --- a/question/type/ddmarker/renderer.php +++ b/question/type/ddmarker/renderer.php @@ -42,88 +42,86 @@ class qtype_ddmarker_renderer extends qtype_ddtoimage_renderer_base { $question = $qa->get_question(); $response = $qa->get_last_qt_data(); + $componentname = $question->qtype->plugin_name(); $questiontext = $question->format_questiontext($qa); - $output = html_writer::tag('div', $questiontext, array('class' => 'qtext')); + $dropareaclass = 'droparea'; + $draghomesclass = 'draghomes'; + if ($options->readonly) { + $dropareaclass .= ' readonly'; + $draghomesclass .= ' readonly'; + } - $bgimage = self::get_url_for_image($qa, 'bgimage'); + $output = html_writer::div($questiontext, 'qtext'); - $img = html_writer::empty_tag('img', array( - 'class' => 'dropbackground', - 'alt' => get_string('dropbackground', 'qtype_ddmarker'))); + $output .= html_writer::start_div('ddarea'); + $output .= html_writer::start_div($dropareaclass); + $output .= html_writer::img(self::get_url_for_image($qa, 'bgimage'), get_string('dropbackground', 'qtype_ddmarker'), + ['class' => 'dropbackground img-responsive img-fluid']); - $droparea = html_writer::tag('div', $img, array('class' => 'droparea')); + $output .= html_writer::div('', 'dropzones'); + $output .= html_writer::div('', 'markertexts'); + + $output .= html_writer::end_div(); + $output .= html_writer::start_div($draghomesclass); - $draghomes = ''; $orderedgroup = $question->get_ordered_choices(1); - $componentname = $question->qtype->plugin_name(); $hiddenfields = ''; foreach ($orderedgroup as $choiceno => $drag) { - $classes = array('draghome', - "choice{$choiceno}"); + $classes = ['marker', 'choice' . $choiceno]; + $attr = []; if ($drag->infinite) { $classes[] = 'infinite'; } else { - $classes[] = 'dragno'.$drag->noofdrags; + $classes[] = 'dragno' . $drag->noofdrags; } - $targeticonhtml = - $this->output->image_icon('crosshairs', '', $componentname, array('class' => 'target')); - - $markertextattrs = array('class' => 'markertext'); - $markertext = html_writer::tag('span', $drag->text, $markertextattrs); - $draghomesattrs = array('class' => join(' ', $classes)); - $draghomes .= html_writer::tag('span', $targeticonhtml . $markertext, $draghomesattrs); + if (!$options->readonly) { + $attr['tabindex'] = 0; + } + $dragoutput = html_writer::start_span(join(' ', $classes), $attr); + $targeticonhtml = $this->output->image_icon('crosshairs', '', $componentname, ['class' => 'target']); + $markertext = html_writer::span($drag->text, 'markertext'); + $dragoutput .= $targeticonhtml . $markertext; + $dragoutput .= html_writer::end_span(); + $output .= $dragoutput; $hiddenfields .= $this->hidden_field_choice($qa, $choiceno, $drag->infinite, $drag->noofdrags); } - $dragitemsclass = 'dragitems'; - if ($options->readonly) { - $dragitemsclass .= ' readonly'; - } - - $dragitems = html_writer::tag('div', $draghomes, array('class' => $dragitemsclass)); - $dropzones = html_writer::tag('div', '', array('class' => 'dropzones')); - $texts = html_writer::tag('div', '', array('class' => 'markertexts')); - $output .= html_writer::tag('div', - $droparea.$dragitems.$dropzones . $texts, - array('class' => 'ddarea')); + $output .= html_writer::end_div(); + $output .= html_writer::end_div(); if ($question->showmisplaced && $qa->get_state()->is_finished()) { $visibledropzones = $question->get_drop_zones_without_hit($response); } else { - $visibledropzones = array(); + $visibledropzones = []; } - $this->page->requires->js_call_amd('qtype_ddmarker/question', 'init', - [$qa->get_outer_question_div_unique_id(), $bgimage, $options->readonly, $visibledropzones]); - if ($qa->get_state() == question_state::$invalid) { - $output .= html_writer::nonempty_tag('div', - $question->get_validation_error($qa->get_last_qt_data()), - array('class' => 'validationerror')); + $output .= html_writer::div($question->get_validation_error($qa->get_last_qt_data()), 'validationerror'); } if ($question->showmisplaced && $qa->get_state()->is_finished()) { $wrongparts = $question->get_drop_zones_without_hit($response); if (count($wrongparts) !== 0) { - $wrongpartsstringspans = array(); + $wrongpartsstringspans = []; foreach ($wrongparts as $wrongpart) { - $wrongpartsstringspans[] = html_writer::nonempty_tag('span', - $wrongpart->markertext, array('class' => 'wrongpart')); + $wrongpartsstringspans[] = html_writer::span($wrongpart->markertext, 'wrongpart'); } $wrongpartsstring = join(', ', $wrongpartsstringspans); - $output .= html_writer::nonempty_tag('span', - get_string('followingarewrongandhighlighted', - 'qtype_ddmarker', - $wrongpartsstring), - array('class' => 'wrongparts')); + $output .= html_writer::span(get_string('followingarewrongandhighlighted', 'qtype_ddmarker', $wrongpartsstring), + 'wrongparts'); } } - $output .= html_writer::tag('div', $hiddenfields, array('class' => 'ddform')); + $output .= html_writer::div($hiddenfields, 'ddform'); + + $this->page->requires->js_call_amd('qtype_ddmarker/question', 'init', + [$qa->get_outer_question_div_unique_id(), $options->readonly, $visibledropzones]); + return $output; } + protected function hidden_field_choice(question_attempt $qa, $choiceno, $infinite, $noofdrags, $value = null) { $varname = 'c'.$choiceno; $classes = array('choices', 'choice'.$choiceno, 'noofdrags'.$noofdrags); diff --git a/question/type/ddmarker/styles.css b/question/type/ddmarker/styles.css index b826e1d26f1..65bcb07e351 100644 --- a/question/type/ddmarker/styles.css +++ b/question/type/ddmarker/styles.css @@ -3,29 +3,46 @@ display: block; } -.que.ddmarker .draghome img, -.que.ddmarker .draghome span { - visibility: hidden; +.que.ddmarker .droparea { + display: inline-block; + position: relative; } -.que.ddmarker .dragitems .dragitem { - cursor: move; +.que.ddmarker .droparea .dropzones, +.que.ddmarker .droparea .markertexts { position: absolute; - z-index: 2; + top: 0; + left: 0; } -.que.ddmarker .dragitems .draghome { +.que.ddmarker .draghomes .marker, +.que.ddmarker .droparea .marker { + vertical-align: top; + cursor: move; +} + +.que.ddmarker .draghomes.readonly .marker, +.que.ddmarker .droparea.readonly .marker { + cursor: auto; +} + +.que.ddmarker .droparea .marker { + position: absolute; +} + +.que.ddmarker .draghomes .marker { + position: relative; display: inline-block; margin: 10px; - vertical-align: top; } -.que.ddmarker .dragitems { - margin-top: 10px; +.que.ddmarker .draghomes .marker.dragplaceholder { + display: none; } -.que.ddmarker .dragitems.readonly .dragitem { - cursor: auto; +.que.ddmarker .draghomes .marker.dragplaceholder.active { + visibility: hidden; + display: inline-block; } .que.ddmarker div.ddarea, @@ -41,9 +58,16 @@ form.mform fieldset#id_previewareaheader div.ddarea .markertexts { form.mform fieldset#id_previewareaheader .dropbackground { margin: 0 auto; border: 1px solid black; +} + +form.mform fieldset#id_previewareaheader .dropbackground { max-width: none; } +.que.ddmarker .dropbackground.img-responsive.img-fluid { + width: 100%; +} + .que.ddmarker div.dragitems div.draghome, .que.ddmarker div.dragitems div.dragitem, form.mform fieldset#id_previewareaheader div.draghome, @@ -51,7 +75,8 @@ form.mform fieldset#id_previewareaheader div.drag { font: 13px/1.231 arial, helvetica, clean, sans-serif; } -.que.ddmarker div.dragitems span.markertext, +.que.ddmarker .droparea .marker span.markertext, +.que.ddmarker .draghomes .marker span.markertext, .que.ddmarker div.markertexts span.markertext, form.mform fieldset#id_previewareaheader div.markertexts span.markertext { margin: 0 5px; @@ -66,11 +91,17 @@ form.mform fieldset#id_previewareaheader div.markertexts span.markertext { opacity: 0.6; } +.que.ddmarker .droparea .marker span.markertext, +.que.ddmarker .draghomes .marker span.markertext { + white-space: nowrap; +} + .que.ddmarker div.markertexts span.markertext { z-index: 2; background-color: yellow; border: 2px solid khaki; position: absolute; + white-space: nowrap; } .que.ddmarker span.wrongpart { @@ -84,7 +115,8 @@ form.mform fieldset#id_previewareaheader div.markertexts span.markertext { display: inline-block; } -.que.ddmarker div.dragitems img.target { +.que.ddmarker .droparea .marker img.target, +.que.ddmarker .draghomes .marker img.target { position: absolute; left: -7px; /* This must be half the size of the target image, minus 0.5. */ top: -7px; /* In other words, this works for a 15x15 cross-hair. */ @@ -94,7 +126,11 @@ form.mform fieldset#id_previewareaheader div.markertexts span.markertext { display: none; } -.que.ddmarker .dragitem.beingdragged span.markertext { +.que.ddmarker .marker.beingdragged { + position: absolute; +} + +.que.ddmarker .marker.beingdragged span.markertext { z-index: 3; box-shadow: 3px 3px 4px #000; } @@ -196,3 +232,7 @@ body#page-question-type-ddmarker div[id^=fitem_id_][id*=hintclearwrong_] { body#page-question-type-ddmarker #fitem_id_penalty { margin-bottom: 2em; } + +body#page-question-type-ddmarker .ddarea.que.ddmarker { + overflow-y: scroll; +} diff --git a/question/type/ddmarker/tests/behat/behat_qtype_ddmarker.php b/question/type/ddmarker/tests/behat/behat_qtype_ddmarker.php index 364f48bdbad..04cabba1853 100644 --- a/question/type/ddmarker/tests/behat/behat_qtype_ddmarker.php +++ b/question/type/ddmarker/tests/behat/behat_qtype_ddmarker.php @@ -37,22 +37,20 @@ class behat_qtype_ddmarker extends behat_base { /** * Get the xpath for a given drag item. - * @param string $dragitem the text of the item to drag. + * + * @param string $marker the text of the item to drag. + * @param bool $iskeyboard is using keyboard or not. * @return string the xpath expression. */ - protected function marker_xpath($marker, $item = 0) { - return '//span[contains(@class, " dragitem ") and contains(@class, " item' . $item . - '") and span[@class = "markertext" and contains(normalize-space(.), "' . - $this->escape($marker) . '")]]'; - } - - protected function parse_marker_name($marker) { - $item = 0; - if (preg_match('~,(\d+)$~', $marker, $matches)) { - $item = $matches[1]; - $marker = substr($marker, 0, -1 - strlen($item)); + protected function marker_xpath($marker, $iskeyboard = false) { + if ($iskeyboard) { + return '//span[contains(@class, "marker") and not(contains(@class, "dragplaceholder")) ' . + 'and span[@class = "markertext" and contains(normalize-space(.), "' . + $this->escape($marker) . '")]]'; } - return array($marker, $item); + return '//span[contains(@class, "marker") and contains(@class, "unneeded") ' . + 'and not(contains(@class, "dragplaceholder")) and span[@class = "markertext" and contains(normalize-space(.), "' . + $this->escape($marker) . '")]]'; } /** @@ -64,7 +62,6 @@ class behat_qtype_ddmarker extends behat_base { * @Given /^I drag "(?P[^"]*)" to "(?P\d+,\d+)" in the drag and drop markers question$/ */ public function i_drag_to_in_the_drag_and_drop_markers_question($marker, $coordinates) { - list($marker, $item) = $this->parse_marker_name($marker); list($x, $y) = explode(',', $coordinates); // This is a bit nasty, but Behat (indeed Selenium) will only drag on @@ -81,7 +78,6 @@ class behat_qtype_ddmarker extends behat_base { var target = document.createElement('div'); target.setAttribute('id', 'target-{$x}-{$y}'); var container = document.querySelector('.droparea'); - container.style.setProperty('position', 'relative'); container.insertBefore(target, image); var xadjusted = {$x} + (container.offsetWidth - image.offsetWidth) / 2; var yadjusted = {$y} + (container.offsetHeight - image.offsetHeight) / 2; @@ -93,7 +89,7 @@ class behat_qtype_ddmarker extends behat_base { }())"); $generalcontext = behat_context_helper::get('behat_general'); - $generalcontext->i_drag_and_i_drop_it_in($this->marker_xpath($marker, $item), + $generalcontext->i_drag_and_i_drop_it_in($this->marker_xpath($marker), 'xpath_element', "#target-{$x}-{$y}", 'css_element'); } @@ -113,8 +109,7 @@ class behat_qtype_ddmarker extends behat_base { 'left' => chr(37), 'right' => chr(39), ); - list($marker, $item) = $this->parse_marker_name($marker); - $node = $this->get_selected_node('xpath_element', $this->marker_xpath($marker, $item)); + $node = $this->get_selected_node('xpath_element', $this->marker_xpath($marker, true)); $this->ensure_node_is_visible($node); for ($i = 0; $i < $repeats; $i++) { $node->keyDown($keycodes[$direction]); diff --git a/question/type/ddmarker/tests/behat/preview.feature b/question/type/ddmarker/tests/behat/preview.feature index b95996eddab..560c688ecad 100644 --- a/question/type/ddmarker/tests/behat/preview.feature +++ b/question/type/ddmarker/tests/behat/preview.feature @@ -33,10 +33,10 @@ Feature: Preview a drag-drop marker question And I change window size to "large" And I wait "2" seconds # Odd, but the
s go to nothing, not a space. - And I drag "OU" to "342,230" in the drag and drop markers question - And I drag "Railway station" to "254,197" in the drag and drop markers question - And I drag "Railway station,1" to "326,319" in the drag and drop markers question - And I drag "Railway station,2" to "203,101" in the drag and drop markers question + And I drag "OU" to "345,230" in the drag and drop markers question + And I drag "Railway station" to "262,197" in the drag and drop markers question + And I drag "Railway station" to "334,319" in the drag and drop markers question + And I drag "Railway station" to "211,101" in the drag and drop markers question And I press "Submit and finish" Then the state of "Please place the markers on the map of Milton Keynes" question is shown as "Correct" And I should see "Mark 1.00 out of 1.00" diff --git a/question/type/ddmarker/tests/walkthrough_test.php b/question/type/ddmarker/tests/walkthrough_test.php index afaf4a6f4f0..527c71d9e63 100644 --- a/question/type/ddmarker/tests/walkthrough_test.php +++ b/question/type/ddmarker/tests/walkthrough_test.php @@ -46,7 +46,7 @@ class qtype_ddmarker_walkthrough_test extends qbehaviour_walkthrough_test_base { * @return question_contains_tag_with_attributes the expectation. */ protected function get_contains_draggable_marker_home_expectation($choice, $infinite) { - $class = 'draghome choice'.$choice; + $class = 'marker choice'.$choice; if ($infinite) { $class .= ' infinite'; }