From 6f956ce910510ded2cf94e9f171c82ba5f921eba Mon Sep 17 00:00:00 2001 From: danghieu1407 Date: Wed, 15 Jan 2025 14:48:21 +0700 Subject: [PATCH] MDL-60910 ddimageortext: Create option for using transparent pictures --- .../type/ddimageortext/amd/build/form.min.js | 2 +- .../ddimageortext/amd/build/form.min.js.map | 2 +- question/type/ddimageortext/amd/src/form.js | 20 +++++++- ...ackup_qtype_ddimageortext_plugin.class.php | 2 +- question/type/ddimageortext/db/install.xml | 1 + question/type/ddimageortext/db/upgrade.php | 50 +++++++++++++++++++ .../ddimageortext/edit_ddimageortext_form.php | 11 ++++ .../lang/en/qtype_ddimageortext.php | 5 ++ question/type/ddimageortext/question.php | 3 +- question/type/ddimageortext/questiontype.php | 7 +++ .../type/ddimageortext/questiontypebase.php | 3 ++ question/type/ddimageortext/rendererbase.php | 4 ++ question/type/ddimageortext/styles.css | 14 ++++++ .../ddimageortext/tests/behat/add.feature | 4 +- .../tests/behat/backup_and_restore.feature | 1 + question/type/ddimageortext/tests/helper.php | 1 + question/type/ddimageortext/version.php | 2 +- 17 files changed, 124 insertions(+), 8 deletions(-) create mode 100644 question/type/ddimageortext/db/upgrade.php diff --git a/question/type/ddimageortext/amd/build/form.min.js b/question/type/ddimageortext/amd/build/form.min.js index 0a26d498139..a10b82a68ac 100644 --- a/question/type/ddimageortext/amd/build/form.min.js +++ b/question/type/ddimageortext/amd/build/form.min.js @@ -5,6 +5,6 @@ * @copyright 2018 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("qtype_ddimageortext/form",["jquery","core/dragdrop"],(function($,dragDrop){var dragDropToImageForm={maxBgImageSize:null,maxDragImageSize:null,fp:null,init:function(){dragDropToImageForm.fp=dragDropToImageForm.filePickers(),dragDropToImageForm.updateVisibilityOfFilePickers(),dragDropToImageForm.setOptionsForDragItemSelectors(),dragDropToImageForm.setupEventHandlers(),dragDropToImageForm.waitForFilePickerToInitialise()},setupPreviewArea:function(){$("#id_previewareaheader").append('
')},waitForFilePickerToInitialise:function(){null!==dragDropToImageForm.fp.file("bgimage").href?(M.util.js_pending("dragDropToImageForm"),$('form.mform[data-qtype="ddimageortext"]').on("change",".filepickerhidden",(function(){M.util.js_pending("dragDropToImageForm"),dragDropToImageForm.loadPreviewImage()})),$("#id_droparea").length||dragDropToImageForm.setupPreviewArea(),dragDropToImageForm.loadPreviewImage()):setTimeout(dragDropToImageForm.waitForFilePickerToInitialise,1e3)},loadPreviewImage:function(){$("fieldset#id_previewareaheader .dropbackground").one("load",dragDropToImageForm.afterPreviewImageLoaded).attr("src",dragDropToImageForm.fp.file("bgimage").href)},afterPreviewImageLoaded:function(){dragDropToImageForm.createDropZones(),M.util.js_complete("dragDropToImageForm")},createDropZones:function(){var dropZoneHolder=$(".dropzones");if(dropZoneHolder.empty(),null!==dragDropToImageForm.fp.file("bgimage").href){for(var numDrops=dragDropToImageForm.form.getFormValue("nodropzone",[]),dropNo=0;dropNo')}else""!==label&&dropZoneHolder.append('
'+label+"
")}}dragDropToImageForm.waitForAllDropImagesToBeLoaded()}},waitForAllDropImagesToBeLoaded:function(){$(".dropzones img").not((function(i,imgNode){return dragDropToImageForm.imageIsLoaded(imgNode)})).length>0?setTimeout((function(){dragDropToImageForm.waitForAllDropImagesToBeLoaded()}),100):dragDropToImageForm.updateDropZones()},imageIsLoaded:function(imgElement){return imgElement.complete&&0!==imgElement.naturalHeight},updateDropZones:function(){if(null!==dragDropToImageForm.fp.file("bgimage").href){for(var dropBackgroundPosition=$("fieldset#id_previewareaheader .dropbackground").offset(),numDrops=dragDropToImageForm.form.getFormValue("nodropzone",[]),dropNo=0;dropNo'+dragItemOptions[value]+"");var optionnode=selector.find('option[value="'+value+'"]');parseInt(value)===parseInt(selectedvalue)?optionnode.attr("selected",!0):dragDropToImageForm.isItemUsed(parseInt(value))&&optionnode.attr("disabled",!0)}}},isItemUsed:function(value){return 0!==value&&(!dragDropToImageForm.form.getFormValue("drags",[value-1,"infinite"])&&0!==$("fieldset#id_dropzoneheader select").filter((function(i,selectNode){return parseInt($(selectNode).val())===value})).length)},dragStart:function(e){var drop=$(e.target).closest(".droppreview");dragDrop.prepare(e).start&&dragDrop.start(e,drop,(function(x,y,drop){dragDropToImageForm.dragMove(drop)}),(function(){dragDropToImageForm.dragEnd()}))},dragMove:function(drop){var backgroundImage=$("fieldset#id_previewareaheader .dropbackground"),backgroundPosition=backgroundImage.offset(),dropNo=drop.data("dropNo"),dropPosition=drop.offset(),left=Math.round(dropPosition.left-backgroundPosition.left),top=Math.round(dropPosition.top-backgroundPosition.top);left=Math.round(Math.max(0,Math.min(left,backgroundImage.outerWidth()-drop.outerWidth()))),top=Math.round(Math.max(0,Math.min(top,backgroundImage.outerHeight()-drop.outerHeight()))),dragDropToImageForm.form.setFormValue("drops",[dropNo,"xleft"],left),dragDropToImageForm.form.setFormValue("drops",[dropNo,"ytop"],top)},dragEnd:function(){dragDropToImageForm.updateDropZones()},form:{toNameWithIndex:function(name,indexes){for(var indexString=name,i=0;i
')},waitForFilePickerToInitialise:function(){null!==dragDropToImageForm.fp.file("bgimage").href?(M.util.js_pending("dragDropToImageForm"),$('form.mform[data-qtype="ddimageortext"]').on("change",".filepickerhidden",(function(){M.util.js_pending("dragDropToImageForm"),dragDropToImageForm.loadPreviewImage()})),$("#id_droparea").length||dragDropToImageForm.setupPreviewArea(),dragDropToImageForm.loadPreviewImage()):setTimeout(dragDropToImageForm.waitForFilePickerToInitialise,1e3)},loadPreviewImage:function(){$("fieldset#id_previewareaheader .dropbackground").one("load",dragDropToImageForm.afterPreviewImageLoaded).attr("src",dragDropToImageForm.fp.file("bgimage").href)},afterPreviewImageLoaded:function(){dragDropToImageForm.createDropZones(),M.util.js_complete("dragDropToImageForm")},createDropZones:function(){var dropZoneHolder=$(".dropzones");if(dropZoneHolder.empty(),null!==dragDropToImageForm.fp.file("bgimage").href){for(var numDrops=dragDropToImageForm.form.getFormValue("nodropzone",[]),dropzonevisibilitystyle="1"===dragDropToImageForm.form.getFormValue("dropzonevisibility",[])?"background: transparent;":"",dropNo=0;dropNo')}else""!==label&&dropZoneHolder.append('
'+label+"
")}}dragDropToImageForm.waitForAllDropImagesToBeLoaded()}},waitForAllDropImagesToBeLoaded:function(){$(".dropzones img").not((function(i,imgNode){return dragDropToImageForm.imageIsLoaded(imgNode)})).length>0?setTimeout((function(){dragDropToImageForm.waitForAllDropImagesToBeLoaded()}),100):dragDropToImageForm.updateDropZones()},imageIsLoaded:function(imgElement){return imgElement.complete&&0!==imgElement.naturalHeight},updateDropZones:function(){if(null!==dragDropToImageForm.fp.file("bgimage").href){for(var dropBackgroundPosition=$("fieldset#id_previewareaheader .dropbackground").offset(),numDrops=dragDropToImageForm.form.getFormValue("nodropzone",[]),dropNo=0;dropNo'+dragItemOptions[value]+"");var optionnode=selector.find('option[value="'+value+'"]');parseInt(value)===parseInt(selectedvalue)?optionnode.attr("selected",!0):dragDropToImageForm.isItemUsed(parseInt(value))&&optionnode.attr("disabled",!0)}}},isItemUsed:function(value){return 0!==value&&(!dragDropToImageForm.form.getFormValue("drags",[value-1,"infinite"])&&0!==$('fieldset#id_dropzoneheader select[name^="drops"]').filter((function(i,selectNode){return parseInt($(selectNode).val())===value})).length)},dragStart:function(e){var drop=$(e.target).closest(".droppreview");dragDrop.prepare(e).start&&dragDrop.start(e,drop,(function(x,y,drop){dragDropToImageForm.dragMove(drop)}),(function(){dragDropToImageForm.dragEnd()}))},dragMove:function(drop){var backgroundImage=$("fieldset#id_previewareaheader .dropbackground"),backgroundPosition=backgroundImage.offset(),dropNo=drop.data("dropNo"),dropPosition=drop.offset(),left=Math.round(dropPosition.left-backgroundPosition.left),top=Math.round(dropPosition.top-backgroundPosition.top);left=Math.round(Math.max(0,Math.min(left,backgroundImage.outerWidth()-drop.outerWidth()))),top=Math.round(Math.max(0,Math.min(top,backgroundImage.outerHeight()-drop.outerHeight()))),dragDropToImageForm.form.setFormValue("drops",[dropNo,"xleft"],left),dragDropToImageForm.form.setFormValue("drops",[dropNo,"ytop"],top)},dragEnd:function(){dragDropToImageForm.updateDropZones()},form:{toNameWithIndex:function(name,indexes){for(var indexString=name,i=0;i.\n\n/*\n * JavaScript to allow dragging options to slots (using mouse down or touch) or tab through slots using keyboard.\n *\n * @module qtype_ddimageortext/form\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/dragdrop'], function($, dragDrop) {\n\n \"use strict\";\n\n /**\n * Singleton object to handle progressive enhancement of the\n * drag-drop onto image question editing form.\n * @type {Object}\n */\n var dragDropToImageForm = {\n /**\n * @var {Object} maxBgImageSize Properties width and height.\n * @private\n */\n maxBgImageSize: null,\n\n /**\n * @var {Object} maxDragImageSize with properties width and height.\n * @private\n */\n maxDragImageSize: null,\n\n /**\n * @property {object} fp for interacting with the file pickers.\n * @private\n */\n fp: null, // Object containing functions associated with the file picker.\n\n /**\n * Initialise the form javascript features.\n *\n * @method\n */\n init: function() {\n dragDropToImageForm.fp = dragDropToImageForm.filePickers();\n dragDropToImageForm.updateVisibilityOfFilePickers();\n dragDropToImageForm.setOptionsForDragItemSelectors();\n dragDropToImageForm.setupEventHandlers();\n dragDropToImageForm.waitForFilePickerToInitialise();\n },\n\n /**\n * Add html for the preview area.\n */\n setupPreviewArea: function() {\n $('#id_previewareaheader').append(\n '
' +\n '
' +\n ' ' +\n '
' +\n '
' +\n '
' +\n '
');\n },\n\n /**\n * Waits for the file-pickers to be sufficiently ready before initialising the preview.\n */\n waitForFilePickerToInitialise: function() {\n if (dragDropToImageForm.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(dragDropToImageForm.waitForFilePickerToInitialise, 1000);\n return;\n }\n M.util.js_pending('dragDropToImageForm');\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[data-qtype=\"ddimageortext\"]').on('change', '.filepickerhidden', function() {\n M.util.js_pending('dragDropToImageForm');\n dragDropToImageForm.loadPreviewImage();\n });\n if ($('#id_droparea').length) {\n dragDropToImageForm.loadPreviewImage();\n } else {\n // Setup preview area when the background image is uploaded the first time.\n dragDropToImageForm.setupPreviewArea();\n dragDropToImageForm.loadPreviewImage();\n }\n },\n\n /**\n * Loads the preview background image.\n */\n loadPreviewImage: function() {\n $('fieldset#id_previewareaheader .dropbackground')\n .one('load', dragDropToImageForm.afterPreviewImageLoaded)\n .attr('src', dragDropToImageForm.fp.file('bgimage').href);\n },\n\n /**\n * After the background image is loaded, continue setting up the preview.\n */\n afterPreviewImageLoaded: function() {\n dragDropToImageForm.createDropZones();\n M.util.js_complete('dragDropToImageForm');\n },\n\n /**\n * Create, or recreate all the drop zones.\n */\n createDropZones: function() {\n var dropZoneHolder = $('.dropzones');\n dropZoneHolder.empty();\n\n var bgimageurl = dragDropToImageForm.fp.file('bgimage').href;\n if (bgimageurl === null) {\n return; // There is not currently a valid preview to update.\n }\n\n var numDrops = dragDropToImageForm.form.getFormValue('nodropzone', []);\n for (var dropNo = 0; dropNo < numDrops; dropNo++) {\n var dragNo = dragDropToImageForm.form.getFormValue('drops', [dropNo, 'choice']);\n if (dragNo === '0') {\n continue;\n }\n dragNo = dragNo - 1;\n var group = dragDropToImageForm.form.getFormValue('drags', [dragNo, 'draggroup']),\n label = dragDropToImageForm.form.getFormValue('draglabel', [dragNo]);\n if ('image' === dragDropToImageForm.form.getFormValue('drags', [dragNo, 'dragitemtype'])) {\n var imgUrl = dragDropToImageForm.fp.file('dragitem[' + dragNo + ']').href;\n if (imgUrl === null) {\n continue;\n }\n // Althoug these are previews of drops, we also add the class name 'drag',\n dropZoneHolder.append('\"'');\n\n } else if (label !== '') {\n dropZoneHolder.append('
' + label + '
');\n }\n }\n\n dragDropToImageForm.waitForAllDropImagesToBeLoaded();\n },\n\n /**\n * This polls until all the drop-zone images have loaded, and then calls updateDropZones().\n */\n waitForAllDropImagesToBeLoaded: function() {\n var notYetLoadedImages = $('.dropzones img').not(function(i, imgNode) {\n return dragDropToImageForm.imageIsLoaded(imgNode);\n });\n\n if (notYetLoadedImages.length > 0) {\n setTimeout(function() {\n dragDropToImageForm.waitForAllDropImagesToBeLoaded();\n }, 100);\n return;\n }\n\n dragDropToImageForm.updateDropZones();\n },\n\n /**\n * Check if an image has loaded without errors.\n *\n * @param {HTMLImageElement} imgElement an image.\n * @returns {boolean} true if this image has loaded without errors.\n */\n imageIsLoaded: function(imgElement) {\n return imgElement.complete && imgElement.naturalHeight !== 0;\n },\n\n /**\n * Set the size and position of all the drop zones.\n */\n updateDropZones: function() {\n var bgimageurl = dragDropToImageForm.fp.file('bgimage').href;\n if (bgimageurl === null) {\n return; // There is not currently a valid preview to update.\n }\n\n var dropBackgroundPosition = $('fieldset#id_previewareaheader .dropbackground').offset(),\n numDrops = dragDropToImageForm.form.getFormValue('nodropzone', []);\n\n // Move each drop to the right position and update the text.\n for (var dropNo = 0; dropNo < numDrops; dropNo++) {\n var drop = $('.dropzones .drop' + dropNo);\n if (drop.length === 0) {\n continue;\n }\n var dragNo = dragDropToImageForm.form.getFormValue('drops', [dropNo, 'choice']) - 1;\n\n drop.offset({\n left: dropBackgroundPosition.left +\n parseInt(dragDropToImageForm.form.getFormValue('drops', [dropNo, 'xleft'])),\n top: dropBackgroundPosition.top +\n parseInt(dragDropToImageForm.form.getFormValue('drops', [dropNo, 'ytop']))\n });\n\n var label = dragDropToImageForm.form.getFormValue('draglabel', [dragNo]);\n if (drop.is('img')) {\n drop.attr('alt', label);\n } else {\n drop.html(label);\n }\n }\n\n // Resize them to the same size.\n $('.dropzones .droppreview').css('padding', '0');\n var numGroups = $('.draggroup select').first().find('option').length;\n for (var group = 1; group <= numGroups; group++) {\n dragDropToImageForm.resizeAllDragsAndDropsInGroup(group);\n }\n },\n\n /**\n * In a given group, set all the drags and drops to be the same size.\n *\n * @param {int} group the group number.\n */\n resizeAllDragsAndDropsInGroup: function(group) {\n var drops = $('.dropzones .droppreview.group' + group),\n maxWidth = 0,\n maxHeight = 0;\n\n // Find the maximum size of any drag in this groups.\n drops.each(function(i, drop) {\n maxWidth = Math.max(maxWidth, Math.ceil(drop.offsetWidth));\n maxHeight = Math.max(maxHeight, Math.ceil(drop.offsetHeight));\n });\n\n // The size we will want to set is a bit bigger than this.\n maxWidth += 10;\n maxHeight += 10;\n\n // Set each drag home to that size.\n drops.each(function(i, drop) {\n var left = Math.round((maxWidth - drop.offsetWidth) / 2),\n top = Math.floor((maxHeight - drop.offsetHeight) / 2);\n // Set top and left padding so the item is centred.\n $(drop).css({\n 'padding-left': left + 'px',\n 'padding-right': (maxWidth - drop.offsetWidth - left) + 'px',\n 'padding-top': top + 'px',\n 'padding-bottom': (maxHeight - drop.offsetHeight - top) + 'px'\n });\n });\n },\n\n /**\n * Events linked to form actions.\n */\n setupEventHandlers: function() {\n // Changes to settings in the draggable items section.\n $('fieldset#id_draggableitemheader')\n .on('change input', 'input, select', function(e) {\n var input = $(e.target).closest('select, input');\n if (input.hasClass('dragitemtype')) {\n dragDropToImageForm.updateVisibilityOfFilePickers();\n }\n\n dragDropToImageForm.setOptionsForDragItemSelectors();\n\n if (input.is('.dragitemtype, .draggroup')) {\n dragDropToImageForm.createDropZones();\n } else if (input.is('.draglabel')) {\n dragDropToImageForm.updateDropZones();\n }\n });\n\n // Changes to Drop zones section: left, top and drag item.\n $('fieldset#id_dropzoneheader').on('change input', 'input, select', function(e) {\n var input = $(e.target).closest('select, input');\n if (input.is('select')) {\n dragDropToImageForm.createDropZones();\n } else {\n dragDropToImageForm.updateDropZones();\n }\n });\n\n // Moving drop zones in the preview.\n $('fieldset#id_previewareaheader').on('mousedown touchstart', '.droppreview', function(e) {\n dragDropToImageForm.dragStart(e);\n });\n\n $(window).on('resize', function() {\n dragDropToImageForm.updateDropZones();\n });\n },\n\n /**\n * Update all the drag item filepickers, so they are only shown for\n */\n updateVisibilityOfFilePickers: function() {\n var numDrags = dragDropToImageForm.form.getFormValue('noitems', []);\n for (var dragNo = 0; dragNo < numDrags; dragNo++) {\n var picker = $('input#id_dragitem_' + dragNo).closest('.fitem_ffilepicker');\n if ('image' === dragDropToImageForm.form.getFormValue('drags', [dragNo, 'dragitemtype'])) {\n picker.show();\n } else {\n picker.hide();\n }\n }\n },\n\n\n setOptionsForDragItemSelectors: function() {\n var dragItemOptions = {'0': ''},\n numDrags = dragDropToImageForm.form.getFormValue('noitems', []),\n numDrops = dragDropToImageForm.form.getFormValue('nodropzone', []);\n\n // Work out the list of options.\n for (var dragNo = 0; dragNo < numDrags; dragNo++) {\n var label = dragDropToImageForm.form.getFormValue('draglabel', [dragNo]);\n var file = dragDropToImageForm.fp.file(dragDropToImageForm.form.toNameWithIndex('dragitem', [dragNo]));\n if ('image' === dragDropToImageForm.form.getFormValue('drags', [dragNo, 'dragitemtype']) && file.name !== null) {\n dragItemOptions[dragNo + 1] = (dragNo + 1) + '. ' + label + ' (' + file.name + ')';\n } else if (label !== '') {\n dragItemOptions[dragNo + 1] = (dragNo + 1) + '. ' + label;\n }\n }\n\n // Initialise each select.\n for (var dropNo = 0; dropNo < numDrops; dropNo++) {\n var selector = $('#id_drops_' + dropNo + '_choice');\n\n var selectedvalue = selector.val();\n selector.find('option').remove();\n for (var value in dragItemOptions) {\n if (!dragItemOptions.hasOwnProperty(value)) {\n continue;\n }\n selector.append('');\n var optionnode = selector.find('option[value=\"' + value + '\"]');\n if (parseInt(value) === parseInt(selectedvalue)) {\n optionnode.attr('selected', true);\n } else if (dragDropToImageForm.isItemUsed(parseInt(value))) {\n optionnode.attr('disabled', true);\n }\n }\n }\n },\n\n /**\n * Checks if the specified drag option is already used somewhere.\n *\n * @param {Number} value of the drag item to check\n * @return {Boolean} true if item is allocated to dropzone\n */\n isItemUsed: function(value) {\n if (value === 0) {\n return false; // None option can always be selected.\n }\n\n if (dragDropToImageForm.form.getFormValue('drags', [value - 1, 'infinite'])) {\n return false; // Infinite, so can't be used up.\n }\n\n return $('fieldset#id_dropzoneheader select').filter(function(i, selectNode) {\n return parseInt($(selectNode).val()) === value;\n }).length !== 0;\n },\n\n /**\n * Handles when a dropzone in dragged in the preview.\n * @param {Object} e Event object\n */\n dragStart: function(e) {\n var drop = $(e.target).closest('.droppreview');\n\n var info = dragDrop.prepare(e);\n if (!info.start) {\n return;\n }\n\n dragDrop.start(e, drop, function(x, y, drop) {\n dragDropToImageForm.dragMove(drop);\n }, function() {\n dragDropToImageForm.dragEnd();\n });\n },\n\n /**\n * Handles update while a drop is being dragged.\n *\n * @param {jQuery} drop the drop preview being moved.\n */\n dragMove: function(drop) {\n var backgroundImage = $('fieldset#id_previewareaheader .dropbackground'),\n backgroundPosition = backgroundImage.offset(),\n dropNo = drop.data('dropNo'),\n dropPosition = drop.offset(),\n left = Math.round(dropPosition.left - backgroundPosition.left),\n top = Math.round(dropPosition.top - backgroundPosition.top);\n\n // Constrain coordinates to be inside the background.\n left = Math.round(Math.max(0, Math.min(left, backgroundImage.outerWidth() - drop.outerWidth())));\n top = Math.round(Math.max(0, Math.min(top, backgroundImage.outerHeight() - drop.outerHeight())));\n\n // Update the form.\n dragDropToImageForm.form.setFormValue('drops', [dropNo, 'xleft'], left);\n dragDropToImageForm.form.setFormValue('drops', [dropNo, 'ytop'], top);\n },\n\n /**\n * Handles when the drag ends.\n */\n dragEnd: function() {\n // Redraw, in case the position was constrained.\n dragDropToImageForm.updateDropZones();\n },\n\n /**\n * Low level operations on form.\n */\n form: {\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[data-qtype=\"ddimageortext\"]')[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][xleft]\".\n *\n * @param {String} name the base name, e.g. 'drops'.\n * @param {String[]} indexes the indexes, e.g. ['0', 'xleft'].\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) {\n el = el[el.length - 1];\n }\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][xleft]\".\n *\n * @param {String} name the base name, e.g. 'drops'.\n * @param {String[]} indexes the indexes, e.g. ['0', 'xleft'].\n * @param {String|Number} 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\n if (draftItemIdsToName === undefined) {\n draftItemIdsToName = {};\n nameToParentNode = {};\n var fp = $('form.mform[data-qtype=\"ddimageortext\"] input.filepickerhidden');\n fp.each(function(index, filepicker) {\n draftItemIdsToName[filepicker.value] = filepicker.name;\n nameToParentNode[filepicker.name] = filepicker.parentNode;\n });\n }\n\n return {\n file: function(name) {\n var parentNode = $(nameToParentNode[name]);\n var fileAnchor = parentNode.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\n name: function(draftitemid) {\n return draftItemIdsToName[draftitemid];\n }\n };\n }\n };\n\n return {\n init: dragDropToImageForm.init\n };\n});\n"],"names":["define","$","dragDrop","dragDropToImageForm","maxBgImageSize","maxDragImageSize","fp","init","filePickers","updateVisibilityOfFilePickers","setOptionsForDragItemSelectors","setupEventHandlers","waitForFilePickerToInitialise","setupPreviewArea","append","file","href","M","util","js_pending","on","loadPreviewImage","length","setTimeout","one","afterPreviewImageLoaded","attr","createDropZones","js_complete","dropZoneHolder","empty","numDrops","form","getFormValue","dropNo","dragNo","group","label","imgUrl","waitForAllDropImagesToBeLoaded","not","i","imgNode","imageIsLoaded","updateDropZones","imgElement","complete","naturalHeight","dropBackgroundPosition","offset","drop","left","parseInt","top","is","html","css","numGroups","first","find","resizeAllDragsAndDropsInGroup","drops","maxWidth","maxHeight","each","Math","max","ceil","offsetWidth","offsetHeight","round","floor","e","input","target","closest","hasClass","dragStart","window","numDrags","picker","show","hide","dragItemOptions","toNameWithIndex","name","selector","selectedvalue","val","value","remove","hasOwnProperty","optionnode","isItemUsed","filter","selectNode","prepare","start","x","y","dragMove","dragEnd","backgroundImage","backgroundPosition","data","dropPosition","min","outerWidth","outerHeight","setFormValue","indexes","indexString","getEl","elements","this","el","type","checked","draftItemIdsToName","nameToParentNode","undefined","index","filepicker","parentNode","fileAnchor","get","innerHTML","draftitemid"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"form.min.js","sources":["../src/form.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/*\n * JavaScript to allow dragging options to slots (using mouse down or touch) or tab through slots using keyboard.\n *\n * @module qtype_ddimageortext/form\n * @copyright 2018 The Open University\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/dragdrop'], function($, dragDrop) {\n\n \"use strict\";\n\n /**\n * Singleton object to handle progressive enhancement of the\n * drag-drop onto image question editing form.\n * @type {Object}\n */\n var dragDropToImageForm = {\n /**\n * @var {Object} maxBgImageSize Properties width and height.\n * @private\n */\n maxBgImageSize: null,\n\n /**\n * @var {Object} maxDragImageSize with properties width and height.\n * @private\n */\n maxDragImageSize: null,\n\n /**\n * @property {object} fp for interacting with the file pickers.\n * @private\n */\n fp: null, // Object containing functions associated with the file picker.\n\n /**\n * Initialise the form javascript features.\n *\n * @method\n */\n init: function() {\n dragDropToImageForm.fp = dragDropToImageForm.filePickers();\n dragDropToImageForm.updateVisibilityOfFilePickers();\n dragDropToImageForm.setOptionsForDragItemSelectors();\n dragDropToImageForm.setupEventHandlers();\n dragDropToImageForm.waitForFilePickerToInitialise();\n },\n\n /**\n * Add html for the preview area.\n */\n setupPreviewArea: function() {\n $('#id_previewareaheader').append(\n '
' +\n '
' +\n ' ' +\n '
' +\n '
' +\n '
' +\n '
');\n },\n\n /**\n * Waits for the file-pickers to be sufficiently ready before initialising the preview.\n */\n waitForFilePickerToInitialise: function() {\n if (dragDropToImageForm.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(dragDropToImageForm.waitForFilePickerToInitialise, 1000);\n return;\n }\n M.util.js_pending('dragDropToImageForm');\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[data-qtype=\"ddimageortext\"]').on('change', '.filepickerhidden', function() {\n M.util.js_pending('dragDropToImageForm');\n dragDropToImageForm.loadPreviewImage();\n });\n if ($('#id_droparea').length) {\n dragDropToImageForm.loadPreviewImage();\n } else {\n // Setup preview area when the background image is uploaded the first time.\n dragDropToImageForm.setupPreviewArea();\n dragDropToImageForm.loadPreviewImage();\n }\n },\n\n /**\n * Loads the preview background image.\n */\n loadPreviewImage: function() {\n $('fieldset#id_previewareaheader .dropbackground')\n .one('load', dragDropToImageForm.afterPreviewImageLoaded)\n .attr('src', dragDropToImageForm.fp.file('bgimage').href);\n },\n\n /**\n * After the background image is loaded, continue setting up the preview.\n */\n afterPreviewImageLoaded: function() {\n dragDropToImageForm.createDropZones();\n M.util.js_complete('dragDropToImageForm');\n },\n\n /**\n * Create, or recreate all the drop zones.\n */\n createDropZones: function() {\n var dropZoneHolder = $('.dropzones');\n dropZoneHolder.empty();\n\n var bgimageurl = dragDropToImageForm.fp.file('bgimage').href;\n if (bgimageurl === null) {\n return; // There is not currently a valid preview to update.\n }\n\n var numDrops = dragDropToImageForm.form.getFormValue('nodropzone', []);\n var dropzonevisibility = dragDropToImageForm.form.getFormValue('dropzonevisibility', []);\n var dropzonevisibilitystyle = dropzonevisibility === '1' ? 'background: transparent;' : '';\n for (var dropNo = 0; dropNo < numDrops; dropNo++) {\n var dragNo = dragDropToImageForm.form.getFormValue('drops', [dropNo, 'choice']);\n if (dragNo === '0') {\n continue;\n }\n dragNo = dragNo - 1;\n var group = dragDropToImageForm.form.getFormValue('drags', [dragNo, 'draggroup']),\n label = dragDropToImageForm.form.getFormValue('draglabel', [dragNo]);\n if ('image' === dragDropToImageForm.form.getFormValue('drags', [dragNo, 'dragitemtype'])) {\n var imgUrl = dragDropToImageForm.fp.file('dragitem[' + dragNo + ']').href;\n if (imgUrl === null) {\n continue;\n }\n // Althoug these are previews of drops, we also add the class name 'drag',\n dropZoneHolder.append('\"'');\n\n } else if (label !== '') {\n dropZoneHolder.append('
' + label + '
');\n }\n }\n\n dragDropToImageForm.waitForAllDropImagesToBeLoaded();\n },\n\n /**\n * This polls until all the drop-zone images have loaded, and then calls updateDropZones().\n */\n waitForAllDropImagesToBeLoaded: function() {\n var notYetLoadedImages = $('.dropzones img').not(function(i, imgNode) {\n return dragDropToImageForm.imageIsLoaded(imgNode);\n });\n\n if (notYetLoadedImages.length > 0) {\n setTimeout(function() {\n dragDropToImageForm.waitForAllDropImagesToBeLoaded();\n }, 100);\n return;\n }\n\n dragDropToImageForm.updateDropZones();\n },\n\n /**\n * Check if an image has loaded without errors.\n *\n * @param {HTMLImageElement} imgElement an image.\n * @returns {boolean} true if this image has loaded without errors.\n */\n imageIsLoaded: function(imgElement) {\n return imgElement.complete && imgElement.naturalHeight !== 0;\n },\n\n /**\n * Set the size and position of all the drop zones.\n */\n updateDropZones: function() {\n var bgimageurl = dragDropToImageForm.fp.file('bgimage').href;\n if (bgimageurl === null) {\n return; // There is not currently a valid preview to update.\n }\n\n var dropBackgroundPosition = $('fieldset#id_previewareaheader .dropbackground').offset(),\n numDrops = dragDropToImageForm.form.getFormValue('nodropzone', []);\n\n // Move each drop to the right position and update the text.\n for (var dropNo = 0; dropNo < numDrops; dropNo++) {\n var drop = $('.dropzones .drop' + dropNo);\n if (drop.length === 0) {\n continue;\n }\n var dragNo = dragDropToImageForm.form.getFormValue('drops', [dropNo, 'choice']) - 1;\n\n drop.offset({\n left: dropBackgroundPosition.left +\n parseInt(dragDropToImageForm.form.getFormValue('drops', [dropNo, 'xleft'])),\n top: dropBackgroundPosition.top +\n parseInt(dragDropToImageForm.form.getFormValue('drops', [dropNo, 'ytop']))\n });\n\n var label = dragDropToImageForm.form.getFormValue('draglabel', [dragNo]);\n if (drop.is('img')) {\n drop.attr('alt', label);\n } else {\n drop.html(label);\n }\n }\n\n // Resize them to the same size.\n $('.dropzones .droppreview').css('padding', '0');\n var numGroups = $('.draggroup select').first().find('option').length;\n for (var group = 1; group <= numGroups; group++) {\n dragDropToImageForm.resizeAllDragsAndDropsInGroup(group);\n }\n },\n\n /**\n * In a given group, set all the drags and drops to be the same size.\n *\n * @param {int} group the group number.\n */\n resizeAllDragsAndDropsInGroup: function(group) {\n var drops = $('.dropzones .droppreview.group' + group),\n maxWidth = 0,\n maxHeight = 0;\n\n // Find the maximum size of any drag in this groups.\n drops.each(function(i, drop) {\n maxWidth = Math.max(maxWidth, Math.ceil(drop.offsetWidth));\n maxHeight = Math.max(maxHeight, Math.ceil(drop.offsetHeight));\n });\n\n // The size we will want to set is a bit bigger than this.\n maxWidth += 10;\n maxHeight += 10;\n\n // Set each drag home to that size.\n drops.each(function(i, drop) {\n var left = Math.round((maxWidth - drop.offsetWidth) / 2),\n top = Math.floor((maxHeight - drop.offsetHeight) / 2);\n // Set top and left padding so the item is centred.\n $(drop).css({\n 'padding-left': left + 'px',\n 'padding-right': (maxWidth - drop.offsetWidth - left) + 'px',\n 'padding-top': top + 'px',\n 'padding-bottom': (maxHeight - drop.offsetHeight - top) + 'px'\n });\n });\n },\n\n /**\n * Events linked to form actions.\n */\n setupEventHandlers: function() {\n // Changes to settings in the draggable items section.\n $('fieldset#id_draggableitemheader')\n .on('change input', 'input, select', function(e) {\n var input = $(e.target).closest('select, input');\n if (input.hasClass('dragitemtype')) {\n dragDropToImageForm.updateVisibilityOfFilePickers();\n }\n\n dragDropToImageForm.setOptionsForDragItemSelectors();\n\n if (input.is('.dragitemtype, .draggroup')) {\n dragDropToImageForm.createDropZones();\n } else if (input.is('.draglabel')) {\n dragDropToImageForm.updateDropZones();\n }\n });\n\n // Changes to Drop zones section: left, top and drag item.\n $('fieldset#id_dropzoneheader').on('change input', 'input, select', function(e) {\n var input = $(e.target).closest('select, input');\n if (input.attr('id') === 'id_dropzonevisibility') {\n return;\n }\n\n if (input.is('select')) {\n dragDropToImageForm.createDropZones();\n } else {\n dragDropToImageForm.updateDropZones();\n }\n });\n\n // Moving drop zones in the preview.\n $('fieldset#id_previewareaheader').on('mousedown touchstart', '.droppreview', function(e) {\n dragDropToImageForm.dragStart(e);\n });\n\n $(window).on('resize', function() {\n dragDropToImageForm.updateDropZones();\n });\n\n $('#id_dropzonevisibility').on('change', function() {\n let selectedvalue = $(this).val();\n if (selectedvalue === \"1\") {\n $('.droppreview').css('background', 'transparent');\n } else if (selectedvalue === \"0\") {\n $('.droppreview').css('background', '');\n }\n });\n },\n\n /**\n * Update all the drag item filepickers, so they are only shown for\n */\n updateVisibilityOfFilePickers: function() {\n var numDrags = dragDropToImageForm.form.getFormValue('noitems', []);\n for (var dragNo = 0; dragNo < numDrags; dragNo++) {\n var picker = $('input#id_dragitem_' + dragNo).closest('.fitem_ffilepicker');\n if ('image' === dragDropToImageForm.form.getFormValue('drags', [dragNo, 'dragitemtype'])) {\n picker.show();\n } else {\n picker.hide();\n }\n }\n },\n\n\n setOptionsForDragItemSelectors: function() {\n var dragItemOptions = {'0': ''},\n numDrags = dragDropToImageForm.form.getFormValue('noitems', []),\n numDrops = dragDropToImageForm.form.getFormValue('nodropzone', []);\n\n // Work out the list of options.\n for (var dragNo = 0; dragNo < numDrags; dragNo++) {\n var label = dragDropToImageForm.form.getFormValue('draglabel', [dragNo]);\n var file = dragDropToImageForm.fp.file(dragDropToImageForm.form.toNameWithIndex('dragitem', [dragNo]));\n if ('image' === dragDropToImageForm.form.getFormValue('drags', [dragNo, 'dragitemtype']) && file.name !== null) {\n dragItemOptions[dragNo + 1] = (dragNo + 1) + '. ' + label + ' (' + file.name + ')';\n } else if (label !== '') {\n dragItemOptions[dragNo + 1] = (dragNo + 1) + '. ' + label;\n }\n }\n\n // Initialise each select.\n for (var dropNo = 0; dropNo < numDrops; dropNo++) {\n var selector = $('#id_drops_' + dropNo + '_choice');\n\n var selectedvalue = selector.val();\n selector.find('option').remove();\n for (var value in dragItemOptions) {\n if (!dragItemOptions.hasOwnProperty(value)) {\n continue;\n }\n selector.append('');\n var optionnode = selector.find('option[value=\"' + value + '\"]');\n if (parseInt(value) === parseInt(selectedvalue)) {\n optionnode.attr('selected', true);\n } else if (dragDropToImageForm.isItemUsed(parseInt(value))) {\n optionnode.attr('disabled', true);\n }\n }\n }\n },\n\n /**\n * Checks if the specified drag option is already used somewhere.\n *\n * @param {Number} value of the drag item to check\n * @return {Boolean} true if item is allocated to dropzone\n */\n isItemUsed: function(value) {\n if (value === 0) {\n return false; // None option can always be selected.\n }\n\n if (dragDropToImageForm.form.getFormValue('drags', [value - 1, 'infinite'])) {\n return false; // Infinite, so can't be used up.\n }\n\n return $('fieldset#id_dropzoneheader select[name^=\"drops\"]').filter(function(i, selectNode) {\n return parseInt($(selectNode).val()) === value;\n }).length !== 0;\n },\n\n /**\n * Handles when a dropzone in dragged in the preview.\n * @param {Object} e Event object\n */\n dragStart: function(e) {\n var drop = $(e.target).closest('.droppreview');\n\n var info = dragDrop.prepare(e);\n if (!info.start) {\n return;\n }\n\n dragDrop.start(e, drop, function(x, y, drop) {\n dragDropToImageForm.dragMove(drop);\n }, function() {\n dragDropToImageForm.dragEnd();\n });\n },\n\n /**\n * Handles update while a drop is being dragged.\n *\n * @param {jQuery} drop the drop preview being moved.\n */\n dragMove: function(drop) {\n var backgroundImage = $('fieldset#id_previewareaheader .dropbackground'),\n backgroundPosition = backgroundImage.offset(),\n dropNo = drop.data('dropNo'),\n dropPosition = drop.offset(),\n left = Math.round(dropPosition.left - backgroundPosition.left),\n top = Math.round(dropPosition.top - backgroundPosition.top);\n\n // Constrain coordinates to be inside the background.\n left = Math.round(Math.max(0, Math.min(left, backgroundImage.outerWidth() - drop.outerWidth())));\n top = Math.round(Math.max(0, Math.min(top, backgroundImage.outerHeight() - drop.outerHeight())));\n\n // Update the form.\n dragDropToImageForm.form.setFormValue('drops', [dropNo, 'xleft'], left);\n dragDropToImageForm.form.setFormValue('drops', [dropNo, 'ytop'], top);\n },\n\n /**\n * Handles when the drag ends.\n */\n dragEnd: function() {\n // Redraw, in case the position was constrained.\n dragDropToImageForm.updateDropZones();\n },\n\n /**\n * Low level operations on form.\n */\n form: {\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[data-qtype=\"ddimageortext\"]')[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][xleft]\".\n *\n * @param {String} name the base name, e.g. 'drops'.\n * @param {String[]} indexes the indexes, e.g. ['0', 'xleft'].\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) {\n el = el[el.length - 1];\n }\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][xleft]\".\n *\n * @param {String} name the base name, e.g. 'drops'.\n * @param {String[]} indexes the indexes, e.g. ['0', 'xleft'].\n * @param {String|Number} 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\n if (draftItemIdsToName === undefined) {\n draftItemIdsToName = {};\n nameToParentNode = {};\n var fp = $('form.mform[data-qtype=\"ddimageortext\"] input.filepickerhidden');\n fp.each(function(index, filepicker) {\n draftItemIdsToName[filepicker.value] = filepicker.name;\n nameToParentNode[filepicker.name] = filepicker.parentNode;\n });\n }\n\n return {\n file: function(name) {\n var parentNode = $(nameToParentNode[name]);\n var fileAnchor = parentNode.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\n name: function(draftitemid) {\n return draftItemIdsToName[draftitemid];\n }\n };\n }\n };\n\n return {\n init: dragDropToImageForm.init\n };\n});\n"],"names":["define","$","dragDrop","dragDropToImageForm","maxBgImageSize","maxDragImageSize","fp","init","filePickers","updateVisibilityOfFilePickers","setOptionsForDragItemSelectors","setupEventHandlers","waitForFilePickerToInitialise","setupPreviewArea","append","file","href","M","util","js_pending","on","loadPreviewImage","length","setTimeout","one","afterPreviewImageLoaded","attr","createDropZones","js_complete","dropZoneHolder","empty","numDrops","form","getFormValue","dropzonevisibilitystyle","dropNo","dragNo","group","label","imgUrl","waitForAllDropImagesToBeLoaded","not","i","imgNode","imageIsLoaded","updateDropZones","imgElement","complete","naturalHeight","dropBackgroundPosition","offset","drop","left","parseInt","top","is","html","css","numGroups","first","find","resizeAllDragsAndDropsInGroup","drops","maxWidth","maxHeight","each","Math","max","ceil","offsetWidth","offsetHeight","round","floor","e","input","target","closest","hasClass","dragStart","window","selectedvalue","this","val","numDrags","picker","show","hide","dragItemOptions","toNameWithIndex","name","selector","value","remove","hasOwnProperty","optionnode","isItemUsed","filter","selectNode","prepare","start","x","y","dragMove","dragEnd","backgroundImage","backgroundPosition","data","dropPosition","min","outerWidth","outerHeight","setFormValue","indexes","indexString","getEl","elements","el","type","checked","draftItemIdsToName","nameToParentNode","undefined","index","filepicker","parentNode","fileAnchor","get","innerHTML","draftitemid"],"mappings":"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"} \ No newline at end of file diff --git a/question/type/ddimageortext/amd/src/form.js b/question/type/ddimageortext/amd/src/form.js index 684c99ed30d..87e90c197f8 100644 --- a/question/type/ddimageortext/amd/src/form.js +++ b/question/type/ddimageortext/amd/src/form.js @@ -133,6 +133,8 @@ define(['jquery', 'core/dragdrop'], function($, dragDrop) { } var numDrops = dragDropToImageForm.form.getFormValue('nodropzone', []); + var dropzonevisibility = dragDropToImageForm.form.getFormValue('dropzonevisibility', []); + var dropzonevisibilitystyle = dropzonevisibility === '1' ? 'background: transparent;' : ''; for (var dropNo = 0; dropNo < numDrops; dropNo++) { var dragNo = dragDropToImageForm.form.getFormValue('drops', [dropNo, 'choice']); if (dragNo === '0') { @@ -148,7 +150,8 @@ define(['jquery', 'core/dragdrop'], function($, dragDrop) { } // Althoug these are previews of drops, we also add the class name 'drag', dropZoneHolder.append('' + label + ''); + '" src="' + imgUrl + '" alt="' + label + '" data-drop-no="' + dropNo + + '" style="' + dropzonevisibilitystyle + '" >'); } else if (label !== '') { dropZoneHolder.append('
add_child($dds); $drags = new backup_nested_element('drags'); diff --git a/question/type/ddimageortext/db/install.xml b/question/type/ddimageortext/db/install.xml index 90759b8d054..14c30aed044 100644 --- a/question/type/ddimageortext/db/install.xml +++ b/question/type/ddimageortext/db/install.xml @@ -16,6 +16,7 @@ + diff --git a/question/type/ddimageortext/db/upgrade.php b/question/type/ddimageortext/db/upgrade.php new file mode 100644 index 00000000000..f3853934daf --- /dev/null +++ b/question/type/ddimageortext/db/upgrade.php @@ -0,0 +1,50 @@ +. + +/** + * Ddimageortext question type upgrade code. + * + * @package qtype_ddimageortext + * @copyright 2024 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** + * Upgrade code for the qtype_ddimageortext question type. + * @param int $oldversion the version we are upgrading from. + * @return bool + */ +function xmldb_qtype_ddimageortext_upgrade($oldversion) { + global $DB; + + $dbman = $DB->get_manager(); // Loads ddl manager and xmldb classes. + if ($oldversion < 2025010901) { + + // Define field dropzonevisibility to be added to qtype_ddimageortext. + $table = new xmldb_table('qtype_ddimageortext'); + $field = new xmldb_field('dropzonevisibility', XMLDB_TYPE_INTEGER, '2', null, XMLDB_NOTNULL, null, '0', 'shownumcorrect'); + + // Conditionally launch add field dropzonevisibility. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Ddimageortext savepoint reached. + upgrade_plugin_savepoint(true, 2025010901, 'qtype', 'ddimageortext'); + } + + return true; +} diff --git a/question/type/ddimageortext/edit_ddimageortext_form.php b/question/type/ddimageortext/edit_ddimageortext_form.php index 21a2f46cb17..47842ae2a32 100644 --- a/question/type/ddimageortext/edit_ddimageortext_form.php +++ b/question/type/ddimageortext/edit_ddimageortext_form.php @@ -47,6 +47,7 @@ class qtype_ddimageortext_edit_form extends qtype_ddtoimage_edit_form_base { $dragids = array(); // Drag no -> dragid. if (!empty($question->options)) { $question->shuffleanswers = $question->options->shuffleanswers; + $question->dropzonevisibility = $question->options->dropzonevisibility; $question->drags = array(); foreach ($question->options->drags as $drag) { $dragindex = $drag->no - 1; @@ -168,6 +169,16 @@ class qtype_ddimageortext_edit_form extends qtype_ddtoimage_edit_form_base { $dropzoneitem = array(); $grouparray = array(); + + $options = [ + 0 => get_string('dropzonevisibility_showoption', 'qtype_ddimageortext'), + 1 => get_string('dropzonevisibility_hideoption', 'qtype_ddimageortext'), + ]; + + $mform->addElement('select', 'dropzonevisibility', + get_string('dropzonevisibility', 'qtype_ddimageortext'), $options); + $mform->setDefault('dropzonevisibility', $this->get_default_value('dropzonevisibility', 0)); + $mform->addHelpButton('dropzonevisibility', 'dropzonevisibility', 'qtype_ddimageortext'); $grouparray[] = $mform->createElement('text', 'xleft', get_string('xleft', 'qtype_ddimageortext'), array('size' => 5, 'class' => 'tweakcss')); diff --git a/question/type/ddimageortext/lang/en/qtype_ddimageortext.php b/question/type/ddimageortext/lang/en/qtype_ddimageortext.php index b55c0494a6c..a60962df4e3 100644 --- a/question/type/ddimageortext/lang/en/qtype_ddimageortext.php +++ b/question/type/ddimageortext/lang/en/qtype_ddimageortext.php @@ -38,6 +38,11 @@ $string['draggableword'] = 'Draggable text'; $string['dropbackground'] = 'Background image for dragging markers onto'; $string['dropzone'] = 'Drop zone {$a}'; $string['dropzoneheader'] = 'Drop zones'; +$string['dropzonevisibility'] = 'Drop zone visibility'; +$string['dropzonevisibility_help'] = 'Setting \'Hide drop zones\' will make them invisible when the question is attempted. However, drag items will still snap to nearby drop zones. This setting is recommended if you have images over an image background and the normal drop zone indicators are not desired. Drop zone borders will remain visible while editing to help with placement.'; +$string['dropzonevisibility_hideoption'] = 'Hide drop zones'; +$string['dropzonevisibility_showoption'] = 'Show drop zones'; +$string['dropzonevisibilitydesc'] = 'Borders will still be visible when editing the question to help with setting the drop zone.'; $string['formerror_disallowedtags'] = 'Only "{$a}" tags are allowed in this draggable text.'; $string['formerror_dragrequired'] = 'You must add at least one draggable item to this question.'; $string['formerror_droprequired'] = 'You must define at least one drop zone for this question.'; diff --git a/question/type/ddimageortext/question.php b/question/type/ddimageortext/question.php index 94c2f861b55..37a3807e232 100644 --- a/question/type/ddimageortext/question.php +++ b/question/type/ddimageortext/question.php @@ -35,7 +35,8 @@ require_once($CFG->dirroot . '/question/type/ddimageortext/questionbase.php'); * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class qtype_ddimageortext_question extends qtype_ddtoimage_question_base { - + /** @var string Whether the dropzone transparent or not. */ + public $dropzonevisibility; } diff --git a/question/type/ddimageortext/questiontype.php b/question/type/ddimageortext/questiontype.php index fa5e42c66ca..9a02da0bf4b 100644 --- a/question/type/ddimageortext/questiontype.php +++ b/question/type/ddimageortext/questiontype.php @@ -53,6 +53,7 @@ class qtype_ddimageortext extends qtype_ddtoimage_base { public function save_defaults_for_new_questions(stdClass $fromform): void { parent::save_defaults_for_new_questions($fromform); $this->set_default_value('shuffleanswers', $fromform->shuffleanswers); + $this->set_default_value('dropzonevisibility', $fromform->dropzonevisibility); } public function save_question_options($formdata) { @@ -70,6 +71,7 @@ class qtype_ddimageortext extends qtype_ddtoimage_base { } $options->shuffleanswers = !empty($formdata->shuffleanswers); + $options->dropzonevisibility = !empty($formdata->dropzonevisibility); $options = $this->save_combined_feedback_helper($options, $formdata, $context, true); $this->save_hints($formdata, true); $DB->update_record('qtype_ddimageortext', $options); @@ -185,6 +187,9 @@ class qtype_ddimageortext extends qtype_ddtoimage_base { if ($question->options->shuffleanswers) { $output .= " \n"; } + if ($question->options->dropzonevisibility) { + $output .= " \n"; + } $output .= $format->write_combined_feedback($question->options, $question->id, $question->contextid); @@ -227,6 +232,8 @@ class qtype_ddimageortext extends qtype_ddtoimage_base { $question->shuffleanswers = array_key_exists('shuffleanswers', $format->getpath($data, array('#'), array())); + $question->dropzonevisibility = array_key_exists('dropzonevisibility', + $format->getpath($data, ['#'], [])); $filexml = $format->getpath($data, array('#', 'file'), array()); $question->bgimage = $format->import_files_as_draft($filexml); diff --git a/question/type/ddimageortext/questiontypebase.php b/question/type/ddimageortext/questiontypebase.php index 947e239578e..d028412504e 100644 --- a/question/type/ddimageortext/questiontypebase.php +++ b/question/type/ddimageortext/questiontypebase.php @@ -61,6 +61,9 @@ class qtype_ddtoimage_base extends question_type { protected function initialise_question_instance(question_definition $question, $questiondata) { parent::initialise_question_instance($question, $questiondata); $question->shufflechoices = $questiondata->options->shuffleanswers; + if (isset($questiondata->options->dropzonevisibility)) { + $question->dropzonevisibility = $questiondata->options->dropzonevisibility; + } $this->initialise_combined_feedback($question, $questiondata, true); diff --git a/question/type/ddimageortext/rendererbase.php b/question/type/ddimageortext/rendererbase.php index cb201cd397d..90c25f604b9 100644 --- a/question/type/ddimageortext/rendererbase.php +++ b/question/type/ddimageortext/rendererbase.php @@ -59,6 +59,10 @@ class qtype_ddtoimage_renderer_base extends qtype_with_combined_feedback_rendere $dropareaclass = 'droparea'; $draghomesclass = 'draghomes'; + if ($question->dropzonevisibility) { + $draghomesclass .= ' transparent'; + $dropareaclass .= ' transparent'; + } if ($options->readonly) { $dropareaclass .= ' readonly'; $draghomesclass .= ' readonly'; diff --git a/question/type/ddimageortext/styles.css b/question/type/ddimageortext/styles.css index 2fae37b115f..9596236a938 100644 --- a/question/type/ddimageortext/styles.css +++ b/question/type/ddimageortext/styles.css @@ -132,6 +132,20 @@ form.mform fieldset#id_previewareaheader .group1 { background-color: #fff; } +.que.ddimageortext .transparent .dropzones > img { + border: none; +} + +.que.ddimageortext .transparent .dropzones > img:hover { + border: 1px solid #000; +} + +.que.ddimageortext .transparent img:not(.dropbackground):hover, +.que.ddimageortext .droparea.transparent .dropzone, +.que.ddimageortext .droparea.transparent .dropzone + img { + background-color: transparent; +} + .que.ddimageortext .group2, form.mform fieldset#id_previewareaheader .group2 { background-color: #b0c4de; diff --git a/question/type/ddimageortext/tests/behat/add.feature b/question/type/ddimageortext/tests/behat/add.feature index c948c845b20..b1768d44881 100644 --- a/question/type/ddimageortext/tests/behat/add.feature +++ b/question/type/ddimageortext/tests/behat/add.feature @@ -57,6 +57,7 @@ Feature: Test creating a drag and drop onto image question # Drop zones And I follow "Drop zones" + And I set the field "id_dropzonevisibility" to "1" And I press "Blanks for 3 more drop zones" And I set the field "id_drops_0_xleft" to "53" @@ -98,7 +99,8 @@ Feature: Test creating a drag and drop onto image question And I set the field "item_qtype_ddimageortext" to "1" And I click on "Add" "button" in the "Choose a question type to add" "dialogue" And the following fields match these values: - | id_shuffleanswers | 1 | + | id_shuffleanswers | 1 | + | id_dropzonevisibility | 1 | @javascript @_file_upload Scenario: Question must have at least one drag item and one drop zone diff --git a/question/type/ddimageortext/tests/behat/backup_and_restore.feature b/question/type/ddimageortext/tests/behat/backup_and_restore.feature index 0d19aef4192..bf90ad48bb3 100644 --- a/question/type/ddimageortext/tests/behat/backup_and_restore.feature +++ b/question/type/ddimageortext/tests/behat/backup_and_restore.feature @@ -37,6 +37,7 @@ Feature: Test duplicating a quiz containing a drag and drop onto image question | General feedback |

More information about the major features of the Earth's surface can be found in Block 3, Section 6.2.

| | Default mark | 1 | | Shuffle | 0 | + | id_dropzonevisibility | 0 | | id_drags_0_dragitemtype | Draggable text | | id_drags_0_draggroup | 1 | | id_draglabel_0 | island
arc | diff --git a/question/type/ddimageortext/tests/helper.php b/question/type/ddimageortext/tests/helper.php index 8f0ee8810fd..eb642859372 100644 --- a/question/type/ddimageortext/tests/helper.php +++ b/question/type/ddimageortext/tests/helper.php @@ -167,6 +167,7 @@ class qtype_ddimageortext_test_helper extends question_test_helper { ); $fromform->bgimage = $bgdraftitemid; $fromform->shuffleanswers = 0; + $fromform->dropzonevisibility = 0; $fromform->drags = array( array('dragitemtype' => 'word', 'draggroup' => '1', 'infinite' => '0'), array('dragitemtype' => 'word', 'draggroup' => '1', 'infinite' => '0'), diff --git a/question/type/ddimageortext/version.php b/question/type/ddimageortext/version.php index ecc57f11b81..6d7a07ea74a 100644 --- a/question/type/ddimageortext/version.php +++ b/question/type/ddimageortext/version.php @@ -24,7 +24,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2024121800; +$plugin->version = 2025010901; $plugin->requires = 2024100100; $plugin->component = 'qtype_ddimageortext';