MDL-63297 qtype_ddimageortext: re-implement JavaScript in AMD
this kills of a bunch of old YUI code. Should also make this question type work a bit mroe robustly.
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,541 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/*
|
||||
* JavaScript to allow dragging options to slots (using mouse down or touch) or tab through slots using keyboard.
|
||||
*
|
||||
* @module qtype_ddimageortext/form
|
||||
* @package qtype_ddimageortext
|
||||
* @copyright 2018 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/dragdrop'], function($, dragDrop) {
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Singleton object to handle progressive enhancement of the
|
||||
* drag-drop onto image question editing form.
|
||||
* @type {Object}
|
||||
*/
|
||||
var dragDropToImageForm = {
|
||||
/**
|
||||
* @var {Object} with properties width and height.
|
||||
*/
|
||||
maxBgImageSize: null,
|
||||
|
||||
/**
|
||||
* @var {Object} with properties width and height.
|
||||
*/
|
||||
maxDragImageSize: null,
|
||||
|
||||
/**
|
||||
* @var {object} for interacting with the file pickers.
|
||||
*/
|
||||
fp: null, // Object containing functions associated with the file picker.
|
||||
|
||||
/**
|
||||
* Initialise the form javascript features.
|
||||
*
|
||||
* @param {Object} maxBgImageSize object with two properties: width and height.
|
||||
* @param {Object} maxDragImageSize object with two properties: width and height.
|
||||
*/
|
||||
init: function(maxBgImageSize, maxDragImageSize) {
|
||||
dragDropToImageForm.maxBgImageSize = maxBgImageSize;
|
||||
dragDropToImageForm.maxDragImageSize = maxDragImageSize;
|
||||
dragDropToImageForm.fp = dragDropToImageForm.filePickers();
|
||||
|
||||
$('#id_previewareaheader').append(
|
||||
'<div class="ddarea">' +
|
||||
' <div class="droparea">' +
|
||||
' <img class="dropbackground" />' +
|
||||
' <div class="dropzones"></div>' +
|
||||
' </div>' +
|
||||
' <div class="dragitems"></div>' +
|
||||
'</div>');
|
||||
|
||||
dragDropToImageForm.updateVisibilityOfFilePickers();
|
||||
dragDropToImageForm.setOptionsForDragItemSelectors();
|
||||
dragDropToImageForm.setupEventHandlers();
|
||||
dragDropToImageForm.waitForFilePickerToInitialise();
|
||||
},
|
||||
|
||||
/**
|
||||
* Waits for the file-pickers to be sufficiently ready before initialising the preview.
|
||||
*/
|
||||
waitForFilePickerToInitialise: function() {
|
||||
if (dragDropToImageForm.fp.file('bgimage').href === null) {
|
||||
// It would be better to use an onload or onchange event rather than this timeout.
|
||||
// Unfortunately attempts to do this early are overwritten by filepicker during its loading.
|
||||
setTimeout(dragDropToImageForm.waitForFilePickerToInitialise, 1000);
|
||||
return;
|
||||
}
|
||||
M.util.js_pending('dragDropToImageForm');
|
||||
|
||||
// From now on, when a new file gets loaded into the filepicker, update the preview.
|
||||
// This is not in the setupEventHandlers section as it needs to be delayed until
|
||||
// after filepicker's javascript has finished.
|
||||
$('form.mform').on('change', '.filepickerhidden', function() {
|
||||
M.util.js_pending('dragDropToImageForm');
|
||||
dragDropToImageForm.loadPreviewImage();
|
||||
});
|
||||
|
||||
dragDropToImageForm.loadPreviewImage();
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads the preview background image.
|
||||
*/
|
||||
loadPreviewImage: function() {
|
||||
$('fieldset#id_previewareaheader .dropbackground')
|
||||
.one('load', dragDropToImageForm.afterPreviewImageLoaded)
|
||||
.attr('src', dragDropToImageForm.fp.file('bgimage').href);
|
||||
},
|
||||
|
||||
/**
|
||||
* After the background image is loaded, continue setting up the preview.
|
||||
*/
|
||||
afterPreviewImageLoaded: function() {
|
||||
var bgImg = $('fieldset#id_previewareaheader .dropbackground');
|
||||
dragDropToImageForm.constrainImageSize(bgImg, dragDropToImageForm.maxBgImageSize);
|
||||
dragDropToImageForm.createDropZones();
|
||||
M.util.js_complete('dragDropToImageForm');
|
||||
},
|
||||
|
||||
/**
|
||||
* Limits an image display size to the given maximums.
|
||||
*
|
||||
* @param {jQuery} img the image.
|
||||
* @param {Object} maxSize with width and height properties.
|
||||
*/
|
||||
constrainImageSize: function(img, maxSize) {
|
||||
var reduceby = Math.max(img.width() / maxSize.width,
|
||||
img.height() / maxSize.height);
|
||||
if (reduceby > 1) {
|
||||
img.css('width', Math.floor(img.width() / reduceby));
|
||||
}
|
||||
img.addClass('constrained');
|
||||
},
|
||||
|
||||
/**
|
||||
* Create, or recreate all the drop zones.
|
||||
*/
|
||||
createDropZones: function() {
|
||||
var dropZoneHolder = $('.dropzones');
|
||||
dropZoneHolder.empty();
|
||||
|
||||
var bgimageurl = dragDropToImageForm.fp.file('bgimage').href;
|
||||
if (bgimageurl === null) {
|
||||
return; // There is not currently a valid preview to update.
|
||||
}
|
||||
|
||||
var numDrops = dragDropToImageForm.form.getFormValue('nodropzone', []);
|
||||
for (var dropNo = 0; dropNo < numDrops; dropNo++) {
|
||||
var dragNo = dragDropToImageForm.form.getFormValue('drops', [dropNo, 'choice']);
|
||||
if (dragNo === '0') {
|
||||
continue;
|
||||
}
|
||||
dragNo = dragNo - 1;
|
||||
var group = dragDropToImageForm.form.getFormValue('drags', [dragNo, 'draggroup']),
|
||||
label = dragDropToImageForm.form.getFormValue('draglabel', [dragNo]);
|
||||
if ('image' === dragDropToImageForm.form.getFormValue('drags', [dragNo, 'dragitemtype'])) {
|
||||
var imgUrl = dragDropToImageForm.fp.file('dragitem[' + dragNo + ']').href;
|
||||
if (imgUrl === null) {
|
||||
continue;
|
||||
}
|
||||
// Althoug these are previews of drops, we also add the class name 'drag',
|
||||
dropZoneHolder.append('<img class="droppreview group' + group + ' drop' + dropNo +
|
||||
'" src="' + imgUrl + '" alt="' + label + '" data-drop-no="' + dropNo + '">');
|
||||
|
||||
} else if (label !== '') {
|
||||
dropZoneHolder.append('<div class="droppreview group' + group + ' drop' + dropNo +
|
||||
'" data-drop-no="' + dropNo + '">' + label + '</div>');
|
||||
}
|
||||
}
|
||||
|
||||
dragDropToImageForm.waitForAllDropImagesToBeLoaded();
|
||||
},
|
||||
|
||||
/**
|
||||
* This polls until all the drop-zone images have loaded, and then calls updateDropZones().
|
||||
*/
|
||||
waitForAllDropImagesToBeLoaded: function() {
|
||||
var notYetLoadedImages = $('.dropzones img').not(function(i, imgNode) {
|
||||
return dragDropToImageForm.imageIsLoaded(imgNode);
|
||||
});
|
||||
|
||||
if (notYetLoadedImages.length > 0) {
|
||||
setTimeout(function() {
|
||||
dragDropToImageForm.waitForAllDropImagesToBeLoaded();
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
dragDropToImageForm.updateDropZones();
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if an image has loaded without errors.
|
||||
*
|
||||
* @param {HTMLImageElement} imgElement an image.
|
||||
* @returns {boolean} true if this image has loaded without errors.
|
||||
*/
|
||||
imageIsLoaded: function(imgElement) {
|
||||
return imgElement.complete && imgElement.naturalHeight !== 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the size and position of all the drop zones.
|
||||
*/
|
||||
updateDropZones: function() {
|
||||
var bgimageurl = dragDropToImageForm.fp.file('bgimage').href;
|
||||
if (bgimageurl === null) {
|
||||
return; // There is not currently a valid preview to update.
|
||||
}
|
||||
|
||||
var dropBackgroundPosition = $('fieldset#id_previewareaheader .dropbackground').offset(),
|
||||
numDrops = dragDropToImageForm.form.getFormValue('nodropzone', []);
|
||||
|
||||
// Move each drop to the right position and update the text.
|
||||
for (var dropNo = 0; dropNo < numDrops; dropNo++) {
|
||||
var drop = $('.dropzones .drop' + dropNo);
|
||||
if (drop.length === 0) {
|
||||
continue;
|
||||
}
|
||||
var dragNo = dragDropToImageForm.form.getFormValue('drops', [dropNo, 'choice']) - 1;
|
||||
|
||||
drop.offset({
|
||||
left: dropBackgroundPosition.left +
|
||||
parseInt(dragDropToImageForm.form.getFormValue('drops', [dropNo, 'xleft'])),
|
||||
top: dropBackgroundPosition.top +
|
||||
parseInt(dragDropToImageForm.form.getFormValue('drops', [dropNo, 'ytop']))
|
||||
});
|
||||
|
||||
var label = dragDropToImageForm.form.getFormValue('draglabel', [dragNo]);
|
||||
if (drop.is('img')) {
|
||||
drop.attr('alt', label);
|
||||
} else {
|
||||
drop.html(label);
|
||||
}
|
||||
}
|
||||
|
||||
// Resize them to the same size.
|
||||
$('.dropzones .droppreview').css('padding', '0');
|
||||
var numGroups = $('select.draggroup').first().find('option').length;
|
||||
for (var group = 1; group <= numGroups; group++) {
|
||||
dragDropToImageForm.resizeAllDragsAndDropsInGroup(group);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* In a given group, set all the drags and drops to be the same size.
|
||||
*
|
||||
* @param {int} group the group number.
|
||||
*/
|
||||
resizeAllDragsAndDropsInGroup: function(group) {
|
||||
var drops = $('.dropzones .droppreview.group' + group),
|
||||
maxWidth = 0,
|
||||
maxHeight = 0;
|
||||
|
||||
// Find the maximum size of any drag in this groups.
|
||||
drops.each(function(i, drop) {
|
||||
maxWidth = Math.max(maxWidth, Math.ceil(drop.offsetWidth));
|
||||
maxHeight = Math.max(maxHeight, Math.ceil(drop.offsetHeight));
|
||||
});
|
||||
|
||||
// The size we will want to set is a bit bigger than this.
|
||||
maxWidth += 10;
|
||||
maxHeight += 10;
|
||||
|
||||
// Set each drag home to that size.
|
||||
drops.each(function(i, drop) {
|
||||
var left = Math.round((maxWidth - drop.offsetWidth) / 2),
|
||||
top = Math.floor((maxHeight - drop.offsetHeight) / 2);
|
||||
// Set top and left padding so the item is centred.
|
||||
$(drop).css({
|
||||
'padding-left': left + 'px',
|
||||
'padding-right': (maxWidth - drop.offsetWidth - left) + 'px',
|
||||
'padding-top': top + 'px',
|
||||
'padding-bottom': (maxHeight - drop.offsetHeight - top) + 'px'
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Events linked to form actions.
|
||||
*/
|
||||
setupEventHandlers: function() {
|
||||
// Changes to settings in the draggable items section.
|
||||
$('fieldset#id_draggableitemheader')
|
||||
.on('change input', 'input, select', function(e) {
|
||||
var input = $(e.target).closest('select, input');
|
||||
if (input.hasClass('dragitemtype')) {
|
||||
dragDropToImageForm.updateVisibilityOfFilePickers();
|
||||
}
|
||||
|
||||
dragDropToImageForm.setOptionsForDragItemSelectors();
|
||||
|
||||
if (input.is('.dragitemtype, .draggroup')) {
|
||||
dragDropToImageForm.createDropZones();
|
||||
} else if (input.is('.draglabel')) {
|
||||
dragDropToImageForm.updateDropZones();
|
||||
}
|
||||
});
|
||||
|
||||
// Changes to Drop zones section: left, top and drag item.
|
||||
$('fieldset#id_dropzoneheader').on('change input', 'input, select', function(e) {
|
||||
var input = $(e.target).closest('select, input');
|
||||
if (input.is('select')) {
|
||||
dragDropToImageForm.createDropZones();
|
||||
} else {
|
||||
dragDropToImageForm.updateDropZones();
|
||||
}
|
||||
});
|
||||
|
||||
// Moving drop zones in the preview.
|
||||
$('fieldset#id_previewareaheader').on('mousedown touchstart', '.droppreview', function(e) {
|
||||
dragDropToImageForm.dragStart(e);
|
||||
});
|
||||
|
||||
$(window).on('resize', function() {
|
||||
dragDropToImageForm.updateDropZones();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Update all the drag item filepickers, so they are only shown for
|
||||
*/
|
||||
updateVisibilityOfFilePickers: function() {
|
||||
var numDrags = dragDropToImageForm.form.getFormValue('noitems', []);
|
||||
for (var dragNo = 0; dragNo < numDrags; dragNo++) {
|
||||
var picker = $('input#id_dragitem_' + dragNo).closest('.fitem_ffilepicker');
|
||||
if ('image' === dragDropToImageForm.form.getFormValue('drags', [dragNo, 'dragitemtype'])) {
|
||||
picker.show();
|
||||
} else {
|
||||
picker.hide();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
setOptionsForDragItemSelectors: function() {
|
||||
var dragItemOptions = {'0': ''},
|
||||
numDrags = dragDropToImageForm.form.getFormValue('noitems', []),
|
||||
numDrops = dragDropToImageForm.form.getFormValue('nodropzone', []);
|
||||
|
||||
// Work out the list of options.
|
||||
for (var dragNo = 0; dragNo < numDrags; dragNo++) {
|
||||
var label = dragDropToImageForm.form.getFormValue('draglabel', [dragNo]);
|
||||
var file = dragDropToImageForm.fp.file(dragDropToImageForm.form.toNameWithIndex('dragitem', [dragNo]));
|
||||
if ('image' === dragDropToImageForm.form.getFormValue('drags', [dragNo, 'dragitemtype']) && file.name !== null) {
|
||||
dragItemOptions[dragNo + 1] = (dragNo + 1) + '. ' + label + ' (' + file.name + ')';
|
||||
} else if (label !== '') {
|
||||
dragItemOptions[dragNo + 1] = (dragNo + 1) + '. ' + label;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialise each select.
|
||||
for (var dropNo = 0; dropNo < numDrops; dropNo++) {
|
||||
var selector = $('#id_drops_' + dropNo + '_choice');
|
||||
|
||||
var selectedvalue = selector.val();
|
||||
selector.find('option').remove();
|
||||
for (var value in dragItemOptions) {
|
||||
if (!dragItemOptions.hasOwnProperty(value)) {
|
||||
continue;
|
||||
}
|
||||
selector.append('<option value="' + value + '">' + dragItemOptions[value] + '</option>');
|
||||
var optionnode = selector.find('option[value="' + value + '"]');
|
||||
if (parseInt(value) === parseInt(selectedvalue)) {
|
||||
optionnode.attr('selected', true);
|
||||
} else if (dragDropToImageForm.isItemUsed(parseInt(value))) {
|
||||
optionnode.attr('disabled', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the specified drag option is already used somewhere.
|
||||
*
|
||||
* @param {Number} value of the drag item to check
|
||||
* @return {Boolean} true if item is allocated to dropzone
|
||||
*/
|
||||
isItemUsed: function(value) {
|
||||
if (value === 0) {
|
||||
return false; // None option can always be selected.
|
||||
}
|
||||
|
||||
if (dragDropToImageForm.form.getFormValue('drags', [value - 1, 'infinite'])) {
|
||||
return false; // Infinite, so can't be used up.
|
||||
}
|
||||
|
||||
return $('fieldset#id_dropzoneheader select').filter(function(i, selectNode) {
|
||||
return parseInt($(selectNode).val()) === value;
|
||||
}).length !== 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles when a dropzone in dragged in the preview.
|
||||
* @param {Object} e Event object
|
||||
*/
|
||||
dragStart: function(e) {
|
||||
var drop = $(e.target).closest('.droppreview');
|
||||
|
||||
var info = dragDrop.prepare(e);
|
||||
if (!info.start) {
|
||||
return;
|
||||
}
|
||||
|
||||
dragDrop.start(e, drop, function(x, y, drop) {
|
||||
dragDropToImageForm.dragMove(drop);
|
||||
}, function() {
|
||||
dragDropToImageForm.dragEnd();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles update while a drop is being dragged.
|
||||
*
|
||||
* @param {jQuery} drop the drop preview being moved.
|
||||
*/
|
||||
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);
|
||||
|
||||
// Constrain coordinates to be inside the background.
|
||||
// The -10 here matches the +10 in resizeAllDragsAndDropsInGroup().
|
||||
left = Math.max(0, Math.min(left, backgroundImage.width() - drop.width() - 10));
|
||||
top = Math.max(0, Math.min(top, backgroundImage.height() - drop.height() - 10));
|
||||
|
||||
// Update the form.
|
||||
dragDropToImageForm.form.setFormValue('drops', [dropNo, 'xleft'], left);
|
||||
dragDropToImageForm.form.setFormValue('drops', [dropNo, 'ytop'], top);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles when the drag ends.
|
||||
*/
|
||||
dragEnd: function() {
|
||||
// Redraw, in case the position was constrained.
|
||||
dragDropToImageForm.updateDropZones();
|
||||
},
|
||||
|
||||
/**
|
||||
* Low level operations on form.
|
||||
*/
|
||||
form: {
|
||||
toNameWithIndex: function(name, indexes) {
|
||||
var indexString = name;
|
||||
for (var i = 0; i < indexes.length; i++) {
|
||||
indexString = indexString + '[' + indexes[i] + ']';
|
||||
}
|
||||
return indexString;
|
||||
},
|
||||
|
||||
getEl: function(name, indexes) {
|
||||
var form = document.getElementById('mform1');
|
||||
return form.elements[this.toNameWithIndex(name, indexes)];
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper to get the value of a form elements with name like "drops[0][xleft]".
|
||||
*
|
||||
* @param {String} name the base name, e.g. 'drops'.
|
||||
* @param {String[]} indexes the indexes, e.g. ['0', 'xleft'].
|
||||
* @return {String} the value of that field.
|
||||
*/
|
||||
getFormValue: function(name, indexes) {
|
||||
var el = this.getEl(name, indexes);
|
||||
if (!el.type) {
|
||||
el = el[el.length - 1];
|
||||
}
|
||||
if (el.type === 'checkbox') {
|
||||
return el.checked;
|
||||
} else {
|
||||
return el.value;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper to get the value of a form elements with name like "drops[0][xleft]".
|
||||
*
|
||||
* @param {String} name the base name, e.g. 'drops'.
|
||||
* @param {String[]} indexes the indexes, e.g. ['0', 'xleft'].
|
||||
* @param {String|Number} value the value to set.
|
||||
*/
|
||||
setFormValue: function(name, indexes, value) {
|
||||
var el = this.getEl(name, indexes);
|
||||
if (el.type === 'checkbox') {
|
||||
el.checked = value;
|
||||
} else {
|
||||
el.value = value;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Utility to get the file name and url from the filepicker.
|
||||
* @returns {Object} object containing functions {file, name}
|
||||
*/
|
||||
filePickers: function() {
|
||||
var draftItemIdsToName;
|
||||
var nameToParentNode;
|
||||
|
||||
if (draftItemIdsToName === undefined) {
|
||||
draftItemIdsToName = {};
|
||||
nameToParentNode = {};
|
||||
var fp = $('form.mform input.filepickerhidden');
|
||||
fp.each(function(index, filepicker) {
|
||||
draftItemIdsToName[filepicker.value] = filepicker.name;
|
||||
nameToParentNode[filepicker.name] = filepicker.parentNode;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
file: function(name) {
|
||||
var parentNode = $(nameToParentNode[name]);
|
||||
var fileAnchor = parentNode.find('div.filepicker-filelist a');
|
||||
if (fileAnchor.length) {
|
||||
return {href: fileAnchor.get(0).href, name: fileAnchor.get(0).innerHTML};
|
||||
} else {
|
||||
return {href: null, name: null};
|
||||
}
|
||||
},
|
||||
|
||||
name: function(draftitemid) {
|
||||
return draftItemIdsToName[draftitemid];
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @alias module:qtype_ddimageortext/form
|
||||
*/
|
||||
return {
|
||||
/**
|
||||
* Initialise the form JavaScript features.
|
||||
*
|
||||
* @param {Object} maxBgImageSize object with two properties: width and height.
|
||||
* @param {Object} maxDragImageSize object with two properties: width and height.
|
||||
*/
|
||||
init: dragDropToImageForm.init
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,760 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/*
|
||||
* JavaScript to allow dragging options to slots (using mouse down or touch) or tab through slots using keyboard.
|
||||
*
|
||||
* @module qtype_ddimageortext/question
|
||||
* @package qtype_ddimageortext
|
||||
* @copyright 2018 The Open University
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
define(['jquery', 'core/dragdrop', 'core/key_codes'], function($, dragDrop, keys) {
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Initialise one drag-drop onto image question.
|
||||
*
|
||||
* @param {String} containerId id of the outer div for this question.
|
||||
* @param {boolean} readOnly whether the question is being displayed read-only.
|
||||
* @param {Array} places Information about the drop places.
|
||||
* @constructor
|
||||
*/
|
||||
function DragDropOntoImageQuestion(containerId, readOnly, places) {
|
||||
this.containerId = containerId;
|
||||
M.util.js_pending('qtype_ddimageortext-init-' + this.containerId);
|
||||
this.places = places;
|
||||
this.allImagesLoaded = false;
|
||||
this.imageLoadingTimeoutId = null;
|
||||
if (readOnly) {
|
||||
this.getRoot().addClass('qtype_ddimageortext-readonly');
|
||||
}
|
||||
|
||||
var thisQ = this;
|
||||
this.getNotYetLoadedImages().one('load', function() {
|
||||
thisQ.waitForAllImagesToBeLoaded();
|
||||
});
|
||||
this.waitForAllImagesToBeLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits until all images are loaded before calling setupQuestion().
|
||||
*
|
||||
* This function is called from the onLoad of each image, and also polls with
|
||||
* a time-out, because image on-loads are allegedly unreliable.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.waitForAllImagesToBeLoaded = function() {
|
||||
var thisQ = this;
|
||||
|
||||
// This method may get called multiple times (via image on-loads or timeouts.
|
||||
// If we are already done, don't do it again.
|
||||
if (this.allImagesLoaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear any current timeout, if set.
|
||||
if (this.imageLoadingTimeoutId !== null) {
|
||||
clearTimeout(this.imageLoadingTimeoutId);
|
||||
}
|
||||
|
||||
// If we have not yet loaded all images, set a timeout to
|
||||
// call ourselves again, since apparently images on-load
|
||||
// events are flakey.
|
||||
if (this.getNotYetLoadedImages().length > 0) {
|
||||
this.imageLoadingTimeoutId = setTimeout(function() {
|
||||
thisQ.waitForAllImagesToBeLoaded();
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// We now have all images. Carry on, but only after giving the layout a chance to settle down.
|
||||
this.allImagesLoaded = true;
|
||||
thisQ.setupQuestion();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get any of the images in the drag-drop area that are not yet fully loaded.
|
||||
*
|
||||
* @returns {jQuery} those images.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.getNotYetLoadedImages = function() {
|
||||
var thisQ = this;
|
||||
return this.getRoot().find('.ddarea img').not(function(i, imgNode) {
|
||||
return thisQ.imageIsLoaded(imgNode);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if an image has loaded without errors.
|
||||
*
|
||||
* @param {HTMLImageElement} imgElement an image.
|
||||
* @returns {boolean} true if this image has loaded without errors.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.imageIsLoaded = function(imgElement) {
|
||||
return imgElement.complete && imgElement.naturalHeight !== 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set up the question, once all images have been loaded.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.setupQuestion = function() {
|
||||
this.resizeAllDragsAndDrops();
|
||||
this.cloneDrags();
|
||||
this.positionDragsAndDrops();
|
||||
M.util.js_complete('qtype_ddimageortext-init-' + this.containerId);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* In each group, resize all the items to be the same size.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.resizeAllDragsAndDrops = function() {
|
||||
var thisQ = this;
|
||||
this.getRoot().find('.draghomes > div').each(function(i) {
|
||||
thisQ.resizeAllDragsAndDropsInGroup(i + 1);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* In a given group, set all the drags and drops to be the same size.
|
||||
*
|
||||
* @param {int} group the group number.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.resizeAllDragsAndDropsInGroup = function(group) {
|
||||
var root = this.getRoot(),
|
||||
dragHomes = root.find('.dragitemgroup' + group + ' .draghome'),
|
||||
maxWidth = 0,
|
||||
maxHeight = 0;
|
||||
|
||||
// Find the maximum size of any drag in this groups.
|
||||
dragHomes.each(function(i, drag) {
|
||||
maxWidth = Math.max(maxWidth, Math.ceil(drag.offsetWidth));
|
||||
maxHeight = Math.max(maxHeight, Math.ceil(drag.offsetHeight));
|
||||
});
|
||||
|
||||
// The size we will want to set is a bit bigger than this.
|
||||
maxWidth += 10;
|
||||
maxHeight += 10;
|
||||
|
||||
// Set each drag home to that size.
|
||||
dragHomes.each(function(i, drag) {
|
||||
var left = Math.round((maxWidth - drag.offsetWidth) / 2),
|
||||
top = Math.floor((maxHeight - drag.offsetHeight) / 2);
|
||||
// Set top and left padding so the item is centred.
|
||||
$(drag).css({
|
||||
'padding-left': left + 'px',
|
||||
'padding-right': (maxWidth - drag.offsetWidth - left) + 'px',
|
||||
'padding-top': top + 'px',
|
||||
'padding-bottom': (maxHeight - drag.offsetHeight - top) + 'px'
|
||||
});
|
||||
});
|
||||
|
||||
// Create the drops and make them the right size.
|
||||
for (var i in this.places) {
|
||||
if (!this.places.hasOwnProperty((i))) {
|
||||
continue;
|
||||
}
|
||||
var place = this.places[i],
|
||||
label = place.text;
|
||||
if (parseInt(place.group) !== group) {
|
||||
continue;
|
||||
}
|
||||
if (label === '') {
|
||||
label = M.util.get_string('blank', 'qtype_ddimageortext');
|
||||
}
|
||||
root.find('.dropzones').append('<div class="dropzone group' + place.group +
|
||||
' place' + i + '" tabindex="0">' +
|
||||
'<span class="accesshide">' + label + '</span> </div>');
|
||||
root.find('.dropzone.place' + i).width(maxWidth - 2).height(maxHeight - 2);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Invisible 'drag homes' are output by the renderer. These have the same properties
|
||||
* as the drag items but are invisible. We clone these invisible elements to make the
|
||||
* actual drag items.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.cloneDrags = function() {
|
||||
var thisQ = this;
|
||||
this.getRoot().find('.ddarea .draghome').each(function(index, dragHome) {
|
||||
thisQ.cloneDragsForOneChoice($(dragHome));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Clone drag item for one choice.
|
||||
*
|
||||
* @param {jQuery} dragHome the drag home to clone.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.cloneDragsForOneChoice = function(dragHome) {
|
||||
if (dragHome.hasClass('infinite')) {
|
||||
var noOfDrags = this.noOfDropsInGroup(this.getGroup(dragHome));
|
||||
for (var i = 0; i < noOfDrags; i++) {
|
||||
this.cloneDrag(dragHome);
|
||||
}
|
||||
} else {
|
||||
this.cloneDrag(dragHome);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clone drag item.
|
||||
*
|
||||
* @param {jQuery} dragHome
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.cloneDrag = function(dragHome) {
|
||||
var drag = dragHome.clone();
|
||||
drag.removeClass('draghome')
|
||||
.addClass('drag unplaced moodle-has-zindex')
|
||||
.offset(dragHome.offset());
|
||||
this.getRoot().find('.dragitems').append(drag);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the position of drags.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.positionDragsAndDrops = function() {
|
||||
var thisQ = this,
|
||||
root = this.getRoot(),
|
||||
bgPosition = this.bgImage().offset();
|
||||
|
||||
// Move the drops into position.
|
||||
root.find('.ddarea .dropzone').each(function(i, dropNode) {
|
||||
var drop = $(dropNode),
|
||||
place = thisQ.places[thisQ.getPlace(drop)];
|
||||
// The xy values come from PHP as strings, so we need parseInt to stop JS doing string concatenation.
|
||||
drop.offset({
|
||||
left: bgPosition.left + parseInt(place.xy[0]),
|
||||
top: bgPosition.top + parseInt(place.xy[1])});
|
||||
});
|
||||
|
||||
// First move all items back home.
|
||||
root.find('.ddarea .drag').each(function(i, dragNode) {
|
||||
var drag = $(dragNode),
|
||||
currentPlace = thisQ.getClassnameNumericSuffix(drag, 'inplace');
|
||||
drag.addClass('unplaced')
|
||||
.removeClass('placed')
|
||||
.offset(thisQ.getDragHome(thisQ.getGroup(drag), thisQ.getChoice(drag)).offset());
|
||||
if (currentPlace !== null) {
|
||||
drag.removeClass('inplace' + currentPlace);
|
||||
}
|
||||
});
|
||||
|
||||
// Then place the ones that should be placed.
|
||||
root.find('input.placeinput').each(function(i, inputNode) {
|
||||
var input = $(inputNode),
|
||||
choice = input.val();
|
||||
if (choice === '0') {
|
||||
// No item in this place.
|
||||
return;
|
||||
}
|
||||
|
||||
var place = thisQ.getPlace(input);
|
||||
thisQ.getUnplacedChoice(thisQ.getGroup(input), choice)
|
||||
.removeClass('unplaced')
|
||||
.addClass('placed inplace' + place)
|
||||
.offset(root.find('.dropzone.place' + place).offset());
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the start of dragging an item.
|
||||
*
|
||||
* @param {Event} e the touch start or mouse down event.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.handleDragStart = function(e) {
|
||||
var thisQ = this,
|
||||
drag = $(e.target).closest('.drag');
|
||||
|
||||
var info = dragDrop.prepare(e);
|
||||
if (!info.start) {
|
||||
return;
|
||||
}
|
||||
|
||||
var currentPlace = this.getClassnameNumericSuffix(drag, 'inplace');
|
||||
if (currentPlace !== null) {
|
||||
this.setInputValue(currentPlace, 0);
|
||||
drag.removeClass('inplace' + currentPlace);
|
||||
}
|
||||
|
||||
drag.addClass('beingdragged');
|
||||
dragDrop.start(e, drag, function(x, y, drag) {
|
||||
thisQ.dragMove(x, y, drag);
|
||||
}, function(x, y, drag) {
|
||||
thisQ.dragEnd(x, y, drag);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Called whenever the currently dragged items moves.
|
||||
*
|
||||
* @param {Number} pageX the x position.
|
||||
* @param {Number} pageY the y position.
|
||||
* @param {jQuery} drag the item being moved.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.dragMove = function(pageX, pageY, drag) {
|
||||
var thisQ = this;
|
||||
this.getRoot().find('.dropzone.group' + this.getGroup(drag)).each(function(i, dropNode) {
|
||||
var drop = $(dropNode);
|
||||
if (thisQ.isPointInDrop(pageX, pageY, drop)) {
|
||||
drop.addClass('valid-drag-over-drop');
|
||||
} else {
|
||||
drop.removeClass('valid-drag-over-drop');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when user drops a drag item.
|
||||
*
|
||||
* @param {Number} pageX the x position.
|
||||
* @param {Number} pageY the y position.
|
||||
* @param {jQuery} drag the item being moved.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.dragEnd = function(pageX, pageY, drag) {
|
||||
var thisQ = this,
|
||||
root = this.getRoot(),
|
||||
placed = false;
|
||||
root.find('.dropzone.group' + this.getGroup(drag)).each(function(i, dropNode) {
|
||||
var drop = $(dropNode);
|
||||
if (!thisQ.isPointInDrop(pageX, pageY, drop)) {
|
||||
// Not this drop.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Now put this drag into the drop.
|
||||
drop.removeClass('valid-drag-over-drop');
|
||||
thisQ.sendDragToDrop(drag, drop);
|
||||
placed = true;
|
||||
return false; // Stop the each() here.
|
||||
});
|
||||
|
||||
if (!placed) {
|
||||
this.sendDragHome(drag);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Animate a drag item into a given place (or back home).
|
||||
*
|
||||
* @param {jQuery|null} drag the item to place. If null, clear the place.
|
||||
* @param {jQuery} drop the place to put it.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.sendDragToDrop = function(drag, drop) {
|
||||
// Is there already a drag in this drop? if so, evict it.
|
||||
var oldDrag = this.getCurrentDragInPlace(this.getPlace(drop));
|
||||
if (oldDrag.length !== 0) {
|
||||
this.sendDragHome(oldDrag);
|
||||
}
|
||||
|
||||
if (drag.length === 0) {
|
||||
this.setInputValue(this.getPlace(drop), 0);
|
||||
} else {
|
||||
this.setInputValue(this.getPlace(drop), this.getChoice(drag));
|
||||
drag.removeClass('unplaced')
|
||||
.addClass('placed inplace' + this.getPlace(drop));
|
||||
this.animateTo(drag, drop);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Animate a drag back to its home.
|
||||
*
|
||||
* @param {jQuery} drag the item being moved.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.sendDragHome = function(drag) {
|
||||
drag.removeClass('placed').addClass('unplaced');
|
||||
var currentPlace = this.getClassnameNumericSuffix(drag, 'inplace');
|
||||
if (currentPlace !== null) {
|
||||
drag.removeClass('inplace' + currentPlace);
|
||||
}
|
||||
|
||||
this.animateTo(drag, this.getDragHome(this.getGroup(drag), this.getChoice(drag)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles keyboard events on drops.
|
||||
*
|
||||
* Drops are focusable. Once focused, right/down/space switches to the next choice, and
|
||||
* left/up switches to the previous. Escape clear.
|
||||
*
|
||||
* @param {KeyboardEvent} e
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.handleKeyPress = function(e) {
|
||||
var drop = $(e.target).closest('.dropzone'),
|
||||
currentDrag = this.getCurrentDragInPlace(this.getPlace(drop)),
|
||||
nextDrag = $();
|
||||
|
||||
switch (e.keyCode) {
|
||||
case keys.space:
|
||||
case keys.arrowRight:
|
||||
case keys.arrowDown:
|
||||
nextDrag = this.getNextDrag(this.getGroup(drop), currentDrag);
|
||||
break;
|
||||
|
||||
case keys.arrowLeft:
|
||||
case keys.arrowUp:
|
||||
nextDrag = this.getPreviousDrag(this.getGroup(drop), currentDrag);
|
||||
break;
|
||||
|
||||
case keys.escape:
|
||||
break;
|
||||
|
||||
default:
|
||||
return; // To avoid the preventDefault below.
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
this.sendDragToDrop(nextDrag, drop);
|
||||
};
|
||||
|
||||
/**
|
||||
* Choose the next drag in a group.
|
||||
*
|
||||
* @param {int} group which group.
|
||||
* @param {jQuery} drag current choice (empty jQuery if there isn't one).
|
||||
* @return {jQuery} the next drag in that group, or null if there wasn't one.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.getNextDrag = function(group, drag) {
|
||||
var choice,
|
||||
numChoices = this.noOfChoicesInGroup(group);
|
||||
|
||||
if (drag.length === 0) {
|
||||
choice = 1; // Was empty, so we want to select the first choice.
|
||||
} else {
|
||||
choice = this.getChoice(drag) + 1;
|
||||
}
|
||||
|
||||
var next = this.getUnplacedChoice(group, choice);
|
||||
while (next.length === 0 && choice < numChoices) {
|
||||
choice++;
|
||||
next = this.getUnplacedChoice(group, choice);
|
||||
}
|
||||
|
||||
return next;
|
||||
};
|
||||
|
||||
/**
|
||||
* Choose the previous drag in a group.
|
||||
*
|
||||
* @param {int} group which group.
|
||||
* @param {jQuery} drag current choice (empty jQuery if there isn't one).
|
||||
* @return {jQuery} the next drag in that group, or null if there wasn't one.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.getPreviousDrag = function(group, drag) {
|
||||
var choice;
|
||||
|
||||
if (drag.length === 0) {
|
||||
choice = this.noOfChoicesInGroup(group);
|
||||
} else {
|
||||
choice = this.getChoice(drag) - 1;
|
||||
}
|
||||
|
||||
var previous = this.getUnplacedChoice(group, choice);
|
||||
while (previous.length === 0 && choice > 1) {
|
||||
choice--;
|
||||
previous = this.getUnplacedChoice(group, choice);
|
||||
}
|
||||
|
||||
// Does this choice exist?
|
||||
return previous;
|
||||
};
|
||||
|
||||
/**
|
||||
* Animate an object to the given destination.
|
||||
*
|
||||
* @param {jQuery} drag the element to be animated.
|
||||
* @param {jQuery} target element marking the place to move it to.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.animateTo = function(drag, target) {
|
||||
var currentPos = drag.offset(),
|
||||
targetPos = target.offset();
|
||||
drag.addClass('beingdragged');
|
||||
|
||||
// Animate works in terms of CSS position, whereas locating an object
|
||||
// on the page works best with jQuery offset() function. So, to get
|
||||
// the right target position, we work out the required change in
|
||||
// offset() and then add that to the current CSS position.
|
||||
drag.animate(
|
||||
{
|
||||
left: parseInt(drag.css('left')) + targetPos.left - currentPos.left,
|
||||
top: parseInt(drag.css('top')) + targetPos.top - currentPos.top
|
||||
},
|
||||
{
|
||||
duration: 'fast',
|
||||
done: function() {
|
||||
drag.removeClass('beingdragged');
|
||||
// It seems that the animation sometimes leaves the drag
|
||||
// one pixel out of position. Put it in exactly the right place.
|
||||
drag.offset(targetPos);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect if a point is inside a given DOM node.
|
||||
*
|
||||
* @param {Number} pageX the x position.
|
||||
* @param {Number} pageY the y position.
|
||||
* @param {jQuery} drop the node to check (typically a drop).
|
||||
* @return {boolean} whether the point is inside the node.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.isPointInDrop = function(pageX, pageY, drop) {
|
||||
var position = drop.offset();
|
||||
return pageX >= position.left && pageX < position.left + drop.width()
|
||||
&& pageY >= position.top && pageY < position.top + drop.height();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the value of the hidden input for a place, to record what is currently there.
|
||||
*
|
||||
* @param {int} place which place to set the input value for.
|
||||
* @param {int} choice the value to set.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.setInputValue = function(place, choice) {
|
||||
this.getRoot().find('input.placeinput.place' + place).val(choice);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the outer div for this question.
|
||||
*
|
||||
* @returns {jQuery} containing that div.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.getRoot = function() {
|
||||
return $(document.getElementById(this.containerId));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the img that is the background image.
|
||||
* @returns {jQuery} containing that img.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.bgImage = function() {
|
||||
return this.getRoot().find('img.dropbackground');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get drag home for a given choice.
|
||||
*
|
||||
* @param {int} group the group.
|
||||
* @param {int} choice the choice number.
|
||||
* @returns {jQuery} containing that div.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.getDragHome = function(group, choice) {
|
||||
return this.getRoot().find('.ddarea .draghome.group' + group + '.choice' + choice);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get an unplaced choice for a particular group.
|
||||
*
|
||||
* @param {int} group the group.
|
||||
* @param {int} choice the choice number.
|
||||
* @returns {jQuery} jQuery wrapping the unplaced choice. If there isn't one, the jQuery will be empty.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.getUnplacedChoice = function(group, choice) {
|
||||
return this.getRoot().find('.ddarea .drag.group' + group + '.choice' + choice + '.unplaced').slice(0, 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the drag that is currently in a given place.
|
||||
*
|
||||
* @param {int} place the place number.
|
||||
* @return {jQuery} the current drag (or an empty jQuery if none).
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.getCurrentDragInPlace = function(place) {
|
||||
return this.getRoot().find('.ddarea .drag.inplace' + place);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the number of blanks in a given group.
|
||||
*
|
||||
* @param {int} group the group number.
|
||||
* @returns {int} the number of drops.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.noOfDropsInGroup = function(group) {
|
||||
return this.getRoot().find('.dropzone.group' + group).length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the number of choices in a given group.
|
||||
*
|
||||
* @param {int} group the group number.
|
||||
* @returns {int} the number of choices.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.noOfChoicesInGroup = function(group) {
|
||||
return this.getRoot().find('.dragitemgroup' + group + ' .draghome').length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the number at the end of the CSS class name with the given prefix.
|
||||
*
|
||||
* @param {jQuery} node
|
||||
* @param {String} prefix name prefix
|
||||
* @returns {Number|null} the suffix if found, else null.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.getClassnameNumericSuffix = function(node, prefix) {
|
||||
var classes = node.attr('class');
|
||||
if (classes !== '') {
|
||||
var classesArr = classes.split(' ');
|
||||
for (var index = 0; index < classesArr.length; index++) {
|
||||
var patt1 = new RegExp('^' + prefix + '([0-9])+$');
|
||||
if (patt1.test(classesArr[index])) {
|
||||
var patt2 = new RegExp('([0-9])+$');
|
||||
var match = patt2.exec(classesArr[index]);
|
||||
return Number(match[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the choice number of a drag.
|
||||
*
|
||||
* @param {jQuery} drag the drag.
|
||||
* @returns {Number} the choice number.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.getChoice = function(drag) {
|
||||
return this.getClassnameNumericSuffix(drag, 'choice');
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a DOM node that is significant to this question
|
||||
* (drag, drop, ...) get the group it belongs to.
|
||||
*
|
||||
* @param {jQuery} node a DOM node.
|
||||
* @returns {Number} the group it belongs to.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.getGroup = function(node) {
|
||||
return this.getClassnameNumericSuffix(node, 'group');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the place number of a drop, or its corresponding hidden input.
|
||||
*
|
||||
* @param {jQuery} node the DOM node.
|
||||
* @returns {Number} the place number.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.getPlace = function(node) {
|
||||
return this.getClassnameNumericSuffix(node, 'place');
|
||||
};
|
||||
|
||||
/**
|
||||
* Singleton object that handles all the DragDropOntoImageQuestions
|
||||
* on the page, and deals with event dispatching.
|
||||
* @type {Object}
|
||||
*/
|
||||
var questionManager = {
|
||||
|
||||
/**
|
||||
* {boolean} ensures that the event handlers are only initialised once per page.
|
||||
*/
|
||||
eventHandlersInitialised: false,
|
||||
|
||||
/**
|
||||
* {Object} all the questions on this page, indexed by containerId (id on the .que div).
|
||||
*/
|
||||
questions: {}, // An object containing all the information about each question on the page.
|
||||
|
||||
/**
|
||||
* Initialise one question.
|
||||
*
|
||||
* @param {String} containerId the id of the div.que that contains this question.
|
||||
* @param {boolean} readOnly whether the question is read-only.
|
||||
* @param {Array} places data.
|
||||
*/
|
||||
init: function(containerId, readOnly, places) {
|
||||
questionManager.questions[containerId] =
|
||||
new DragDropOntoImageQuestion(containerId, readOnly, places);
|
||||
if (!questionManager.eventHandlersInitialised) {
|
||||
questionManager.setupEventHandlers();
|
||||
questionManager.eventHandlersInitialised = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set up the event handlers that make this question type work. (Done once per page.)
|
||||
*/
|
||||
setupEventHandlers: function() {
|
||||
$('body')
|
||||
.on('mousedown touchstart',
|
||||
'.que.ddimageortext:not(.qtype_ddimageortext-readonly) .dragitems .drag',
|
||||
questionManager.handleDragStart)
|
||||
.on('keydown',
|
||||
'.que.ddimageortext:not(.qtype_ddimageortext-readonly) .dropzones .dropzone',
|
||||
questionManager.handleKeyPress);
|
||||
$(window).on('resize', questionManager.handleWindowResize);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle mouse down / touch start events on drags.
|
||||
* @param {Event} e the DOM event.
|
||||
*/
|
||||
handleDragStart: function(e) {
|
||||
e.preventDefault();
|
||||
var question = questionManager.getQuestionForEvent(e);
|
||||
if (question) {
|
||||
question.handleDragStart(e);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle key down / press events on drags.
|
||||
* @param {KeyboardEvent} e
|
||||
*/
|
||||
handleKeyPress: function(e) {
|
||||
var question = questionManager.getQuestionForEvent(e);
|
||||
if (question) {
|
||||
question.handleKeyPress(e);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle when the window is resized.
|
||||
*/
|
||||
handleWindowResize: function() {
|
||||
for (var containerId in questionManager.questions) {
|
||||
if (questionManager.questions.hasOwnProperty(containerId)) {
|
||||
questionManager.questions[containerId].positionDragsAndDrops();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Given an event, work out which question it effects.
|
||||
* @param {Event} e the event.
|
||||
* @returns {DragDropOntoImageQuestion|undefined} The question, or undefined.
|
||||
*/
|
||||
getQuestionForEvent: function(e) {
|
||||
var containerId = $(e.currentTarget).closest('.que.ddimageortext').attr('id');
|
||||
return questionManager.questions[containerId];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @alias module:qtype_ddimageortext/question
|
||||
*/
|
||||
return {
|
||||
/**
|
||||
* Initialise one drag-drop onto image question.
|
||||
*
|
||||
* @param {String} containerId id of the outer div for this question.
|
||||
* @param {boolean} readOnly whether the question is being displayed read-only.
|
||||
* @param {Array} Information about the drop places.
|
||||
*/
|
||||
init: questionManager.init
|
||||
};
|
||||
});
|
||||
@@ -108,20 +108,17 @@ class qtype_ddimageortext_edit_form extends qtype_ddtoimage_edit_form_base {
|
||||
|
||||
public function js_call() {
|
||||
global $PAGE;
|
||||
$maxsizes = new stdClass();
|
||||
$maxsizes->bgimage = new stdClass();
|
||||
$maxsizes->bgimage->width = QTYPE_DDIMAGEORTEXT_BGIMAGE_MAXWIDTH;
|
||||
$maxsizes->bgimage->height = QTYPE_DDIMAGEORTEXT_BGIMAGE_MAXHEIGHT;
|
||||
$maxsizes->dragimage = new stdClass();
|
||||
$maxsizes->dragimage->width = QTYPE_DDIMAGEORTEXT_DRAGIMAGE_MAXWIDTH;
|
||||
$maxsizes->dragimage->height = QTYPE_DDIMAGEORTEXT_DRAGIMAGE_MAXHEIGHT;
|
||||
|
||||
$params = array('maxsizes' => $maxsizes,
|
||||
'topnode' => 'fieldset#id_previewareaheader');
|
||||
|
||||
$PAGE->requires->yui_module('moodle-qtype_ddimageortext-form',
|
||||
'M.qtype_ddimageortext.init_form',
|
||||
array($params));
|
||||
$maxbgimagesize = [
|
||||
'width' => QTYPE_DDIMAGEORTEXT_BGIMAGE_MAXWIDTH,
|
||||
'height' => QTYPE_DDIMAGEORTEXT_BGIMAGE_MAXHEIGHT
|
||||
];
|
||||
$maxdragimagesize = [
|
||||
'width' => QTYPE_DDIMAGEORTEXT_DRAGIMAGE_MAXWIDTH,
|
||||
'height' => QTYPE_DDIMAGEORTEXT_DRAGIMAGE_MAXHEIGHT
|
||||
];
|
||||
$PAGE->requires->js_call_amd('qtype_ddimageortext/form', 'init',
|
||||
[$maxbgimagesize, $maxdragimagesize]);
|
||||
}
|
||||
|
||||
// Drag items.
|
||||
@@ -166,7 +163,7 @@ class qtype_ddimageortext_edit_form extends qtype_ddtoimage_edit_form_base {
|
||||
|
||||
$draggableimageitem[] = $mform->createElement('text', 'draglabel',
|
||||
get_string('label', 'qtype_ddimageortext'),
|
||||
array('size' => 30, 'class' => 'tweakcss'));
|
||||
array('size' => 30, 'class' => 'tweakcss draglabel'));
|
||||
$mform->setType('draglabel', PARAM_RAW); // These are validated manually.
|
||||
return $draggableimageitem;
|
||||
}
|
||||
|
||||
@@ -66,8 +66,9 @@ class qtype_ddtoimage_renderer_base extends qtype_with_combined_feedback_rendere
|
||||
$img = html_writer::empty_tag('img', array(
|
||||
'src' => $bgimage, 'class' => 'dropbackground',
|
||||
'alt' => get_string('dropbackground', 'qtype_ddimageortext')));
|
||||
$dropzones = html_writer::tag('div', '', array('class' => 'dropzones'));
|
||||
|
||||
$droparea = html_writer::tag('div', $img, array('class' => 'droparea'));
|
||||
$droparea = html_writer::tag('div', $img . $dropzones, array('class' => 'droparea'));
|
||||
|
||||
$dragimagehomes = '';
|
||||
foreach ($question->choices as $groupno => $group) {
|
||||
@@ -77,13 +78,11 @@ class qtype_ddtoimage_renderer_base extends qtype_with_combined_feedback_rendere
|
||||
$dragimageurl = self::get_url_for_image($qa, 'dragimage', $dragimage->id);
|
||||
$classes = array("group{$groupno}",
|
||||
'draghome',
|
||||
"dragitemhomes{$dragimage->no}",
|
||||
"choice{$choiceno}");
|
||||
if ($dragimage->infinite) {
|
||||
$classes[] = 'infinite';
|
||||
}
|
||||
if ($dragimageurl === null) {
|
||||
$classes[] = 'yui3-cssfonts';
|
||||
$dragimagehomesgroup .= html_writer::tag('div', $dragimage->text,
|
||||
array('src' => $dragimageurl, 'class' => join(' ', $classes)));
|
||||
} else {
|
||||
@@ -96,31 +95,27 @@ class qtype_ddtoimage_renderer_base extends qtype_with_combined_feedback_rendere
|
||||
array('class' => 'dragitemgroup' . $groupno));
|
||||
}
|
||||
|
||||
$draghomes = html_writer::tag('div', $dragimagehomes, array('class' => 'draghomes'));
|
||||
$dragitemsclass = 'dragitems';
|
||||
if ($options->readonly) {
|
||||
$dragitemsclass .= ' readonly';
|
||||
}
|
||||
$dragitems = html_writer::tag('div', $dragimagehomes, array('class' => $dragitemsclass));
|
||||
$dropzones = html_writer::tag('div', '', array('class' => 'dropzones'));
|
||||
$dragitems = html_writer::tag('div', '', array('class' => $dragitemsclass));
|
||||
|
||||
$hiddens = '';
|
||||
foreach ($question->places as $placeno => $place) {
|
||||
$varname = $question->field($placeno);
|
||||
list($fieldname, $html) = $this->hidden_field_for_qt_var($qa, $varname);
|
||||
list($fieldname, $html) = $this->hidden_field_for_qt_var($qa, $varname, null,
|
||||
['placeinput', 'place' . $placeno, 'group' . $place->group]);
|
||||
$hiddens .= $html;
|
||||
$question->places[$placeno]->fieldname = $fieldname;
|
||||
}
|
||||
$output .= html_writer::tag('div',
|
||||
$droparea . $dragitems . $dropzones . $hiddens, array('class' => 'ddarea'));
|
||||
$topnode = 'div#q'.$qa->get_slot().' div.ddarea';
|
||||
$params = array('drops' => $question->places,
|
||||
'topnode' => $topnode,
|
||||
'readonly' => $options->readonly);
|
||||
$droparea . $draghomes. $dragitems . $hiddens, array('class' => 'ddarea'));
|
||||
|
||||
$PAGE->requires->string_for_js('blank', 'qtype_ddimageortext');
|
||||
$PAGE->requires->yui_module('moodle-qtype_ddimageortext-dd',
|
||||
'M.qtype_ddimageortext.init_question',
|
||||
array($params));
|
||||
$PAGE->requires->js_call_amd('qtype_ddimageortext/question', 'init',
|
||||
['q' . $qa->get_slot(), $options->readonly, $question->places]);
|
||||
|
||||
if ($qa->get_state() == question_state::$invalid) {
|
||||
$output .= html_writer::nonempty_tag('div',
|
||||
|
||||
@@ -3,28 +3,73 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.que.ddimageortext div.droparea img,
|
||||
form.mform fieldset#id_previewareaheader div.droparea img {
|
||||
.que.ddimageortext div.ddarea,
|
||||
form.mform fieldset#id_previewareaheader div.ddarea {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.que.ddimageortext div.droparea,
|
||||
form.mform fieldset#id_previewareaheader div.ddarea {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.que.ddimageortext .dropbackground,
|
||||
form.mform fieldset#id_previewareaheader .dropbackground {
|
||||
border: 1px solid #000;
|
||||
max-width: none;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.que.ddimageortext .dropzone {
|
||||
position: absolute;
|
||||
opacity: 0.5;
|
||||
border: 1px solid black;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.que.ddimageortext .dropzone:focus,
|
||||
.que.ddimageortext .dropzone.valid-drag-over-drop {
|
||||
border-color: #0a0;
|
||||
box-shadow: 0 0 5px 5px rgba(255, 255, 150, 1);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.que.ddimageortext .draghome,
|
||||
form.mform fieldset#id_previewareaheader .draghome {
|
||||
.que.ddimageortext .drag,
|
||||
form.mform fieldset#id_previewareaheader .droppreview {
|
||||
border: 1px solid black;
|
||||
display: inline-block;
|
||||
font: 13px/1.231 arial, helvetica, clean, sans-serif;
|
||||
}
|
||||
|
||||
.que.ddimageortext .draghome {
|
||||
vertical-align: top;
|
||||
margin: 5px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.que.ddimageortext div.draghome,
|
||||
form.mform fieldset#id_previewareaheader div.draghome {
|
||||
border: 1px solid black;
|
||||
cursor: move;
|
||||
background-color: #b0c4de;
|
||||
display: inline-block;
|
||||
height: auto;
|
||||
width: auto;
|
||||
zoom: 1;
|
||||
}
|
||||
|
||||
.que.ddimageortext .dragitems,
|
||||
form.mform fieldset#id_previewareaheader .dragitems {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.que.ddimageortext .drag,
|
||||
form.mform fieldset#id_previewareaheader .droppreview {
|
||||
position: absolute;
|
||||
cursor: move;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.que.ddimageortext .dragitems.readonly .drag {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
form.mform fieldset#id_previewareaheader .drag.beingdragged,
|
||||
.que.ddimageortext .drag.beingdragged {
|
||||
z-index: 3;
|
||||
box-shadow: 3px 3px 4px #000;
|
||||
}
|
||||
|
||||
.que.ddimageortext .group1,
|
||||
@@ -67,51 +112,6 @@ form.mform fieldset#id_previewareaheader .group8 {
|
||||
background-color: #f0e68c;
|
||||
}
|
||||
|
||||
.que.ddimageortext .drag,
|
||||
form.mform fieldset#id_previewareaheader .drag {
|
||||
border: 1px solid black;
|
||||
cursor: move;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.que.ddimageortext .dragitems.readonly .drag {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.que.ddimageortext div.ddarea,
|
||||
form.mform fieldset#id_previewareaheader div.ddarea {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.que.ddimageortext .dropbackground,
|
||||
form.mform fieldset#id_previewareaheader .dropbackground {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.que.ddimageortext .dropzone {
|
||||
border: 1px solid black;
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.que.ddimageortext .dropzone:focus,
|
||||
.que.ddimageortext .dropzone.yui3-dd-drop-over.yui3-dd-drop-active-valid {
|
||||
border-color: #0a0;
|
||||
box-shadow: 0 0 5px 5px rgba(255, 255, 150, 1);
|
||||
}
|
||||
|
||||
.que.ddimageortext div.dragitems div.draghome,
|
||||
.que.ddimageortext div.dragitems div.drag,
|
||||
form.mform fieldset#id_previewareaheader div.draghome,
|
||||
form.mform fieldset#id_previewareaheader div.drag {
|
||||
font: 13px/1.231 arial, helvetica, clean, sans-serif;
|
||||
}
|
||||
|
||||
form.mform fieldset#id_previewareaheader div.drag.yui3-dd-dragging,
|
||||
.que.ddimageortext div.drag.yui3-dd-dragging {
|
||||
z-index: 3;
|
||||
box-shadow: 3px 3px 4px #000;
|
||||
}
|
||||
/* Editing form. Style repeated elements*/
|
||||
/*Top*/
|
||||
body#page-question-type-ddimageortext div[id^=fgroup_id_][id*=drags_] {
|
||||
|
||||
@@ -41,7 +41,8 @@ class behat_qtype_ddimageortext extends behat_base {
|
||||
* @return string the xpath expression.
|
||||
*/
|
||||
protected function drag_xpath($dragitem) {
|
||||
return '//div[contains(@class, " drag ") and contains(normalize-space(.), "' . $this->escape($dragitem) . '")]';
|
||||
return '//div[contains(concat(" ", @class, " "), " drag ") and ' .
|
||||
'contains(normalize-space(.), "' . $this->escape($dragitem) . '")]';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -50,7 +51,8 @@ class behat_qtype_ddimageortext extends behat_base {
|
||||
* @return string the xpath expression.
|
||||
*/
|
||||
protected function drop_xpath($placenumber) {
|
||||
return '//div[contains(@class, "dropzone ") and contains(@class, "place' . $placenumber . ' ")]';
|
||||
return '//div[contains(concat(" ", @class, " "), " dropzone ") and ' .
|
||||
'contains(concat(" ", @class, " "), " place' . $placenumber . ' ")]';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,7 +30,7 @@ Feature: Preview a drag-drop onto image question
|
||||
And I switch to "questionpreview" window
|
||||
# Increase window size and wait 2 seconds to ensure elements are placed properly by js.
|
||||
# Keep window large else drag will scroll the window to find element.
|
||||
And I change window size to "large"
|
||||
And I change window size to "medium"
|
||||
And I wait "2" seconds
|
||||
# Odd, but the <br>s go to nothing, not a space.
|
||||
And I drag "mountainbelt" to place "1" in the drag and drop onto image question
|
||||
@@ -51,7 +51,7 @@ Feature: Preview a drag-drop onto image question
|
||||
When I click on "Preview" "link" in the "Drag onto image" "table_row"
|
||||
And I switch to "questionpreview" window
|
||||
# Increase window size and wait 2 seconds to ensure elements are placed properly by js.
|
||||
And I change window size to "large"
|
||||
And I change window size to "medium"
|
||||
And I wait "2" seconds
|
||||
And I type " " on place "1" in the drag and drop onto image question
|
||||
And I type " " on place "2" in the drag and drop onto image question
|
||||
|
||||
@@ -46,8 +46,7 @@ class qtype_ddimageortext_walkthrough_test extends qbehaviour_walkthrough_test_b
|
||||
* @return question_contains_tag_with_attributes the required expectation.
|
||||
*/
|
||||
protected function get_contains_drag_image_home_expectation($dragitemno, $choice, $group) {
|
||||
$class = 'group' . $group;
|
||||
$class .= ' draghome dragitemhomes' . $dragitemno. ' choice'.$choice.' yui3-cssfonts';
|
||||
$class = 'group' . $group . ' draghome choice' . $choice;
|
||||
|
||||
$expectedattrs = array();
|
||||
$expectedattrs['class'] = $class;
|
||||
|
||||
-546
@@ -1,546 +0,0 @@
|
||||
YUI.add('moodle-qtype_ddimageortext-dd', function (Y, NAME) {
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
var DDIMAGEORTEXTDDNAME = 'ddimageortext_dd';
|
||||
var DDIMAGEORTEXT_DD = function() {
|
||||
DDIMAGEORTEXT_DD.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the base class for the question rendering and question editing form code.
|
||||
*/
|
||||
Y.extend(DDIMAGEORTEXT_DD, Y.Base, {
|
||||
doc: null,
|
||||
polltimer: null,
|
||||
afterimageloaddone: false,
|
||||
poll_for_image_load: function(e, waitforimageconstrain, pause, doafterwords) {
|
||||
if (this.afterimageloaddone) {
|
||||
return;
|
||||
}
|
||||
var bgdone = this.doc.bg_img().get('complete');
|
||||
if (waitforimageconstrain) {
|
||||
bgdone = bgdone && this.doc.bg_img().hasClass('constrained');
|
||||
}
|
||||
var alldragsloaded = !this.doc.drag_item_homes().some(function(dragitemhome) {
|
||||
// in 'some' loop returning true breaks the loop and is passed as return value from
|
||||
// 'some' else returns false. Can be though of as equivalent to ||.
|
||||
if (dragitemhome.get('tagName') !== 'IMG') {
|
||||
return false;
|
||||
}
|
||||
var done = (dragitemhome.get('complete'));
|
||||
if (waitforimageconstrain) {
|
||||
done = done && dragitemhome.hasClass('constrained');
|
||||
}
|
||||
return !done;
|
||||
});
|
||||
if (bgdone && alldragsloaded) {
|
||||
if (this.polltimer !== null) {
|
||||
this.polltimer.cancel();
|
||||
this.polltimer = null;
|
||||
}
|
||||
this.doc.drag_item_homes().detach('load', this.poll_for_image_load);
|
||||
this.doc.bg_img().detach('load', this.poll_for_image_load);
|
||||
if (pause !== 0) {
|
||||
Y.later(pause, this, doafterwords);
|
||||
} else {
|
||||
doafterwords.call(this);
|
||||
}
|
||||
this.afterimageloaddone = true;
|
||||
} else if (this.polltimer === null) {
|
||||
var pollarguments = [null, waitforimageconstrain, pause, doafterwords];
|
||||
this.polltimer =
|
||||
Y.later(1000, this, this.poll_for_image_load, pollarguments, true);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Object to encapsulate operations on dd area.
|
||||
*/
|
||||
doc_structure: function(mainobj) {
|
||||
var topnode = Y.one(this.get('topnode'));
|
||||
var dragitemsarea = topnode.one('div.dragitems');
|
||||
var dropbgarea = topnode.one('div.droparea');
|
||||
return {
|
||||
top_node: function() {
|
||||
return topnode;
|
||||
},
|
||||
drag_items: function() {
|
||||
return dragitemsarea.all('.drag');
|
||||
},
|
||||
drop_zones: function() {
|
||||
return topnode.all('div.dropzones div.dropzone');
|
||||
},
|
||||
drop_zone_group: function(groupno) {
|
||||
return topnode.all('div.dropzones div.group' + groupno);
|
||||
},
|
||||
drag_items_cloned_from: function(dragitemno) {
|
||||
return dragitemsarea.all('.dragitems' + dragitemno);
|
||||
},
|
||||
drag_item: function(draginstanceno) {
|
||||
return dragitemsarea.one('.draginstance' + draginstanceno);
|
||||
},
|
||||
drag_items_in_group: function(groupno) {
|
||||
return dragitemsarea.all('.drag.group' + groupno);
|
||||
},
|
||||
drag_item_homes: function() {
|
||||
return dragitemsarea.all('.draghome');
|
||||
},
|
||||
bg_img: function() {
|
||||
return topnode.one('.dropbackground');
|
||||
},
|
||||
load_bg_img: function(url) {
|
||||
dropbgarea.setContent('<img class="dropbackground" src="' + url + '"/>');
|
||||
this.bg_img().on('load', this.on_image_load, this, 'bg_image');
|
||||
},
|
||||
add_or_update_drag_item_home: function(dragitemno, url, alt, group) {
|
||||
var oldhome = this.drag_item_home(dragitemno);
|
||||
var classes = 'draghome dragitemhomes' + dragitemno + ' group' + group;
|
||||
var imghtml = '<img class="' + classes + '" src="' + url + '" alt="' + alt + '" />';
|
||||
var divhtml = '<div class="' + classes + '">' + alt + '</div>';
|
||||
if (oldhome === null) {
|
||||
if (url) {
|
||||
dragitemsarea.append(imghtml);
|
||||
} else if (alt !== '') {
|
||||
dragitemsarea.append(divhtml);
|
||||
}
|
||||
} else {
|
||||
if (url) {
|
||||
dragitemsarea.insert(imghtml, oldhome);
|
||||
} else if (alt !== '') {
|
||||
dragitemsarea.insert(divhtml, oldhome);
|
||||
}
|
||||
oldhome.remove(true);
|
||||
}
|
||||
var newlycreated = dragitemsarea.one('.dragitemhomes' + dragitemno);
|
||||
if (newlycreated !== null) {
|
||||
newlycreated.setData('groupno', group);
|
||||
newlycreated.setData('dragitemno', dragitemno);
|
||||
}
|
||||
},
|
||||
drag_item_home: function(dragitemno) {
|
||||
return dragitemsarea.one('.dragitemhomes' + dragitemno);
|
||||
},
|
||||
get_classname_numeric_suffix: function(node, prefix) {
|
||||
var classes = node.getAttribute('class');
|
||||
if (classes !== '') {
|
||||
var classesarr = classes.split(' ');
|
||||
for (var index = 0; index < classesarr.length; index++) {
|
||||
var patt1 = new RegExp('^' + prefix + '([0-9])+$');
|
||||
if (patt1.test(classesarr[index])) {
|
||||
var patt2 = new RegExp('([0-9])+$');
|
||||
var match = patt2.exec(classesarr[index]);
|
||||
return +match[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
throw 'Prefix "' + prefix + '" not found in class names.';
|
||||
},
|
||||
clone_new_drag_item: function(draginstanceno, dragitemno) {
|
||||
var draghome = this.drag_item_home(dragitemno);
|
||||
if (draghome === null) {
|
||||
return null;
|
||||
}
|
||||
var drag = draghome.cloneNode(true);
|
||||
drag.removeClass('dragitemhomes' + dragitemno);
|
||||
drag.addClass('dragitems' + dragitemno);
|
||||
drag.addClass('draginstance' + draginstanceno);
|
||||
drag.removeClass('draghome');
|
||||
drag.addClass('drag');
|
||||
drag.setStyles({'visibility': 'visible', 'position': 'absolute'});
|
||||
drag.setData('draginstanceno', draginstanceno);
|
||||
drag.setData('dragitemno', dragitemno);
|
||||
draghome.get('parentNode').appendChild(drag);
|
||||
return drag;
|
||||
},
|
||||
draggable_for_question: function(drag, group, choice) {
|
||||
new Y.DD.Drag({
|
||||
node: drag,
|
||||
dragMode: 'point',
|
||||
groups: [group]
|
||||
}).plug(Y.Plugin.DDConstrained, {constrain2node: topnode});
|
||||
|
||||
drag.setData('group', group);
|
||||
drag.setData('choice', choice);
|
||||
},
|
||||
draggable_for_form: function(drag) {
|
||||
var dd = new Y.DD.Drag({
|
||||
node: drag,
|
||||
dragMode: 'point'
|
||||
}).plug(Y.Plugin.DDConstrained, {constrain2node: topnode});
|
||||
dd.on('drag:end', function(e) {
|
||||
var dragnode = e.target.get('node');
|
||||
var draginstanceno = dragnode.getData('draginstanceno');
|
||||
var gooddrop = dragnode.getData('gooddrop');
|
||||
|
||||
if (!gooddrop) {
|
||||
mainobj.reset_drag_xy(draginstanceno);
|
||||
} else {
|
||||
mainobj.set_drag_xy(draginstanceno, [e.pageX, e.pageY]);
|
||||
}
|
||||
}, this);
|
||||
dd.on('drag:start', function(e) {
|
||||
var drag = e.target;
|
||||
drag.get('node').setData('gooddrop', false);
|
||||
}, this);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
},
|
||||
|
||||
update_padding_sizes_all: function() {
|
||||
for (var groupno = 1; groupno <= 8; groupno++) {
|
||||
this.update_padding_size_for_group(groupno);
|
||||
}
|
||||
},
|
||||
update_padding_size_for_group: function(groupno) {
|
||||
var groupitems = this.doc.top_node().all('.draghome.group' + groupno);
|
||||
if (groupitems.size() !== 0) {
|
||||
var maxwidth = 0;
|
||||
var maxheight = 0;
|
||||
groupitems.each(function(item) {
|
||||
maxwidth = Math.max(maxwidth, item.get('clientWidth'));
|
||||
maxheight = Math.max(maxheight, item.get('clientHeight'));
|
||||
}, this);
|
||||
groupitems.each(function(item) {
|
||||
var margintopbottom = Math.round((10 + maxheight - item.get('clientHeight')) / 2);
|
||||
var marginleftright = Math.round((10 + maxwidth - item.get('clientWidth')) / 2);
|
||||
item.setStyle('padding', margintopbottom + 'px ' + marginleftright + 'px ' +
|
||||
margintopbottom + 'px ' + marginleftright + 'px');
|
||||
}, this);
|
||||
this.doc.drop_zone_group(groupno).setStyles({'width': maxwidth + 10,
|
||||
'height': maxheight + 10});
|
||||
}
|
||||
},
|
||||
convert_to_window_xy: function(bgimgxy) {
|
||||
return [Number(bgimgxy[0]) + this.doc.bg_img().getX() + 1,
|
||||
Number(bgimgxy[1]) + this.doc.bg_img().getY() + 1];
|
||||
}
|
||||
}, {
|
||||
NAME: DDIMAGEORTEXTDDNAME,
|
||||
ATTRS: {
|
||||
drops: {value: null},
|
||||
readonly: {value: false},
|
||||
topnode: {value: null}
|
||||
}
|
||||
});
|
||||
|
||||
M.qtype_ddimageortext = M.qtype_ddimageortext || {};
|
||||
M.qtype_ddimageortext.dd_base_class = DDIMAGEORTEXT_DD;
|
||||
|
||||
var DDIMAGEORTEXTQUESTIONNAME = 'ddimageortext_question';
|
||||
var DDIMAGEORTEXT_QUESTION = function() {
|
||||
DDIMAGEORTEXT_QUESTION.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
/**
|
||||
* This is the code for question rendering.
|
||||
*/
|
||||
Y.extend(DDIMAGEORTEXT_QUESTION, M.qtype_ddimageortext.dd_base_class, {
|
||||
passiveSupported: false,
|
||||
pendingid: '',
|
||||
initializer: function() {
|
||||
this.pendingid = 'qtype_ddimageortext-' + Math.random().toString(36).slice(2); // Random string.
|
||||
M.util.js_pending(this.pendingid);
|
||||
this.doc = this.doc_structure(this);
|
||||
this.poll_for_image_load(null, false, 10, this.create_all_drag_and_drops);
|
||||
this.doc.bg_img().after('load', this.poll_for_image_load, this,
|
||||
false, 10, this.create_all_drag_and_drops);
|
||||
this.doc.drag_item_homes().after('load', this.poll_for_image_load, this,
|
||||
false, 10, this.create_all_drag_and_drops);
|
||||
if (!this.get('readonly')) {
|
||||
Y.later(500, this, this.reposition_drags_for_question, true);
|
||||
} else {
|
||||
Y.one('window').on('resize', function() {
|
||||
this.reposition_drags_for_question();
|
||||
}, this);
|
||||
}
|
||||
this.checkPassiveSupported();
|
||||
},
|
||||
|
||||
/**
|
||||
* prevent_touchmove_from_scrolling allows users of touch screen devices to
|
||||
* use drag and drop and normal scrolling at the same time. I.e. when
|
||||
* touching and dragging a draggable item, the screen does not scroll, but
|
||||
* you can scroll by touching other area of the screen apart from the
|
||||
* draggable items.
|
||||
*/
|
||||
prevent_touchmove_from_scrolling: function(drag) {
|
||||
var touchmove = (Y.UA.ie) ? 'MSPointerMove' : 'touchmove';
|
||||
var eventHandler = function(event) {
|
||||
event.preventDefault();
|
||||
};
|
||||
var dragId = drag.get('id');
|
||||
var el = document.getElementById(dragId);
|
||||
// Note do not dynamically add events within another event, as this causes issues on iOS11.3.
|
||||
// See https://github.com/atlassian/react-beautiful-dnd/issues/413 and
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=184250 for fuller explanation.
|
||||
el.addEventListener(touchmove, eventHandler, this.passiveSupported ? {passive: false, capture: true} : false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Some older browsers do not support passing an options object to addEventListener.
|
||||
* This is a check from https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener.
|
||||
*/
|
||||
checkPassiveSupported: function() {
|
||||
try {
|
||||
var options = Object.defineProperty({}, 'passive', {
|
||||
get: function() {
|
||||
this.passiveSupported = true;
|
||||
}.bind(this)
|
||||
});
|
||||
window.addEventListener('test', options, options);
|
||||
window.removeEventListener('test', options, options);
|
||||
} catch (err) {
|
||||
this.passiveSupported = false;
|
||||
}
|
||||
},
|
||||
|
||||
create_all_drag_and_drops: function() {
|
||||
this.init_drops();
|
||||
this.update_padding_sizes_all();
|
||||
var i = 0;
|
||||
this.doc.drag_item_homes().each(function(dragitemhome) {
|
||||
var dragitemno = Number(this.doc.get_classname_numeric_suffix(dragitemhome, 'dragitemhomes'));
|
||||
var choice = +this.doc.get_classname_numeric_suffix(dragitemhome, 'choice');
|
||||
var group = +this.doc.get_classname_numeric_suffix(dragitemhome, 'group');
|
||||
var groupsize = this.doc.drop_zone_group(group).size();
|
||||
var dragnode = this.doc.clone_new_drag_item(i, dragitemno);
|
||||
i++;
|
||||
if (!this.get('readonly')) {
|
||||
this.doc.draggable_for_question(dragnode, group, choice);
|
||||
|
||||
// Prevent scrolling whilst dragging on Adroid devices.
|
||||
this.prevent_touchmove_from_scrolling(dragnode);
|
||||
}
|
||||
if (dragnode.hasClass('infinite')) {
|
||||
var dragstocreate = groupsize - 1;
|
||||
while (dragstocreate > 0) {
|
||||
dragnode = this.doc.clone_new_drag_item(i, dragitemno);
|
||||
i++;
|
||||
if (!this.get('readonly')) {
|
||||
this.doc.draggable_for_question(dragnode, group, choice);
|
||||
|
||||
// Prevent scrolling whilst dragging on Adroid devices.
|
||||
this.prevent_touchmove_from_scrolling(dragnode);
|
||||
}
|
||||
dragstocreate--;
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
this.reposition_drags_for_question();
|
||||
if (!this.get('readonly')) {
|
||||
this.doc.drop_zones().set('tabIndex', 0);
|
||||
this.doc.drop_zones().each(
|
||||
function(v) {
|
||||
v.on('dragchange', this.drop_zone_key_press, this);
|
||||
}, this);
|
||||
}
|
||||
M.util.js_complete(this.pendingid);
|
||||
},
|
||||
drop_zone_key_press: function(e) {
|
||||
switch (e.direction) {
|
||||
case 'next' :
|
||||
this.place_next_drag_in(e.target);
|
||||
break;
|
||||
case 'previous' :
|
||||
this.place_previous_drag_in(e.target);
|
||||
break;
|
||||
case 'remove' :
|
||||
this.remove_drag_from_drop(e.target);
|
||||
break;
|
||||
}
|
||||
e.preventDefault();
|
||||
this.reposition_drags_for_question();
|
||||
},
|
||||
place_next_drag_in: function(drop) {
|
||||
this.search_for_unplaced_drop_choice(drop, 1);
|
||||
},
|
||||
place_previous_drag_in: function(drop) {
|
||||
this.search_for_unplaced_drop_choice(drop, -1);
|
||||
},
|
||||
search_for_unplaced_drop_choice: function(drop, direction) {
|
||||
var next;
|
||||
var current = this.current_drag_in_drop(drop);
|
||||
if ('' === current) {
|
||||
if (direction === 1) {
|
||||
next = 1;
|
||||
} else {
|
||||
next = 1;
|
||||
var groupno = drop.getData('group');
|
||||
this.doc.drag_items_in_group(groupno).each(function(drag) {
|
||||
next = Math.max(next, drag.getData('choice'));
|
||||
}, this);
|
||||
}
|
||||
} else {
|
||||
next = +current + direction;
|
||||
}
|
||||
var drag;
|
||||
do {
|
||||
if (this.get_choices_for_drop(next, drop).size() === 0) {
|
||||
this.remove_drag_from_drop(drop);
|
||||
return;
|
||||
} else {
|
||||
drag = this.get_unplaced_choice_for_drop(next, drop);
|
||||
}
|
||||
next = next + direction;
|
||||
} while (drag === null);
|
||||
this.place_drag_in_drop(drag, drop);
|
||||
},
|
||||
current_drag_in_drop: function(drop) {
|
||||
var inputid = drop.getData('inputid');
|
||||
var inputnode = Y.one('input#' + inputid);
|
||||
return inputnode.get('value');
|
||||
},
|
||||
remove_drag_from_drop: function(drop) {
|
||||
this.place_drag_in_drop(null, drop);
|
||||
},
|
||||
place_drag_in_drop: function(drag, drop) {
|
||||
var inputid = drop.getData('inputid');
|
||||
var inputnode = Y.one('input#' + inputid);
|
||||
if (drag !== null) {
|
||||
inputnode.set('value', drag.getData('choice'));
|
||||
} else {
|
||||
inputnode.set('value', '');
|
||||
}
|
||||
},
|
||||
reposition_drags_for_question: function(dotimeout) {
|
||||
this.doc.drag_items().removeClass('placed');
|
||||
this.doc.drag_items().each(function(dragitem) {
|
||||
if (dragitem.dd !== undefined) {
|
||||
dragitem.dd.detachAll('drag:start');
|
||||
}
|
||||
}, this);
|
||||
this.doc.drop_zones().each(function(dropzone) {
|
||||
var relativexy = dropzone.getData('xy');
|
||||
dropzone.setXY(this.convert_to_window_xy(relativexy));
|
||||
var inputcss = 'input#' + dropzone.getData('inputid');
|
||||
var input = this.doc.top_node().one(inputcss);
|
||||
var choice = input.get('value');
|
||||
if (choice !== "") {
|
||||
var dragitem = this.get_unplaced_choice_for_drop(choice, dropzone);
|
||||
if (dragitem !== null) {
|
||||
dragitem.setXY(dropzone.getXY());
|
||||
dragitem.addClass('placed');
|
||||
if (dragitem.dd !== undefined) {
|
||||
dragitem.dd.once('drag:start', function(e, input) {
|
||||
input.set('value', '');
|
||||
e.target.get('node').removeClass('placed');
|
||||
}, this, input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
this.doc.drag_items().each(function(dragitem) {
|
||||
if (!dragitem.hasClass('placed') && !dragitem.hasClass('yui3-dd-dragging')) {
|
||||
var dragitemhome = this.doc.drag_item_home(dragitem.getData('dragitemno'));
|
||||
dragitem.setXY(dragitemhome.getXY());
|
||||
}
|
||||
}, this);
|
||||
if (dotimeout) {
|
||||
Y.later(500, this, this.reposition_drags_for_question, true);
|
||||
}
|
||||
},
|
||||
get_choices_for_drop: function(choice, drop) {
|
||||
var group = drop.getData('group');
|
||||
return this.doc.top_node().all(
|
||||
'div.dragitemgroup' + group + ' .choice' + choice + '.drag');
|
||||
},
|
||||
get_unplaced_choice_for_drop: function(choice, drop) {
|
||||
var dragitems = this.get_choices_for_drop(choice, drop);
|
||||
var dragitem = null;
|
||||
dragitems.some(function(d) {
|
||||
if (this.get('readonly') || (!d.hasClass('placed') && !d.hasClass('yui3-dd-dragging'))) {
|
||||
dragitem = d;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return dragitem;
|
||||
},
|
||||
init_drops: function() {
|
||||
var dropareas = this.doc.top_node().one('div.dropzones');
|
||||
var groupnodes = {};
|
||||
for (var groupno = 1; groupno <= 8; groupno++) {
|
||||
var groupnode = Y.Node.create('<div class = "dropzonegroup' + groupno + '"></div>');
|
||||
dropareas.append(groupnode);
|
||||
groupnodes[groupno] = groupnode;
|
||||
}
|
||||
var drop_hit_handler = function(e) {
|
||||
var drag = e.drag.get('node');
|
||||
var drop = e.drop.get('node');
|
||||
if (Number(drop.getData('group')) === drag.getData('group')) {
|
||||
this.place_drag_in_drop(drag, drop);
|
||||
}
|
||||
};
|
||||
for (var dropno in this.get('drops')) {
|
||||
var drop = this.get('drops')[dropno];
|
||||
var nodeclass = 'dropzone group' + drop.group + ' place' + dropno;
|
||||
var title = drop.text.replace('"', '\"');
|
||||
if (!title) {
|
||||
title = M.util.get_string('blank', 'qtype_ddimageortext');
|
||||
}
|
||||
var dropnodehtml = '<div title="' + title + '" class="' + nodeclass + '">' +
|
||||
'<span class="accesshide">' + title + '</span> </div>';
|
||||
var dropnode = Y.Node.create(dropnodehtml);
|
||||
groupnodes[drop.group].append(dropnode);
|
||||
dropnode.setStyles({'opacity': 0.5});
|
||||
dropnode.setData('xy', drop.xy);
|
||||
dropnode.setData('place', dropno);
|
||||
dropnode.setData('inputid', drop.fieldname.replace(':', '_'));
|
||||
dropnode.setData('group', drop.group);
|
||||
var dropdd = new Y.DD.Drop({
|
||||
node: dropnode, groups: [drop.group]});
|
||||
dropdd.on('drop:hit', drop_hit_handler, this);
|
||||
}
|
||||
}
|
||||
}, {NAME: DDIMAGEORTEXTQUESTIONNAME, ATTRS: {}});
|
||||
|
||||
Y.Event.define('dragchange', {
|
||||
// Webkit and IE repeat keydown when you hold down arrow keys.
|
||||
// Opera links keypress to page scroll; others keydown.
|
||||
// Firefox prevents page scroll via preventDefault() on either
|
||||
// keydown or keypress.
|
||||
_event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
|
||||
|
||||
_keys: {
|
||||
'32': 'next', // Space
|
||||
'37': 'previous', // Left arrow
|
||||
'38': 'previous', // Up arrow
|
||||
'39': 'next', // Right arrow
|
||||
'40': 'next', // Down arrow
|
||||
'27': 'remove' // Escape
|
||||
},
|
||||
|
||||
_keyHandler: function(e, notifier) {
|
||||
if (this._keys[e.keyCode]) {
|
||||
e.direction = this._keys[e.keyCode];
|
||||
notifier.fire(e);
|
||||
}
|
||||
},
|
||||
|
||||
on: function(node, sub, notifier) {
|
||||
sub._detacher = node.on(this._event, this._keyHandler,
|
||||
this, notifier);
|
||||
}
|
||||
});
|
||||
|
||||
M.qtype_ddimageortext.init_question = function(config) {
|
||||
return new DDIMAGEORTEXT_QUESTION(config);
|
||||
};
|
||||
|
||||
|
||||
}, '@VERSION@', {"requires": ["node", "dd", "dd-drop", "dd-constrain"]});
|
||||
-2
File diff suppressed because one or more lines are too long
question/type/ddimageortext/yui/build/moodle-qtype_ddimageortext-dd/moodle-qtype_ddimageortext-dd.js
Vendored
-546
@@ -1,546 +0,0 @@
|
||||
YUI.add('moodle-qtype_ddimageortext-dd', function (Y, NAME) {
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
var DDIMAGEORTEXTDDNAME = 'ddimageortext_dd';
|
||||
var DDIMAGEORTEXT_DD = function() {
|
||||
DDIMAGEORTEXT_DD.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the base class for the question rendering and question editing form code.
|
||||
*/
|
||||
Y.extend(DDIMAGEORTEXT_DD, Y.Base, {
|
||||
doc: null,
|
||||
polltimer: null,
|
||||
afterimageloaddone: false,
|
||||
poll_for_image_load: function(e, waitforimageconstrain, pause, doafterwords) {
|
||||
if (this.afterimageloaddone) {
|
||||
return;
|
||||
}
|
||||
var bgdone = this.doc.bg_img().get('complete');
|
||||
if (waitforimageconstrain) {
|
||||
bgdone = bgdone && this.doc.bg_img().hasClass('constrained');
|
||||
}
|
||||
var alldragsloaded = !this.doc.drag_item_homes().some(function(dragitemhome) {
|
||||
// in 'some' loop returning true breaks the loop and is passed as return value from
|
||||
// 'some' else returns false. Can be though of as equivalent to ||.
|
||||
if (dragitemhome.get('tagName') !== 'IMG') {
|
||||
return false;
|
||||
}
|
||||
var done = (dragitemhome.get('complete'));
|
||||
if (waitforimageconstrain) {
|
||||
done = done && dragitemhome.hasClass('constrained');
|
||||
}
|
||||
return !done;
|
||||
});
|
||||
if (bgdone && alldragsloaded) {
|
||||
if (this.polltimer !== null) {
|
||||
this.polltimer.cancel();
|
||||
this.polltimer = null;
|
||||
}
|
||||
this.doc.drag_item_homes().detach('load', this.poll_for_image_load);
|
||||
this.doc.bg_img().detach('load', this.poll_for_image_load);
|
||||
if (pause !== 0) {
|
||||
Y.later(pause, this, doafterwords);
|
||||
} else {
|
||||
doafterwords.call(this);
|
||||
}
|
||||
this.afterimageloaddone = true;
|
||||
} else if (this.polltimer === null) {
|
||||
var pollarguments = [null, waitforimageconstrain, pause, doafterwords];
|
||||
this.polltimer =
|
||||
Y.later(1000, this, this.poll_for_image_load, pollarguments, true);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Object to encapsulate operations on dd area.
|
||||
*/
|
||||
doc_structure: function(mainobj) {
|
||||
var topnode = Y.one(this.get('topnode'));
|
||||
var dragitemsarea = topnode.one('div.dragitems');
|
||||
var dropbgarea = topnode.one('div.droparea');
|
||||
return {
|
||||
top_node: function() {
|
||||
return topnode;
|
||||
},
|
||||
drag_items: function() {
|
||||
return dragitemsarea.all('.drag');
|
||||
},
|
||||
drop_zones: function() {
|
||||
return topnode.all('div.dropzones div.dropzone');
|
||||
},
|
||||
drop_zone_group: function(groupno) {
|
||||
return topnode.all('div.dropzones div.group' + groupno);
|
||||
},
|
||||
drag_items_cloned_from: function(dragitemno) {
|
||||
return dragitemsarea.all('.dragitems' + dragitemno);
|
||||
},
|
||||
drag_item: function(draginstanceno) {
|
||||
return dragitemsarea.one('.draginstance' + draginstanceno);
|
||||
},
|
||||
drag_items_in_group: function(groupno) {
|
||||
return dragitemsarea.all('.drag.group' + groupno);
|
||||
},
|
||||
drag_item_homes: function() {
|
||||
return dragitemsarea.all('.draghome');
|
||||
},
|
||||
bg_img: function() {
|
||||
return topnode.one('.dropbackground');
|
||||
},
|
||||
load_bg_img: function(url) {
|
||||
dropbgarea.setContent('<img class="dropbackground" src="' + url + '"/>');
|
||||
this.bg_img().on('load', this.on_image_load, this, 'bg_image');
|
||||
},
|
||||
add_or_update_drag_item_home: function(dragitemno, url, alt, group) {
|
||||
var oldhome = this.drag_item_home(dragitemno);
|
||||
var classes = 'draghome dragitemhomes' + dragitemno + ' group' + group;
|
||||
var imghtml = '<img class="' + classes + '" src="' + url + '" alt="' + alt + '" />';
|
||||
var divhtml = '<div class="' + classes + '">' + alt + '</div>';
|
||||
if (oldhome === null) {
|
||||
if (url) {
|
||||
dragitemsarea.append(imghtml);
|
||||
} else if (alt !== '') {
|
||||
dragitemsarea.append(divhtml);
|
||||
}
|
||||
} else {
|
||||
if (url) {
|
||||
dragitemsarea.insert(imghtml, oldhome);
|
||||
} else if (alt !== '') {
|
||||
dragitemsarea.insert(divhtml, oldhome);
|
||||
}
|
||||
oldhome.remove(true);
|
||||
}
|
||||
var newlycreated = dragitemsarea.one('.dragitemhomes' + dragitemno);
|
||||
if (newlycreated !== null) {
|
||||
newlycreated.setData('groupno', group);
|
||||
newlycreated.setData('dragitemno', dragitemno);
|
||||
}
|
||||
},
|
||||
drag_item_home: function(dragitemno) {
|
||||
return dragitemsarea.one('.dragitemhomes' + dragitemno);
|
||||
},
|
||||
get_classname_numeric_suffix: function(node, prefix) {
|
||||
var classes = node.getAttribute('class');
|
||||
if (classes !== '') {
|
||||
var classesarr = classes.split(' ');
|
||||
for (var index = 0; index < classesarr.length; index++) {
|
||||
var patt1 = new RegExp('^' + prefix + '([0-9])+$');
|
||||
if (patt1.test(classesarr[index])) {
|
||||
var patt2 = new RegExp('([0-9])+$');
|
||||
var match = patt2.exec(classesarr[index]);
|
||||
return +match[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
throw 'Prefix "' + prefix + '" not found in class names.';
|
||||
},
|
||||
clone_new_drag_item: function(draginstanceno, dragitemno) {
|
||||
var draghome = this.drag_item_home(dragitemno);
|
||||
if (draghome === null) {
|
||||
return null;
|
||||
}
|
||||
var drag = draghome.cloneNode(true);
|
||||
drag.removeClass('dragitemhomes' + dragitemno);
|
||||
drag.addClass('dragitems' + dragitemno);
|
||||
drag.addClass('draginstance' + draginstanceno);
|
||||
drag.removeClass('draghome');
|
||||
drag.addClass('drag');
|
||||
drag.setStyles({'visibility': 'visible', 'position': 'absolute'});
|
||||
drag.setData('draginstanceno', draginstanceno);
|
||||
drag.setData('dragitemno', dragitemno);
|
||||
draghome.get('parentNode').appendChild(drag);
|
||||
return drag;
|
||||
},
|
||||
draggable_for_question: function(drag, group, choice) {
|
||||
new Y.DD.Drag({
|
||||
node: drag,
|
||||
dragMode: 'point',
|
||||
groups: [group]
|
||||
}).plug(Y.Plugin.DDConstrained, {constrain2node: topnode});
|
||||
|
||||
drag.setData('group', group);
|
||||
drag.setData('choice', choice);
|
||||
},
|
||||
draggable_for_form: function(drag) {
|
||||
var dd = new Y.DD.Drag({
|
||||
node: drag,
|
||||
dragMode: 'point'
|
||||
}).plug(Y.Plugin.DDConstrained, {constrain2node: topnode});
|
||||
dd.on('drag:end', function(e) {
|
||||
var dragnode = e.target.get('node');
|
||||
var draginstanceno = dragnode.getData('draginstanceno');
|
||||
var gooddrop = dragnode.getData('gooddrop');
|
||||
|
||||
if (!gooddrop) {
|
||||
mainobj.reset_drag_xy(draginstanceno);
|
||||
} else {
|
||||
mainobj.set_drag_xy(draginstanceno, [e.pageX, e.pageY]);
|
||||
}
|
||||
}, this);
|
||||
dd.on('drag:start', function(e) {
|
||||
var drag = e.target;
|
||||
drag.get('node').setData('gooddrop', false);
|
||||
}, this);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
},
|
||||
|
||||
update_padding_sizes_all: function() {
|
||||
for (var groupno = 1; groupno <= 8; groupno++) {
|
||||
this.update_padding_size_for_group(groupno);
|
||||
}
|
||||
},
|
||||
update_padding_size_for_group: function(groupno) {
|
||||
var groupitems = this.doc.top_node().all('.draghome.group' + groupno);
|
||||
if (groupitems.size() !== 0) {
|
||||
var maxwidth = 0;
|
||||
var maxheight = 0;
|
||||
groupitems.each(function(item) {
|
||||
maxwidth = Math.max(maxwidth, item.get('clientWidth'));
|
||||
maxheight = Math.max(maxheight, item.get('clientHeight'));
|
||||
}, this);
|
||||
groupitems.each(function(item) {
|
||||
var margintopbottom = Math.round((10 + maxheight - item.get('clientHeight')) / 2);
|
||||
var marginleftright = Math.round((10 + maxwidth - item.get('clientWidth')) / 2);
|
||||
item.setStyle('padding', margintopbottom + 'px ' + marginleftright + 'px ' +
|
||||
margintopbottom + 'px ' + marginleftright + 'px');
|
||||
}, this);
|
||||
this.doc.drop_zone_group(groupno).setStyles({'width': maxwidth + 10,
|
||||
'height': maxheight + 10});
|
||||
}
|
||||
},
|
||||
convert_to_window_xy: function(bgimgxy) {
|
||||
return [Number(bgimgxy[0]) + this.doc.bg_img().getX() + 1,
|
||||
Number(bgimgxy[1]) + this.doc.bg_img().getY() + 1];
|
||||
}
|
||||
}, {
|
||||
NAME: DDIMAGEORTEXTDDNAME,
|
||||
ATTRS: {
|
||||
drops: {value: null},
|
||||
readonly: {value: false},
|
||||
topnode: {value: null}
|
||||
}
|
||||
});
|
||||
|
||||
M.qtype_ddimageortext = M.qtype_ddimageortext || {};
|
||||
M.qtype_ddimageortext.dd_base_class = DDIMAGEORTEXT_DD;
|
||||
|
||||
var DDIMAGEORTEXTQUESTIONNAME = 'ddimageortext_question';
|
||||
var DDIMAGEORTEXT_QUESTION = function() {
|
||||
DDIMAGEORTEXT_QUESTION.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
/**
|
||||
* This is the code for question rendering.
|
||||
*/
|
||||
Y.extend(DDIMAGEORTEXT_QUESTION, M.qtype_ddimageortext.dd_base_class, {
|
||||
passiveSupported: false,
|
||||
pendingid: '',
|
||||
initializer: function() {
|
||||
this.pendingid = 'qtype_ddimageortext-' + Math.random().toString(36).slice(2); // Random string.
|
||||
M.util.js_pending(this.pendingid);
|
||||
this.doc = this.doc_structure(this);
|
||||
this.poll_for_image_load(null, false, 10, this.create_all_drag_and_drops);
|
||||
this.doc.bg_img().after('load', this.poll_for_image_load, this,
|
||||
false, 10, this.create_all_drag_and_drops);
|
||||
this.doc.drag_item_homes().after('load', this.poll_for_image_load, this,
|
||||
false, 10, this.create_all_drag_and_drops);
|
||||
if (!this.get('readonly')) {
|
||||
Y.later(500, this, this.reposition_drags_for_question, true);
|
||||
} else {
|
||||
Y.one('window').on('resize', function() {
|
||||
this.reposition_drags_for_question();
|
||||
}, this);
|
||||
}
|
||||
this.checkPassiveSupported();
|
||||
},
|
||||
|
||||
/**
|
||||
* prevent_touchmove_from_scrolling allows users of touch screen devices to
|
||||
* use drag and drop and normal scrolling at the same time. I.e. when
|
||||
* touching and dragging a draggable item, the screen does not scroll, but
|
||||
* you can scroll by touching other area of the screen apart from the
|
||||
* draggable items.
|
||||
*/
|
||||
prevent_touchmove_from_scrolling: function(drag) {
|
||||
var touchmove = (Y.UA.ie) ? 'MSPointerMove' : 'touchmove';
|
||||
var eventHandler = function(event) {
|
||||
event.preventDefault();
|
||||
};
|
||||
var dragId = drag.get('id');
|
||||
var el = document.getElementById(dragId);
|
||||
// Note do not dynamically add events within another event, as this causes issues on iOS11.3.
|
||||
// See https://github.com/atlassian/react-beautiful-dnd/issues/413 and
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=184250 for fuller explanation.
|
||||
el.addEventListener(touchmove, eventHandler, this.passiveSupported ? {passive: false, capture: true} : false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Some older browsers do not support passing an options object to addEventListener.
|
||||
* This is a check from https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener.
|
||||
*/
|
||||
checkPassiveSupported: function() {
|
||||
try {
|
||||
var options = Object.defineProperty({}, 'passive', {
|
||||
get: function() {
|
||||
this.passiveSupported = true;
|
||||
}.bind(this)
|
||||
});
|
||||
window.addEventListener('test', options, options);
|
||||
window.removeEventListener('test', options, options);
|
||||
} catch (err) {
|
||||
this.passiveSupported = false;
|
||||
}
|
||||
},
|
||||
|
||||
create_all_drag_and_drops: function() {
|
||||
this.init_drops();
|
||||
this.update_padding_sizes_all();
|
||||
var i = 0;
|
||||
this.doc.drag_item_homes().each(function(dragitemhome) {
|
||||
var dragitemno = Number(this.doc.get_classname_numeric_suffix(dragitemhome, 'dragitemhomes'));
|
||||
var choice = +this.doc.get_classname_numeric_suffix(dragitemhome, 'choice');
|
||||
var group = +this.doc.get_classname_numeric_suffix(dragitemhome, 'group');
|
||||
var groupsize = this.doc.drop_zone_group(group).size();
|
||||
var dragnode = this.doc.clone_new_drag_item(i, dragitemno);
|
||||
i++;
|
||||
if (!this.get('readonly')) {
|
||||
this.doc.draggable_for_question(dragnode, group, choice);
|
||||
|
||||
// Prevent scrolling whilst dragging on Adroid devices.
|
||||
this.prevent_touchmove_from_scrolling(dragnode);
|
||||
}
|
||||
if (dragnode.hasClass('infinite')) {
|
||||
var dragstocreate = groupsize - 1;
|
||||
while (dragstocreate > 0) {
|
||||
dragnode = this.doc.clone_new_drag_item(i, dragitemno);
|
||||
i++;
|
||||
if (!this.get('readonly')) {
|
||||
this.doc.draggable_for_question(dragnode, group, choice);
|
||||
|
||||
// Prevent scrolling whilst dragging on Adroid devices.
|
||||
this.prevent_touchmove_from_scrolling(dragnode);
|
||||
}
|
||||
dragstocreate--;
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
this.reposition_drags_for_question();
|
||||
if (!this.get('readonly')) {
|
||||
this.doc.drop_zones().set('tabIndex', 0);
|
||||
this.doc.drop_zones().each(
|
||||
function(v) {
|
||||
v.on('dragchange', this.drop_zone_key_press, this);
|
||||
}, this);
|
||||
}
|
||||
M.util.js_complete(this.pendingid);
|
||||
},
|
||||
drop_zone_key_press: function(e) {
|
||||
switch (e.direction) {
|
||||
case 'next' :
|
||||
this.place_next_drag_in(e.target);
|
||||
break;
|
||||
case 'previous' :
|
||||
this.place_previous_drag_in(e.target);
|
||||
break;
|
||||
case 'remove' :
|
||||
this.remove_drag_from_drop(e.target);
|
||||
break;
|
||||
}
|
||||
e.preventDefault();
|
||||
this.reposition_drags_for_question();
|
||||
},
|
||||
place_next_drag_in: function(drop) {
|
||||
this.search_for_unplaced_drop_choice(drop, 1);
|
||||
},
|
||||
place_previous_drag_in: function(drop) {
|
||||
this.search_for_unplaced_drop_choice(drop, -1);
|
||||
},
|
||||
search_for_unplaced_drop_choice: function(drop, direction) {
|
||||
var next;
|
||||
var current = this.current_drag_in_drop(drop);
|
||||
if ('' === current) {
|
||||
if (direction === 1) {
|
||||
next = 1;
|
||||
} else {
|
||||
next = 1;
|
||||
var groupno = drop.getData('group');
|
||||
this.doc.drag_items_in_group(groupno).each(function(drag) {
|
||||
next = Math.max(next, drag.getData('choice'));
|
||||
}, this);
|
||||
}
|
||||
} else {
|
||||
next = +current + direction;
|
||||
}
|
||||
var drag;
|
||||
do {
|
||||
if (this.get_choices_for_drop(next, drop).size() === 0) {
|
||||
this.remove_drag_from_drop(drop);
|
||||
return;
|
||||
} else {
|
||||
drag = this.get_unplaced_choice_for_drop(next, drop);
|
||||
}
|
||||
next = next + direction;
|
||||
} while (drag === null);
|
||||
this.place_drag_in_drop(drag, drop);
|
||||
},
|
||||
current_drag_in_drop: function(drop) {
|
||||
var inputid = drop.getData('inputid');
|
||||
var inputnode = Y.one('input#' + inputid);
|
||||
return inputnode.get('value');
|
||||
},
|
||||
remove_drag_from_drop: function(drop) {
|
||||
this.place_drag_in_drop(null, drop);
|
||||
},
|
||||
place_drag_in_drop: function(drag, drop) {
|
||||
var inputid = drop.getData('inputid');
|
||||
var inputnode = Y.one('input#' + inputid);
|
||||
if (drag !== null) {
|
||||
inputnode.set('value', drag.getData('choice'));
|
||||
} else {
|
||||
inputnode.set('value', '');
|
||||
}
|
||||
},
|
||||
reposition_drags_for_question: function(dotimeout) {
|
||||
this.doc.drag_items().removeClass('placed');
|
||||
this.doc.drag_items().each(function(dragitem) {
|
||||
if (dragitem.dd !== undefined) {
|
||||
dragitem.dd.detachAll('drag:start');
|
||||
}
|
||||
}, this);
|
||||
this.doc.drop_zones().each(function(dropzone) {
|
||||
var relativexy = dropzone.getData('xy');
|
||||
dropzone.setXY(this.convert_to_window_xy(relativexy));
|
||||
var inputcss = 'input#' + dropzone.getData('inputid');
|
||||
var input = this.doc.top_node().one(inputcss);
|
||||
var choice = input.get('value');
|
||||
if (choice !== "") {
|
||||
var dragitem = this.get_unplaced_choice_for_drop(choice, dropzone);
|
||||
if (dragitem !== null) {
|
||||
dragitem.setXY(dropzone.getXY());
|
||||
dragitem.addClass('placed');
|
||||
if (dragitem.dd !== undefined) {
|
||||
dragitem.dd.once('drag:start', function(e, input) {
|
||||
input.set('value', '');
|
||||
e.target.get('node').removeClass('placed');
|
||||
}, this, input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
this.doc.drag_items().each(function(dragitem) {
|
||||
if (!dragitem.hasClass('placed') && !dragitem.hasClass('yui3-dd-dragging')) {
|
||||
var dragitemhome = this.doc.drag_item_home(dragitem.getData('dragitemno'));
|
||||
dragitem.setXY(dragitemhome.getXY());
|
||||
}
|
||||
}, this);
|
||||
if (dotimeout) {
|
||||
Y.later(500, this, this.reposition_drags_for_question, true);
|
||||
}
|
||||
},
|
||||
get_choices_for_drop: function(choice, drop) {
|
||||
var group = drop.getData('group');
|
||||
return this.doc.top_node().all(
|
||||
'div.dragitemgroup' + group + ' .choice' + choice + '.drag');
|
||||
},
|
||||
get_unplaced_choice_for_drop: function(choice, drop) {
|
||||
var dragitems = this.get_choices_for_drop(choice, drop);
|
||||
var dragitem = null;
|
||||
dragitems.some(function(d) {
|
||||
if (this.get('readonly') || (!d.hasClass('placed') && !d.hasClass('yui3-dd-dragging'))) {
|
||||
dragitem = d;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return dragitem;
|
||||
},
|
||||
init_drops: function() {
|
||||
var dropareas = this.doc.top_node().one('div.dropzones');
|
||||
var groupnodes = {};
|
||||
for (var groupno = 1; groupno <= 8; groupno++) {
|
||||
var groupnode = Y.Node.create('<div class = "dropzonegroup' + groupno + '"></div>');
|
||||
dropareas.append(groupnode);
|
||||
groupnodes[groupno] = groupnode;
|
||||
}
|
||||
var drop_hit_handler = function(e) {
|
||||
var drag = e.drag.get('node');
|
||||
var drop = e.drop.get('node');
|
||||
if (Number(drop.getData('group')) === drag.getData('group')) {
|
||||
this.place_drag_in_drop(drag, drop);
|
||||
}
|
||||
};
|
||||
for (var dropno in this.get('drops')) {
|
||||
var drop = this.get('drops')[dropno];
|
||||
var nodeclass = 'dropzone group' + drop.group + ' place' + dropno;
|
||||
var title = drop.text.replace('"', '\"');
|
||||
if (!title) {
|
||||
title = M.util.get_string('blank', 'qtype_ddimageortext');
|
||||
}
|
||||
var dropnodehtml = '<div title="' + title + '" class="' + nodeclass + '">' +
|
||||
'<span class="accesshide">' + title + '</span> </div>';
|
||||
var dropnode = Y.Node.create(dropnodehtml);
|
||||
groupnodes[drop.group].append(dropnode);
|
||||
dropnode.setStyles({'opacity': 0.5});
|
||||
dropnode.setData('xy', drop.xy);
|
||||
dropnode.setData('place', dropno);
|
||||
dropnode.setData('inputid', drop.fieldname.replace(':', '_'));
|
||||
dropnode.setData('group', drop.group);
|
||||
var dropdd = new Y.DD.Drop({
|
||||
node: dropnode, groups: [drop.group]});
|
||||
dropdd.on('drop:hit', drop_hit_handler, this);
|
||||
}
|
||||
}
|
||||
}, {NAME: DDIMAGEORTEXTQUESTIONNAME, ATTRS: {}});
|
||||
|
||||
Y.Event.define('dragchange', {
|
||||
// Webkit and IE repeat keydown when you hold down arrow keys.
|
||||
// Opera links keypress to page scroll; others keydown.
|
||||
// Firefox prevents page scroll via preventDefault() on either
|
||||
// keydown or keypress.
|
||||
_event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
|
||||
|
||||
_keys: {
|
||||
'32': 'next', // Space
|
||||
'37': 'previous', // Left arrow
|
||||
'38': 'previous', // Up arrow
|
||||
'39': 'next', // Right arrow
|
||||
'40': 'next', // Down arrow
|
||||
'27': 'remove' // Escape
|
||||
},
|
||||
|
||||
_keyHandler: function(e, notifier) {
|
||||
if (this._keys[e.keyCode]) {
|
||||
e.direction = this._keys[e.keyCode];
|
||||
notifier.fire(e);
|
||||
}
|
||||
},
|
||||
|
||||
on: function(node, sub, notifier) {
|
||||
sub._detacher = node.on(this._event, this._keyHandler,
|
||||
this, notifier);
|
||||
}
|
||||
});
|
||||
|
||||
M.qtype_ddimageortext.init_question = function(config) {
|
||||
return new DDIMAGEORTEXT_QUESTION(config);
|
||||
};
|
||||
|
||||
|
||||
}, '@VERSION@', {"requires": ["node", "dd", "dd-drop", "dd-constrain"]});
|
||||
-369
@@ -1,369 +0,0 @@
|
||||
YUI.add('moodle-qtype_ddimageortext-form', function (Y, NAME) {
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This is the question editing form code.
|
||||
*/
|
||||
var DDIMAGEORTEXTFORMNAME = 'moodle-qtype_ddimageortext-form';
|
||||
var DDIMAGEORTEXT_FORM = function() {
|
||||
DDIMAGEORTEXT_FORM.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Y.extend(DDIMAGEORTEXT_FORM, M.qtype_ddimageortext.dd_base_class, {
|
||||
pendingid: '',
|
||||
fp: null,
|
||||
|
||||
initializer: function() {
|
||||
this.pendingid = 'qtype_ddimageortext-form-' + Math.random().toString(36).slice(2); // Random string.
|
||||
M.util.js_pending(this.pendingid);
|
||||
this.fp = this.file_pickers();
|
||||
var tn = Y.one(this.get('topnode'));
|
||||
tn.one('div.fcontainer').append('<div class="ddarea"><div class="droparea"></div><div class="dragitems"></div>' +
|
||||
'<div class="dropzones"></div></div>');
|
||||
this.doc = this.doc_structure(this);
|
||||
this.draw_dd_area();
|
||||
},
|
||||
|
||||
draw_dd_area: function() {
|
||||
var bgimageurl = this.fp.file('bgimage').href;
|
||||
this.stop_selector_events();
|
||||
this.set_options_for_drag_item_selectors();
|
||||
if (bgimageurl !== null) {
|
||||
this.doc.load_bg_img(bgimageurl);
|
||||
this.load_drag_homes();
|
||||
|
||||
var drop = new Y.DD.Drop({
|
||||
node: this.doc.bg_img()
|
||||
});
|
||||
// Listen for a drop:hit on the background image
|
||||
drop.on('drop:hit', function(e) {
|
||||
e.drag.get('node').setData('gooddrop', true);
|
||||
});
|
||||
|
||||
this.afterimageloaddone = false;
|
||||
this.doc.bg_img().on('load', this.constrain_image_size, this, 'bgimage');
|
||||
this.doc.drag_item_homes()
|
||||
.on('load', this.constrain_image_size, this, 'dragimage');
|
||||
this.doc.bg_img().after('load', this.poll_for_image_load, this,
|
||||
true, 0, this.after_all_images_loaded);
|
||||
this.doc.drag_item_homes().after('load', this.poll_for_image_load, this,
|
||||
true, 0, this.after_all_images_loaded);
|
||||
} else {
|
||||
this.setup_form_events();
|
||||
M.util.js_complete(this.pendingid);
|
||||
}
|
||||
this.update_visibility_of_file_pickers();
|
||||
},
|
||||
|
||||
after_all_images_loaded: function() {
|
||||
this.update_padding_sizes_all();
|
||||
this.update_drag_instances();
|
||||
this.reposition_drags_for_form();
|
||||
this.set_options_for_drag_item_selectors();
|
||||
this.setup_form_events();
|
||||
Y.later(500, this, this.reposition_drags_for_form, [], true);
|
||||
},
|
||||
|
||||
constrain_image_size: function(e, imagetype) {
|
||||
var maxsize = this.get('maxsizes')[imagetype];
|
||||
var reduceby = Math.max(e.target.get('width') / maxsize.width,
|
||||
e.target.get('height') / maxsize.height);
|
||||
if (reduceby > 1) {
|
||||
e.target.set('width', Math.floor(e.target.get('width') / reduceby));
|
||||
}
|
||||
e.target.addClass('constrained');
|
||||
e.target.detach('load', this.constrain_image_size);
|
||||
},
|
||||
|
||||
load_drag_homes: function() {
|
||||
// Set up drag items homes.
|
||||
for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
|
||||
this.load_drag_home(i);
|
||||
}
|
||||
},
|
||||
|
||||
load_drag_home: function(dragitemno) {
|
||||
var url = null;
|
||||
if ('image' === this.form.get_form_value('drags', [dragitemno, 'dragitemtype'])) {
|
||||
url = this.fp.file(this.form.to_name_with_index('dragitem', [dragitemno])).href;
|
||||
}
|
||||
this.doc.add_or_update_drag_item_home(dragitemno, url,
|
||||
this.form.get_form_value('draglabel', [dragitemno]),
|
||||
this.form.get_form_value('drags', [dragitemno, 'draggroup']));
|
||||
},
|
||||
|
||||
update_drag_instances: function() {
|
||||
// Set up drop zones.
|
||||
for (var i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
|
||||
var dragitemno = this.form.get_form_value('drops', [i, 'choice']);
|
||||
if (dragitemno !== '0' && (this.doc.drag_item(i) === null)) {
|
||||
var drag = this.doc.clone_new_drag_item(i, dragitemno - 1);
|
||||
if (drag !== null) {
|
||||
this.doc.draggable_for_form(drag);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
set_options_for_drag_item_selectors: function() {
|
||||
var dragitemsoptions = {'0': ''};
|
||||
for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
|
||||
var label = this.form.get_form_value('draglabel', [i]);
|
||||
var file = this.fp.file(this.form.to_name_with_index('dragitem', [i]));
|
||||
if ('image' === this.form.get_form_value('drags', [i, 'dragitemtype'])
|
||||
&& file.name !== null) {
|
||||
dragitemsoptions[i + 1] = (i + 1) + '. ' + label + ' (' + file.name + ')';
|
||||
} else if (label !== '') {
|
||||
dragitemsoptions[i + 1] = (i + 1) + '. ' + label;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
|
||||
var selector = Y.one('#id_drops_' + i + '_choice');
|
||||
var selectedvalue = selector.get('value');
|
||||
selector.all('option').remove(true);
|
||||
for (var value in dragitemsoptions) {
|
||||
value = +value;
|
||||
var option = '<option value="' + value + '">' + dragitemsoptions[value] + '</option>';
|
||||
selector.append(option);
|
||||
var optionnode = selector.one('option[value="' + value + '"]');
|
||||
if (value === +selectedvalue) {
|
||||
optionnode.set('selected', true);
|
||||
} else {
|
||||
if (value !== 0) { // No item option is always selectable.
|
||||
var cbel = Y.one('#id_drags_' + (value - 1) + '_infinite');
|
||||
if (cbel && !cbel.get('checked')) {
|
||||
if (this.item_is_allocated_to_dropzone(value)) {
|
||||
optionnode.set('disabled', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
stop_selector_events: function() {
|
||||
Y.all('fieldset#id_dropzoneheader select').detachAll();
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the specified drag item is allocated to a dropzone.
|
||||
*
|
||||
* @method item_is_allocated_to_dropzone
|
||||
* @param {Number} value of the drag item to check
|
||||
* @return {Boolean} true if item is allocated to dropzone
|
||||
*/
|
||||
item_is_allocated_to_dropzone: function(itemvalue) {
|
||||
return Y.all('fieldset#id_dropzoneheader select').some(function(selectNode) {
|
||||
return Number(selectNode.get('value')) === itemvalue;
|
||||
});
|
||||
},
|
||||
|
||||
setup_form_events: function() {
|
||||
// Events triggered by changes to form data.
|
||||
|
||||
// X and y coordinates.
|
||||
Y.all('fieldset#id_dropzoneheader input').on('blur', function(e) {
|
||||
var name = e.target.getAttribute('name');
|
||||
var draginstanceno = this.form.from_name_with_index(name).indexes[0];
|
||||
var fromform = [this.form.get_form_value('drops', [draginstanceno, 'xleft']),
|
||||
this.form.get_form_value('drops', [draginstanceno, 'ytop'])];
|
||||
var constrainedxy = this.constrain_xy(draginstanceno, fromform);
|
||||
this.form.set_form_value('drops', [draginstanceno, 'xleft'], constrainedxy[0]);
|
||||
this.form.set_form_value('drops', [draginstanceno, 'ytop'], constrainedxy[1]);
|
||||
}, this);
|
||||
|
||||
// Change in selected item.
|
||||
Y.all('fieldset#id_dropzoneheader select').on('change', function(e) {
|
||||
var name = e.target.getAttribute('name');
|
||||
var draginstanceno = this.form.from_name_with_index(name).indexes[0];
|
||||
var old = this.doc.drag_item(draginstanceno);
|
||||
if (old !== null) {
|
||||
old.remove(true);
|
||||
}
|
||||
this.draw_dd_area();
|
||||
}, this);
|
||||
|
||||
for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
|
||||
// Change to group selector.
|
||||
Y.all('#fgroup_id_drags_' + i + ' select.draggroup').on('change', this.redraw_dd_area, this);
|
||||
Y.all('#fgroup_id_drags_' + i + ' select.dragitemtype').on('change', this.redraw_dd_area, this);
|
||||
Y.all('fieldset#draggableitemheader_' + i + ' input[type="text"]')
|
||||
.on('blur', this.set_options_for_drag_item_selectors, this);
|
||||
// Change to infinite checkbox.
|
||||
Y.all('fieldset#draggableitemheader_' + i + ' input[type="checkbox"]')
|
||||
.on('change', this.set_options_for_drag_item_selectors, this);
|
||||
}
|
||||
// Event on file picker new file selection.
|
||||
Y.after(function(e) {
|
||||
var name = this.fp.name(e.id);
|
||||
if (name !== 'bgimage') {
|
||||
this.doc.drag_items().remove(true);
|
||||
}
|
||||
this.draw_dd_area();
|
||||
}, M.form_filepicker, 'callback', this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Redraws drag and drop preview area.
|
||||
*
|
||||
* @method redraw_dd_area
|
||||
*/
|
||||
redraw_dd_area: function() {
|
||||
this.doc.drag_items().remove(true);
|
||||
this.draw_dd_area();
|
||||
},
|
||||
|
||||
update_visibility_of_file_pickers: function() {
|
||||
for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
|
||||
if ('image' === this.form.get_form_value('drags', [i, 'dragitemtype'])) {
|
||||
Y.one('input#id_dragitem_' + i).get('parentNode').get('parentNode')
|
||||
.setStyle('display', 'block');
|
||||
} else {
|
||||
Y.one('input#id_dragitem_' + i).get('parentNode').get('parentNode')
|
||||
.setStyle('display', 'none');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
reposition_drags_for_form: function() {
|
||||
this.doc.drag_items().each(function(drag) {
|
||||
var draginstanceno = drag.getData('draginstanceno');
|
||||
this.reposition_drag_for_form(draginstanceno);
|
||||
}, this);
|
||||
M.util.js_complete(this.pendingid);
|
||||
},
|
||||
|
||||
reposition_drag_for_form: function(draginstanceno) {
|
||||
var drag = this.doc.drag_item(draginstanceno);
|
||||
if (null !== drag && !drag.hasClass('yui3-dd-dragging')) {
|
||||
var fromform = [this.form.get_form_value('drops', [draginstanceno, 'xleft']),
|
||||
this.form.get_form_value('drops', [draginstanceno, 'ytop'])];
|
||||
if (fromform[0] === '' && fromform[1] === '') {
|
||||
var dragitemno = drag.getData('dragitemno');
|
||||
drag.setXY(this.doc.drag_item_home(dragitemno).getXY());
|
||||
} else {
|
||||
drag.setXY(this.convert_to_window_xy(fromform));
|
||||
}
|
||||
}
|
||||
},
|
||||
set_drag_xy: function(draginstanceno, xy) {
|
||||
xy = this.constrain_xy(draginstanceno, this.convert_to_bg_img_xy(xy));
|
||||
this.form.set_form_value('drops', [draginstanceno, 'xleft'], Math.round(xy[0]));
|
||||
this.form.set_form_value('drops', [draginstanceno, 'ytop'], Math.round(xy[1]));
|
||||
},
|
||||
reset_drag_xy: function(draginstanceno) {
|
||||
this.form.set_form_value('drops', [draginstanceno, 'xleft'], '');
|
||||
this.form.set_form_value('drops', [draginstanceno, 'ytop'], '');
|
||||
},
|
||||
|
||||
// make sure xy value is not out of bounds of bg image
|
||||
constrain_xy: function(draginstanceno, bgimgxy) {
|
||||
var drag = this.doc.drag_item(draginstanceno);
|
||||
var xleftconstrained =
|
||||
Math.min(bgimgxy[0], this.doc.bg_img().get('width') - drag.get('offsetWidth'));
|
||||
var ytopconstrained =
|
||||
Math.min(bgimgxy[1], this.doc.bg_img().get('height') - drag.get('offsetHeight'));
|
||||
xleftconstrained = Math.max(xleftconstrained, 0);
|
||||
ytopconstrained = Math.max(ytopconstrained, 0);
|
||||
return [xleftconstrained, ytopconstrained];
|
||||
},
|
||||
convert_to_bg_img_xy: function(windowxy) {
|
||||
return [Number(windowxy[0]) - this.doc.bg_img().getX() - 1,
|
||||
Number(windowxy[1]) - this.doc.bg_img().getY() - 1];
|
||||
},
|
||||
|
||||
/**
|
||||
* Low level operations on form.
|
||||
*/
|
||||
form: {
|
||||
to_name_with_index: function(name, indexes) {
|
||||
var indexstring = name;
|
||||
for (var i = 0; i < indexes.length; i++) {
|
||||
indexstring = indexstring + '[' + indexes[i] + ']';
|
||||
}
|
||||
return indexstring;
|
||||
},
|
||||
get_el: function(name, indexes) {
|
||||
var form = document.getElementById('mform1');
|
||||
return form.elements[this.to_name_with_index(name, indexes)];
|
||||
},
|
||||
get_form_value: function(name, indexes) {
|
||||
var el = this.get_el(name, indexes);
|
||||
if (el.type === 'checkbox') {
|
||||
return el.checked;
|
||||
} else {
|
||||
return el.value;
|
||||
}
|
||||
},
|
||||
set_form_value: function(name, indexes, value) {
|
||||
var el = this.get_el(name, indexes);
|
||||
if (el.type === 'checkbox') {
|
||||
el.checked = value;
|
||||
} else {
|
||||
el.value = value;
|
||||
}
|
||||
},
|
||||
from_name_with_index: function(name) {
|
||||
var toreturn = {};
|
||||
toreturn.indexes = [];
|
||||
var bracket = name.indexOf('[');
|
||||
toreturn.name = name.substring(0, bracket);
|
||||
while (bracket !== -1) {
|
||||
var end = name.indexOf(']', bracket + 1);
|
||||
toreturn.indexes.push(name.substring(bracket + 1, end));
|
||||
bracket = name.indexOf('[', end + 1);
|
||||
}
|
||||
return toreturn;
|
||||
}
|
||||
},
|
||||
|
||||
file_pickers: function() {
|
||||
var draftitemidstoname;
|
||||
var nametoparentnode;
|
||||
if (draftitemidstoname === undefined) {
|
||||
draftitemidstoname = {};
|
||||
nametoparentnode = {};
|
||||
var filepickers = Y.all('form.mform input.filepickerhidden');
|
||||
filepickers.each(function(filepicker) {
|
||||
draftitemidstoname[filepicker.get('value')] = filepicker.get('name');
|
||||
nametoparentnode[filepicker.get('name')] = filepicker.get('parentNode');
|
||||
}, this);
|
||||
}
|
||||
var toreturn = {
|
||||
file: function(name) {
|
||||
var parentnode = nametoparentnode[name];
|
||||
var fileanchor = parentnode.one('div.filepicker-filelist a');
|
||||
if (fileanchor) {
|
||||
return {href: fileanchor.get('href'), name: fileanchor.get('innerHTML')};
|
||||
} else {
|
||||
return {href: null, name: null};
|
||||
}
|
||||
},
|
||||
name: function(draftitemid) {
|
||||
return draftitemidstoname[draftitemid];
|
||||
}
|
||||
};
|
||||
return toreturn;
|
||||
}
|
||||
}, {NAME: DDIMAGEORTEXTFORMNAME, ATTRS: {maxsizes: {value: null}}});
|
||||
M.qtype_ddimageortext = M.qtype_ddimageortext || {};
|
||||
M.qtype_ddimageortext.init_form = function(config) {
|
||||
return new DDIMAGEORTEXT_FORM(config);
|
||||
};
|
||||
|
||||
|
||||
}, '@VERSION@', {"requires": ["moodle-qtype_ddimageortext-dd", "form_filepicker"]});
|
||||
-2
File diff suppressed because one or more lines are too long
-369
@@ -1,369 +0,0 @@
|
||||
YUI.add('moodle-qtype_ddimageortext-form', function (Y, NAME) {
|
||||
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This is the question editing form code.
|
||||
*/
|
||||
var DDIMAGEORTEXTFORMNAME = 'moodle-qtype_ddimageortext-form';
|
||||
var DDIMAGEORTEXT_FORM = function() {
|
||||
DDIMAGEORTEXT_FORM.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Y.extend(DDIMAGEORTEXT_FORM, M.qtype_ddimageortext.dd_base_class, {
|
||||
pendingid: '',
|
||||
fp: null,
|
||||
|
||||
initializer: function() {
|
||||
this.pendingid = 'qtype_ddimageortext-form-' + Math.random().toString(36).slice(2); // Random string.
|
||||
M.util.js_pending(this.pendingid);
|
||||
this.fp = this.file_pickers();
|
||||
var tn = Y.one(this.get('topnode'));
|
||||
tn.one('div.fcontainer').append('<div class="ddarea"><div class="droparea"></div><div class="dragitems"></div>' +
|
||||
'<div class="dropzones"></div></div>');
|
||||
this.doc = this.doc_structure(this);
|
||||
this.draw_dd_area();
|
||||
},
|
||||
|
||||
draw_dd_area: function() {
|
||||
var bgimageurl = this.fp.file('bgimage').href;
|
||||
this.stop_selector_events();
|
||||
this.set_options_for_drag_item_selectors();
|
||||
if (bgimageurl !== null) {
|
||||
this.doc.load_bg_img(bgimageurl);
|
||||
this.load_drag_homes();
|
||||
|
||||
var drop = new Y.DD.Drop({
|
||||
node: this.doc.bg_img()
|
||||
});
|
||||
// Listen for a drop:hit on the background image
|
||||
drop.on('drop:hit', function(e) {
|
||||
e.drag.get('node').setData('gooddrop', true);
|
||||
});
|
||||
|
||||
this.afterimageloaddone = false;
|
||||
this.doc.bg_img().on('load', this.constrain_image_size, this, 'bgimage');
|
||||
this.doc.drag_item_homes()
|
||||
.on('load', this.constrain_image_size, this, 'dragimage');
|
||||
this.doc.bg_img().after('load', this.poll_for_image_load, this,
|
||||
true, 0, this.after_all_images_loaded);
|
||||
this.doc.drag_item_homes().after('load', this.poll_for_image_load, this,
|
||||
true, 0, this.after_all_images_loaded);
|
||||
} else {
|
||||
this.setup_form_events();
|
||||
M.util.js_complete(this.pendingid);
|
||||
}
|
||||
this.update_visibility_of_file_pickers();
|
||||
},
|
||||
|
||||
after_all_images_loaded: function() {
|
||||
this.update_padding_sizes_all();
|
||||
this.update_drag_instances();
|
||||
this.reposition_drags_for_form();
|
||||
this.set_options_for_drag_item_selectors();
|
||||
this.setup_form_events();
|
||||
Y.later(500, this, this.reposition_drags_for_form, [], true);
|
||||
},
|
||||
|
||||
constrain_image_size: function(e, imagetype) {
|
||||
var maxsize = this.get('maxsizes')[imagetype];
|
||||
var reduceby = Math.max(e.target.get('width') / maxsize.width,
|
||||
e.target.get('height') / maxsize.height);
|
||||
if (reduceby > 1) {
|
||||
e.target.set('width', Math.floor(e.target.get('width') / reduceby));
|
||||
}
|
||||
e.target.addClass('constrained');
|
||||
e.target.detach('load', this.constrain_image_size);
|
||||
},
|
||||
|
||||
load_drag_homes: function() {
|
||||
// Set up drag items homes.
|
||||
for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
|
||||
this.load_drag_home(i);
|
||||
}
|
||||
},
|
||||
|
||||
load_drag_home: function(dragitemno) {
|
||||
var url = null;
|
||||
if ('image' === this.form.get_form_value('drags', [dragitemno, 'dragitemtype'])) {
|
||||
url = this.fp.file(this.form.to_name_with_index('dragitem', [dragitemno])).href;
|
||||
}
|
||||
this.doc.add_or_update_drag_item_home(dragitemno, url,
|
||||
this.form.get_form_value('draglabel', [dragitemno]),
|
||||
this.form.get_form_value('drags', [dragitemno, 'draggroup']));
|
||||
},
|
||||
|
||||
update_drag_instances: function() {
|
||||
// Set up drop zones.
|
||||
for (var i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
|
||||
var dragitemno = this.form.get_form_value('drops', [i, 'choice']);
|
||||
if (dragitemno !== '0' && (this.doc.drag_item(i) === null)) {
|
||||
var drag = this.doc.clone_new_drag_item(i, dragitemno - 1);
|
||||
if (drag !== null) {
|
||||
this.doc.draggable_for_form(drag);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
set_options_for_drag_item_selectors: function() {
|
||||
var dragitemsoptions = {'0': ''};
|
||||
for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
|
||||
var label = this.form.get_form_value('draglabel', [i]);
|
||||
var file = this.fp.file(this.form.to_name_with_index('dragitem', [i]));
|
||||
if ('image' === this.form.get_form_value('drags', [i, 'dragitemtype'])
|
||||
&& file.name !== null) {
|
||||
dragitemsoptions[i + 1] = (i + 1) + '. ' + label + ' (' + file.name + ')';
|
||||
} else if (label !== '') {
|
||||
dragitemsoptions[i + 1] = (i + 1) + '. ' + label;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
|
||||
var selector = Y.one('#id_drops_' + i + '_choice');
|
||||
var selectedvalue = selector.get('value');
|
||||
selector.all('option').remove(true);
|
||||
for (var value in dragitemsoptions) {
|
||||
value = +value;
|
||||
var option = '<option value="' + value + '">' + dragitemsoptions[value] + '</option>';
|
||||
selector.append(option);
|
||||
var optionnode = selector.one('option[value="' + value + '"]');
|
||||
if (value === +selectedvalue) {
|
||||
optionnode.set('selected', true);
|
||||
} else {
|
||||
if (value !== 0) { // No item option is always selectable.
|
||||
var cbel = Y.one('#id_drags_' + (value - 1) + '_infinite');
|
||||
if (cbel && !cbel.get('checked')) {
|
||||
if (this.item_is_allocated_to_dropzone(value)) {
|
||||
optionnode.set('disabled', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
stop_selector_events: function() {
|
||||
Y.all('fieldset#id_dropzoneheader select').detachAll();
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the specified drag item is allocated to a dropzone.
|
||||
*
|
||||
* @method item_is_allocated_to_dropzone
|
||||
* @param {Number} value of the drag item to check
|
||||
* @return {Boolean} true if item is allocated to dropzone
|
||||
*/
|
||||
item_is_allocated_to_dropzone: function(itemvalue) {
|
||||
return Y.all('fieldset#id_dropzoneheader select').some(function(selectNode) {
|
||||
return Number(selectNode.get('value')) === itemvalue;
|
||||
});
|
||||
},
|
||||
|
||||
setup_form_events: function() {
|
||||
// Events triggered by changes to form data.
|
||||
|
||||
// X and y coordinates.
|
||||
Y.all('fieldset#id_dropzoneheader input').on('blur', function(e) {
|
||||
var name = e.target.getAttribute('name');
|
||||
var draginstanceno = this.form.from_name_with_index(name).indexes[0];
|
||||
var fromform = [this.form.get_form_value('drops', [draginstanceno, 'xleft']),
|
||||
this.form.get_form_value('drops', [draginstanceno, 'ytop'])];
|
||||
var constrainedxy = this.constrain_xy(draginstanceno, fromform);
|
||||
this.form.set_form_value('drops', [draginstanceno, 'xleft'], constrainedxy[0]);
|
||||
this.form.set_form_value('drops', [draginstanceno, 'ytop'], constrainedxy[1]);
|
||||
}, this);
|
||||
|
||||
// Change in selected item.
|
||||
Y.all('fieldset#id_dropzoneheader select').on('change', function(e) {
|
||||
var name = e.target.getAttribute('name');
|
||||
var draginstanceno = this.form.from_name_with_index(name).indexes[0];
|
||||
var old = this.doc.drag_item(draginstanceno);
|
||||
if (old !== null) {
|
||||
old.remove(true);
|
||||
}
|
||||
this.draw_dd_area();
|
||||
}, this);
|
||||
|
||||
for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
|
||||
// Change to group selector.
|
||||
Y.all('#fgroup_id_drags_' + i + ' select.draggroup').on('change', this.redraw_dd_area, this);
|
||||
Y.all('#fgroup_id_drags_' + i + ' select.dragitemtype').on('change', this.redraw_dd_area, this);
|
||||
Y.all('fieldset#draggableitemheader_' + i + ' input[type="text"]')
|
||||
.on('blur', this.set_options_for_drag_item_selectors, this);
|
||||
// Change to infinite checkbox.
|
||||
Y.all('fieldset#draggableitemheader_' + i + ' input[type="checkbox"]')
|
||||
.on('change', this.set_options_for_drag_item_selectors, this);
|
||||
}
|
||||
// Event on file picker new file selection.
|
||||
Y.after(function(e) {
|
||||
var name = this.fp.name(e.id);
|
||||
if (name !== 'bgimage') {
|
||||
this.doc.drag_items().remove(true);
|
||||
}
|
||||
this.draw_dd_area();
|
||||
}, M.form_filepicker, 'callback', this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Redraws drag and drop preview area.
|
||||
*
|
||||
* @method redraw_dd_area
|
||||
*/
|
||||
redraw_dd_area: function() {
|
||||
this.doc.drag_items().remove(true);
|
||||
this.draw_dd_area();
|
||||
},
|
||||
|
||||
update_visibility_of_file_pickers: function() {
|
||||
for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
|
||||
if ('image' === this.form.get_form_value('drags', [i, 'dragitemtype'])) {
|
||||
Y.one('input#id_dragitem_' + i).get('parentNode').get('parentNode')
|
||||
.setStyle('display', 'block');
|
||||
} else {
|
||||
Y.one('input#id_dragitem_' + i).get('parentNode').get('parentNode')
|
||||
.setStyle('display', 'none');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
reposition_drags_for_form: function() {
|
||||
this.doc.drag_items().each(function(drag) {
|
||||
var draginstanceno = drag.getData('draginstanceno');
|
||||
this.reposition_drag_for_form(draginstanceno);
|
||||
}, this);
|
||||
M.util.js_complete(this.pendingid);
|
||||
},
|
||||
|
||||
reposition_drag_for_form: function(draginstanceno) {
|
||||
var drag = this.doc.drag_item(draginstanceno);
|
||||
if (null !== drag && !drag.hasClass('yui3-dd-dragging')) {
|
||||
var fromform = [this.form.get_form_value('drops', [draginstanceno, 'xleft']),
|
||||
this.form.get_form_value('drops', [draginstanceno, 'ytop'])];
|
||||
if (fromform[0] === '' && fromform[1] === '') {
|
||||
var dragitemno = drag.getData('dragitemno');
|
||||
drag.setXY(this.doc.drag_item_home(dragitemno).getXY());
|
||||
} else {
|
||||
drag.setXY(this.convert_to_window_xy(fromform));
|
||||
}
|
||||
}
|
||||
},
|
||||
set_drag_xy: function(draginstanceno, xy) {
|
||||
xy = this.constrain_xy(draginstanceno, this.convert_to_bg_img_xy(xy));
|
||||
this.form.set_form_value('drops', [draginstanceno, 'xleft'], Math.round(xy[0]));
|
||||
this.form.set_form_value('drops', [draginstanceno, 'ytop'], Math.round(xy[1]));
|
||||
},
|
||||
reset_drag_xy: function(draginstanceno) {
|
||||
this.form.set_form_value('drops', [draginstanceno, 'xleft'], '');
|
||||
this.form.set_form_value('drops', [draginstanceno, 'ytop'], '');
|
||||
},
|
||||
|
||||
// make sure xy value is not out of bounds of bg image
|
||||
constrain_xy: function(draginstanceno, bgimgxy) {
|
||||
var drag = this.doc.drag_item(draginstanceno);
|
||||
var xleftconstrained =
|
||||
Math.min(bgimgxy[0], this.doc.bg_img().get('width') - drag.get('offsetWidth'));
|
||||
var ytopconstrained =
|
||||
Math.min(bgimgxy[1], this.doc.bg_img().get('height') - drag.get('offsetHeight'));
|
||||
xleftconstrained = Math.max(xleftconstrained, 0);
|
||||
ytopconstrained = Math.max(ytopconstrained, 0);
|
||||
return [xleftconstrained, ytopconstrained];
|
||||
},
|
||||
convert_to_bg_img_xy: function(windowxy) {
|
||||
return [Number(windowxy[0]) - this.doc.bg_img().getX() - 1,
|
||||
Number(windowxy[1]) - this.doc.bg_img().getY() - 1];
|
||||
},
|
||||
|
||||
/**
|
||||
* Low level operations on form.
|
||||
*/
|
||||
form: {
|
||||
to_name_with_index: function(name, indexes) {
|
||||
var indexstring = name;
|
||||
for (var i = 0; i < indexes.length; i++) {
|
||||
indexstring = indexstring + '[' + indexes[i] + ']';
|
||||
}
|
||||
return indexstring;
|
||||
},
|
||||
get_el: function(name, indexes) {
|
||||
var form = document.getElementById('mform1');
|
||||
return form.elements[this.to_name_with_index(name, indexes)];
|
||||
},
|
||||
get_form_value: function(name, indexes) {
|
||||
var el = this.get_el(name, indexes);
|
||||
if (el.type === 'checkbox') {
|
||||
return el.checked;
|
||||
} else {
|
||||
return el.value;
|
||||
}
|
||||
},
|
||||
set_form_value: function(name, indexes, value) {
|
||||
var el = this.get_el(name, indexes);
|
||||
if (el.type === 'checkbox') {
|
||||
el.checked = value;
|
||||
} else {
|
||||
el.value = value;
|
||||
}
|
||||
},
|
||||
from_name_with_index: function(name) {
|
||||
var toreturn = {};
|
||||
toreturn.indexes = [];
|
||||
var bracket = name.indexOf('[');
|
||||
toreturn.name = name.substring(0, bracket);
|
||||
while (bracket !== -1) {
|
||||
var end = name.indexOf(']', bracket + 1);
|
||||
toreturn.indexes.push(name.substring(bracket + 1, end));
|
||||
bracket = name.indexOf('[', end + 1);
|
||||
}
|
||||
return toreturn;
|
||||
}
|
||||
},
|
||||
|
||||
file_pickers: function() {
|
||||
var draftitemidstoname;
|
||||
var nametoparentnode;
|
||||
if (draftitemidstoname === undefined) {
|
||||
draftitemidstoname = {};
|
||||
nametoparentnode = {};
|
||||
var filepickers = Y.all('form.mform input.filepickerhidden');
|
||||
filepickers.each(function(filepicker) {
|
||||
draftitemidstoname[filepicker.get('value')] = filepicker.get('name');
|
||||
nametoparentnode[filepicker.get('name')] = filepicker.get('parentNode');
|
||||
}, this);
|
||||
}
|
||||
var toreturn = {
|
||||
file: function(name) {
|
||||
var parentnode = nametoparentnode[name];
|
||||
var fileanchor = parentnode.one('div.filepicker-filelist a');
|
||||
if (fileanchor) {
|
||||
return {href: fileanchor.get('href'), name: fileanchor.get('innerHTML')};
|
||||
} else {
|
||||
return {href: null, name: null};
|
||||
}
|
||||
},
|
||||
name: function(draftitemid) {
|
||||
return draftitemidstoname[draftitemid];
|
||||
}
|
||||
};
|
||||
return toreturn;
|
||||
}
|
||||
}, {NAME: DDIMAGEORTEXTFORMNAME, ATTRS: {maxsizes: {value: null}}});
|
||||
M.qtype_ddimageortext = M.qtype_ddimageortext || {};
|
||||
M.qtype_ddimageortext.init_form = function(config) {
|
||||
return new DDIMAGEORTEXT_FORM(config);
|
||||
};
|
||||
|
||||
|
||||
}, '@VERSION@', {"requires": ["moodle-qtype_ddimageortext-dd", "form_filepicker"]});
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "moodle-qtype_ddimageortext-dd",
|
||||
"builds": {
|
||||
"moodle-qtype_ddimageortext-dd": {
|
||||
"jsfiles": [
|
||||
"ddimageortext.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,541 +0,0 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
var DDIMAGEORTEXTDDNAME = 'ddimageortext_dd';
|
||||
var DDIMAGEORTEXT_DD = function() {
|
||||
DDIMAGEORTEXT_DD.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the base class for the question rendering and question editing form code.
|
||||
*/
|
||||
Y.extend(DDIMAGEORTEXT_DD, Y.Base, {
|
||||
doc: null,
|
||||
polltimer: null,
|
||||
afterimageloaddone: false,
|
||||
poll_for_image_load: function(e, waitforimageconstrain, pause, doafterwords) {
|
||||
if (this.afterimageloaddone) {
|
||||
return;
|
||||
}
|
||||
var bgdone = this.doc.bg_img().get('complete');
|
||||
if (waitforimageconstrain) {
|
||||
bgdone = bgdone && this.doc.bg_img().hasClass('constrained');
|
||||
}
|
||||
var alldragsloaded = !this.doc.drag_item_homes().some(function(dragitemhome) {
|
||||
// in 'some' loop returning true breaks the loop and is passed as return value from
|
||||
// 'some' else returns false. Can be though of as equivalent to ||.
|
||||
if (dragitemhome.get('tagName') !== 'IMG') {
|
||||
return false;
|
||||
}
|
||||
var done = (dragitemhome.get('complete'));
|
||||
if (waitforimageconstrain) {
|
||||
done = done && dragitemhome.hasClass('constrained');
|
||||
}
|
||||
return !done;
|
||||
});
|
||||
if (bgdone && alldragsloaded) {
|
||||
if (this.polltimer !== null) {
|
||||
this.polltimer.cancel();
|
||||
this.polltimer = null;
|
||||
}
|
||||
this.doc.drag_item_homes().detach('load', this.poll_for_image_load);
|
||||
this.doc.bg_img().detach('load', this.poll_for_image_load);
|
||||
if (pause !== 0) {
|
||||
Y.later(pause, this, doafterwords);
|
||||
} else {
|
||||
doafterwords.call(this);
|
||||
}
|
||||
this.afterimageloaddone = true;
|
||||
} else if (this.polltimer === null) {
|
||||
var pollarguments = [null, waitforimageconstrain, pause, doafterwords];
|
||||
this.polltimer =
|
||||
Y.later(1000, this, this.poll_for_image_load, pollarguments, true);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Object to encapsulate operations on dd area.
|
||||
*/
|
||||
doc_structure: function(mainobj) {
|
||||
var topnode = Y.one(this.get('topnode'));
|
||||
var dragitemsarea = topnode.one('div.dragitems');
|
||||
var dropbgarea = topnode.one('div.droparea');
|
||||
return {
|
||||
top_node: function() {
|
||||
return topnode;
|
||||
},
|
||||
drag_items: function() {
|
||||
return dragitemsarea.all('.drag');
|
||||
},
|
||||
drop_zones: function() {
|
||||
return topnode.all('div.dropzones div.dropzone');
|
||||
},
|
||||
drop_zone_group: function(groupno) {
|
||||
return topnode.all('div.dropzones div.group' + groupno);
|
||||
},
|
||||
drag_items_cloned_from: function(dragitemno) {
|
||||
return dragitemsarea.all('.dragitems' + dragitemno);
|
||||
},
|
||||
drag_item: function(draginstanceno) {
|
||||
return dragitemsarea.one('.draginstance' + draginstanceno);
|
||||
},
|
||||
drag_items_in_group: function(groupno) {
|
||||
return dragitemsarea.all('.drag.group' + groupno);
|
||||
},
|
||||
drag_item_homes: function() {
|
||||
return dragitemsarea.all('.draghome');
|
||||
},
|
||||
bg_img: function() {
|
||||
return topnode.one('.dropbackground');
|
||||
},
|
||||
load_bg_img: function(url) {
|
||||
dropbgarea.setContent('<img class="dropbackground" src="' + url + '"/>');
|
||||
this.bg_img().on('load', this.on_image_load, this, 'bg_image');
|
||||
},
|
||||
add_or_update_drag_item_home: function(dragitemno, url, alt, group) {
|
||||
var oldhome = this.drag_item_home(dragitemno);
|
||||
var classes = 'draghome dragitemhomes' + dragitemno + ' group' + group;
|
||||
var imghtml = '<img class="' + classes + '" src="' + url + '" alt="' + alt + '" />';
|
||||
var divhtml = '<div class="' + classes + '">' + alt + '</div>';
|
||||
if (oldhome === null) {
|
||||
if (url) {
|
||||
dragitemsarea.append(imghtml);
|
||||
} else if (alt !== '') {
|
||||
dragitemsarea.append(divhtml);
|
||||
}
|
||||
} else {
|
||||
if (url) {
|
||||
dragitemsarea.insert(imghtml, oldhome);
|
||||
} else if (alt !== '') {
|
||||
dragitemsarea.insert(divhtml, oldhome);
|
||||
}
|
||||
oldhome.remove(true);
|
||||
}
|
||||
var newlycreated = dragitemsarea.one('.dragitemhomes' + dragitemno);
|
||||
if (newlycreated !== null) {
|
||||
newlycreated.setData('groupno', group);
|
||||
newlycreated.setData('dragitemno', dragitemno);
|
||||
}
|
||||
},
|
||||
drag_item_home: function(dragitemno) {
|
||||
return dragitemsarea.one('.dragitemhomes' + dragitemno);
|
||||
},
|
||||
get_classname_numeric_suffix: function(node, prefix) {
|
||||
var classes = node.getAttribute('class');
|
||||
if (classes !== '') {
|
||||
var classesarr = classes.split(' ');
|
||||
for (var index = 0; index < classesarr.length; index++) {
|
||||
var patt1 = new RegExp('^' + prefix + '([0-9])+$');
|
||||
if (patt1.test(classesarr[index])) {
|
||||
var patt2 = new RegExp('([0-9])+$');
|
||||
var match = patt2.exec(classesarr[index]);
|
||||
return +match[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
throw 'Prefix "' + prefix + '" not found in class names.';
|
||||
},
|
||||
clone_new_drag_item: function(draginstanceno, dragitemno) {
|
||||
var draghome = this.drag_item_home(dragitemno);
|
||||
if (draghome === null) {
|
||||
return null;
|
||||
}
|
||||
var drag = draghome.cloneNode(true);
|
||||
drag.removeClass('dragitemhomes' + dragitemno);
|
||||
drag.addClass('dragitems' + dragitemno);
|
||||
drag.addClass('draginstance' + draginstanceno);
|
||||
drag.removeClass('draghome');
|
||||
drag.addClass('drag');
|
||||
drag.setStyles({'visibility': 'visible', 'position': 'absolute'});
|
||||
drag.setData('draginstanceno', draginstanceno);
|
||||
drag.setData('dragitemno', dragitemno);
|
||||
draghome.get('parentNode').appendChild(drag);
|
||||
return drag;
|
||||
},
|
||||
draggable_for_question: function(drag, group, choice) {
|
||||
new Y.DD.Drag({
|
||||
node: drag,
|
||||
dragMode: 'point',
|
||||
groups: [group]
|
||||
}).plug(Y.Plugin.DDConstrained, {constrain2node: topnode});
|
||||
|
||||
drag.setData('group', group);
|
||||
drag.setData('choice', choice);
|
||||
},
|
||||
draggable_for_form: function(drag) {
|
||||
var dd = new Y.DD.Drag({
|
||||
node: drag,
|
||||
dragMode: 'point'
|
||||
}).plug(Y.Plugin.DDConstrained, {constrain2node: topnode});
|
||||
dd.on('drag:end', function(e) {
|
||||
var dragnode = e.target.get('node');
|
||||
var draginstanceno = dragnode.getData('draginstanceno');
|
||||
var gooddrop = dragnode.getData('gooddrop');
|
||||
|
||||
if (!gooddrop) {
|
||||
mainobj.reset_drag_xy(draginstanceno);
|
||||
} else {
|
||||
mainobj.set_drag_xy(draginstanceno, [e.pageX, e.pageY]);
|
||||
}
|
||||
}, this);
|
||||
dd.on('drag:start', function(e) {
|
||||
var drag = e.target;
|
||||
drag.get('node').setData('gooddrop', false);
|
||||
}, this);
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
},
|
||||
|
||||
update_padding_sizes_all: function() {
|
||||
for (var groupno = 1; groupno <= 8; groupno++) {
|
||||
this.update_padding_size_for_group(groupno);
|
||||
}
|
||||
},
|
||||
update_padding_size_for_group: function(groupno) {
|
||||
var groupitems = this.doc.top_node().all('.draghome.group' + groupno);
|
||||
if (groupitems.size() !== 0) {
|
||||
var maxwidth = 0;
|
||||
var maxheight = 0;
|
||||
groupitems.each(function(item) {
|
||||
maxwidth = Math.max(maxwidth, item.get('clientWidth'));
|
||||
maxheight = Math.max(maxheight, item.get('clientHeight'));
|
||||
}, this);
|
||||
groupitems.each(function(item) {
|
||||
var margintopbottom = Math.round((10 + maxheight - item.get('clientHeight')) / 2);
|
||||
var marginleftright = Math.round((10 + maxwidth - item.get('clientWidth')) / 2);
|
||||
item.setStyle('padding', margintopbottom + 'px ' + marginleftright + 'px ' +
|
||||
margintopbottom + 'px ' + marginleftright + 'px');
|
||||
}, this);
|
||||
this.doc.drop_zone_group(groupno).setStyles({'width': maxwidth + 10,
|
||||
'height': maxheight + 10});
|
||||
}
|
||||
},
|
||||
convert_to_window_xy: function(bgimgxy) {
|
||||
return [Number(bgimgxy[0]) + this.doc.bg_img().getX() + 1,
|
||||
Number(bgimgxy[1]) + this.doc.bg_img().getY() + 1];
|
||||
}
|
||||
}, {
|
||||
NAME: DDIMAGEORTEXTDDNAME,
|
||||
ATTRS: {
|
||||
drops: {value: null},
|
||||
readonly: {value: false},
|
||||
topnode: {value: null}
|
||||
}
|
||||
});
|
||||
|
||||
M.qtype_ddimageortext = M.qtype_ddimageortext || {};
|
||||
M.qtype_ddimageortext.dd_base_class = DDIMAGEORTEXT_DD;
|
||||
|
||||
var DDIMAGEORTEXTQUESTIONNAME = 'ddimageortext_question';
|
||||
var DDIMAGEORTEXT_QUESTION = function() {
|
||||
DDIMAGEORTEXT_QUESTION.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
/**
|
||||
* This is the code for question rendering.
|
||||
*/
|
||||
Y.extend(DDIMAGEORTEXT_QUESTION, M.qtype_ddimageortext.dd_base_class, {
|
||||
passiveSupported: false,
|
||||
pendingid: '',
|
||||
initializer: function() {
|
||||
this.pendingid = 'qtype_ddimageortext-' + Math.random().toString(36).slice(2); // Random string.
|
||||
M.util.js_pending(this.pendingid);
|
||||
this.doc = this.doc_structure(this);
|
||||
this.poll_for_image_load(null, false, 10, this.create_all_drag_and_drops);
|
||||
this.doc.bg_img().after('load', this.poll_for_image_load, this,
|
||||
false, 10, this.create_all_drag_and_drops);
|
||||
this.doc.drag_item_homes().after('load', this.poll_for_image_load, this,
|
||||
false, 10, this.create_all_drag_and_drops);
|
||||
if (!this.get('readonly')) {
|
||||
Y.later(500, this, this.reposition_drags_for_question, true);
|
||||
} else {
|
||||
Y.one('window').on('resize', function() {
|
||||
this.reposition_drags_for_question();
|
||||
}, this);
|
||||
}
|
||||
this.checkPassiveSupported();
|
||||
},
|
||||
|
||||
/**
|
||||
* prevent_touchmove_from_scrolling allows users of touch screen devices to
|
||||
* use drag and drop and normal scrolling at the same time. I.e. when
|
||||
* touching and dragging a draggable item, the screen does not scroll, but
|
||||
* you can scroll by touching other area of the screen apart from the
|
||||
* draggable items.
|
||||
*/
|
||||
prevent_touchmove_from_scrolling: function(drag) {
|
||||
var touchmove = (Y.UA.ie) ? 'MSPointerMove' : 'touchmove';
|
||||
var eventHandler = function(event) {
|
||||
event.preventDefault();
|
||||
};
|
||||
var dragId = drag.get('id');
|
||||
var el = document.getElementById(dragId);
|
||||
// Note do not dynamically add events within another event, as this causes issues on iOS11.3.
|
||||
// See https://github.com/atlassian/react-beautiful-dnd/issues/413 and
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=184250 for fuller explanation.
|
||||
el.addEventListener(touchmove, eventHandler, this.passiveSupported ? {passive: false, capture: true} : false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Some older browsers do not support passing an options object to addEventListener.
|
||||
* This is a check from https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener.
|
||||
*/
|
||||
checkPassiveSupported: function() {
|
||||
try {
|
||||
var options = Object.defineProperty({}, 'passive', {
|
||||
get: function() {
|
||||
this.passiveSupported = true;
|
||||
}.bind(this)
|
||||
});
|
||||
window.addEventListener('test', options, options);
|
||||
window.removeEventListener('test', options, options);
|
||||
} catch (err) {
|
||||
this.passiveSupported = false;
|
||||
}
|
||||
},
|
||||
|
||||
create_all_drag_and_drops: function() {
|
||||
this.init_drops();
|
||||
this.update_padding_sizes_all();
|
||||
var i = 0;
|
||||
this.doc.drag_item_homes().each(function(dragitemhome) {
|
||||
var dragitemno = Number(this.doc.get_classname_numeric_suffix(dragitemhome, 'dragitemhomes'));
|
||||
var choice = +this.doc.get_classname_numeric_suffix(dragitemhome, 'choice');
|
||||
var group = +this.doc.get_classname_numeric_suffix(dragitemhome, 'group');
|
||||
var groupsize = this.doc.drop_zone_group(group).size();
|
||||
var dragnode = this.doc.clone_new_drag_item(i, dragitemno);
|
||||
i++;
|
||||
if (!this.get('readonly')) {
|
||||
this.doc.draggable_for_question(dragnode, group, choice);
|
||||
|
||||
// Prevent scrolling whilst dragging on Adroid devices.
|
||||
this.prevent_touchmove_from_scrolling(dragnode);
|
||||
}
|
||||
if (dragnode.hasClass('infinite')) {
|
||||
var dragstocreate = groupsize - 1;
|
||||
while (dragstocreate > 0) {
|
||||
dragnode = this.doc.clone_new_drag_item(i, dragitemno);
|
||||
i++;
|
||||
if (!this.get('readonly')) {
|
||||
this.doc.draggable_for_question(dragnode, group, choice);
|
||||
|
||||
// Prevent scrolling whilst dragging on Adroid devices.
|
||||
this.prevent_touchmove_from_scrolling(dragnode);
|
||||
}
|
||||
dragstocreate--;
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
this.reposition_drags_for_question();
|
||||
if (!this.get('readonly')) {
|
||||
this.doc.drop_zones().set('tabIndex', 0);
|
||||
this.doc.drop_zones().each(
|
||||
function(v) {
|
||||
v.on('dragchange', this.drop_zone_key_press, this);
|
||||
}, this);
|
||||
}
|
||||
M.util.js_complete(this.pendingid);
|
||||
},
|
||||
drop_zone_key_press: function(e) {
|
||||
switch (e.direction) {
|
||||
case 'next' :
|
||||
this.place_next_drag_in(e.target);
|
||||
break;
|
||||
case 'previous' :
|
||||
this.place_previous_drag_in(e.target);
|
||||
break;
|
||||
case 'remove' :
|
||||
this.remove_drag_from_drop(e.target);
|
||||
break;
|
||||
}
|
||||
e.preventDefault();
|
||||
this.reposition_drags_for_question();
|
||||
},
|
||||
place_next_drag_in: function(drop) {
|
||||
this.search_for_unplaced_drop_choice(drop, 1);
|
||||
},
|
||||
place_previous_drag_in: function(drop) {
|
||||
this.search_for_unplaced_drop_choice(drop, -1);
|
||||
},
|
||||
search_for_unplaced_drop_choice: function(drop, direction) {
|
||||
var next;
|
||||
var current = this.current_drag_in_drop(drop);
|
||||
if ('' === current) {
|
||||
if (direction === 1) {
|
||||
next = 1;
|
||||
} else {
|
||||
next = 1;
|
||||
var groupno = drop.getData('group');
|
||||
this.doc.drag_items_in_group(groupno).each(function(drag) {
|
||||
next = Math.max(next, drag.getData('choice'));
|
||||
}, this);
|
||||
}
|
||||
} else {
|
||||
next = +current + direction;
|
||||
}
|
||||
var drag;
|
||||
do {
|
||||
if (this.get_choices_for_drop(next, drop).size() === 0) {
|
||||
this.remove_drag_from_drop(drop);
|
||||
return;
|
||||
} else {
|
||||
drag = this.get_unplaced_choice_for_drop(next, drop);
|
||||
}
|
||||
next = next + direction;
|
||||
} while (drag === null);
|
||||
this.place_drag_in_drop(drag, drop);
|
||||
},
|
||||
current_drag_in_drop: function(drop) {
|
||||
var inputid = drop.getData('inputid');
|
||||
var inputnode = Y.one('input#' + inputid);
|
||||
return inputnode.get('value');
|
||||
},
|
||||
remove_drag_from_drop: function(drop) {
|
||||
this.place_drag_in_drop(null, drop);
|
||||
},
|
||||
place_drag_in_drop: function(drag, drop) {
|
||||
var inputid = drop.getData('inputid');
|
||||
var inputnode = Y.one('input#' + inputid);
|
||||
if (drag !== null) {
|
||||
inputnode.set('value', drag.getData('choice'));
|
||||
} else {
|
||||
inputnode.set('value', '');
|
||||
}
|
||||
},
|
||||
reposition_drags_for_question: function(dotimeout) {
|
||||
this.doc.drag_items().removeClass('placed');
|
||||
this.doc.drag_items().each(function(dragitem) {
|
||||
if (dragitem.dd !== undefined) {
|
||||
dragitem.dd.detachAll('drag:start');
|
||||
}
|
||||
}, this);
|
||||
this.doc.drop_zones().each(function(dropzone) {
|
||||
var relativexy = dropzone.getData('xy');
|
||||
dropzone.setXY(this.convert_to_window_xy(relativexy));
|
||||
var inputcss = 'input#' + dropzone.getData('inputid');
|
||||
var input = this.doc.top_node().one(inputcss);
|
||||
var choice = input.get('value');
|
||||
if (choice !== "") {
|
||||
var dragitem = this.get_unplaced_choice_for_drop(choice, dropzone);
|
||||
if (dragitem !== null) {
|
||||
dragitem.setXY(dropzone.getXY());
|
||||
dragitem.addClass('placed');
|
||||
if (dragitem.dd !== undefined) {
|
||||
dragitem.dd.once('drag:start', function(e, input) {
|
||||
input.set('value', '');
|
||||
e.target.get('node').removeClass('placed');
|
||||
}, this, input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, this);
|
||||
this.doc.drag_items().each(function(dragitem) {
|
||||
if (!dragitem.hasClass('placed') && !dragitem.hasClass('yui3-dd-dragging')) {
|
||||
var dragitemhome = this.doc.drag_item_home(dragitem.getData('dragitemno'));
|
||||
dragitem.setXY(dragitemhome.getXY());
|
||||
}
|
||||
}, this);
|
||||
if (dotimeout) {
|
||||
Y.later(500, this, this.reposition_drags_for_question, true);
|
||||
}
|
||||
},
|
||||
get_choices_for_drop: function(choice, drop) {
|
||||
var group = drop.getData('group');
|
||||
return this.doc.top_node().all(
|
||||
'div.dragitemgroup' + group + ' .choice' + choice + '.drag');
|
||||
},
|
||||
get_unplaced_choice_for_drop: function(choice, drop) {
|
||||
var dragitems = this.get_choices_for_drop(choice, drop);
|
||||
var dragitem = null;
|
||||
dragitems.some(function(d) {
|
||||
if (this.get('readonly') || (!d.hasClass('placed') && !d.hasClass('yui3-dd-dragging'))) {
|
||||
dragitem = d;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
return dragitem;
|
||||
},
|
||||
init_drops: function() {
|
||||
var dropareas = this.doc.top_node().one('div.dropzones');
|
||||
var groupnodes = {};
|
||||
for (var groupno = 1; groupno <= 8; groupno++) {
|
||||
var groupnode = Y.Node.create('<div class = "dropzonegroup' + groupno + '"></div>');
|
||||
dropareas.append(groupnode);
|
||||
groupnodes[groupno] = groupnode;
|
||||
}
|
||||
var drop_hit_handler = function(e) {
|
||||
var drag = e.drag.get('node');
|
||||
var drop = e.drop.get('node');
|
||||
if (Number(drop.getData('group')) === drag.getData('group')) {
|
||||
this.place_drag_in_drop(drag, drop);
|
||||
}
|
||||
};
|
||||
for (var dropno in this.get('drops')) {
|
||||
var drop = this.get('drops')[dropno];
|
||||
var nodeclass = 'dropzone group' + drop.group + ' place' + dropno;
|
||||
var title = drop.text.replace('"', '\"');
|
||||
if (!title) {
|
||||
title = M.util.get_string('blank', 'qtype_ddimageortext');
|
||||
}
|
||||
var dropnodehtml = '<div title="' + title + '" class="' + nodeclass + '">' +
|
||||
'<span class="accesshide">' + title + '</span> </div>';
|
||||
var dropnode = Y.Node.create(dropnodehtml);
|
||||
groupnodes[drop.group].append(dropnode);
|
||||
dropnode.setStyles({'opacity': 0.5});
|
||||
dropnode.setData('xy', drop.xy);
|
||||
dropnode.setData('place', dropno);
|
||||
dropnode.setData('inputid', drop.fieldname.replace(':', '_'));
|
||||
dropnode.setData('group', drop.group);
|
||||
var dropdd = new Y.DD.Drop({
|
||||
node: dropnode, groups: [drop.group]});
|
||||
dropdd.on('drop:hit', drop_hit_handler, this);
|
||||
}
|
||||
}
|
||||
}, {NAME: DDIMAGEORTEXTQUESTIONNAME, ATTRS: {}});
|
||||
|
||||
Y.Event.define('dragchange', {
|
||||
// Webkit and IE repeat keydown when you hold down arrow keys.
|
||||
// Opera links keypress to page scroll; others keydown.
|
||||
// Firefox prevents page scroll via preventDefault() on either
|
||||
// keydown or keypress.
|
||||
_event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
|
||||
|
||||
_keys: {
|
||||
'32': 'next', // Space
|
||||
'37': 'previous', // Left arrow
|
||||
'38': 'previous', // Up arrow
|
||||
'39': 'next', // Right arrow
|
||||
'40': 'next', // Down arrow
|
||||
'27': 'remove' // Escape
|
||||
},
|
||||
|
||||
_keyHandler: function(e, notifier) {
|
||||
if (this._keys[e.keyCode]) {
|
||||
e.direction = this._keys[e.keyCode];
|
||||
notifier.fire(e);
|
||||
}
|
||||
},
|
||||
|
||||
on: function(node, sub, notifier) {
|
||||
sub._detacher = node.on(this._event, this._keyHandler,
|
||||
this, notifier);
|
||||
}
|
||||
});
|
||||
|
||||
M.qtype_ddimageortext.init_question = function(config) {
|
||||
return new DDIMAGEORTEXT_QUESTION(config);
|
||||
};
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"moodle-qtype_ddimageortext-dd": {
|
||||
"requires": [
|
||||
"node",
|
||||
"dd",
|
||||
"dd-drop",
|
||||
"dd-constrain"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "moodle-qtype_ddimageortext-form",
|
||||
"builds": {
|
||||
"moodle-qtype_ddimageortext-form": {
|
||||
"jsfiles": [
|
||||
"form.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
-364
@@ -1,364 +0,0 @@
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* This is the question editing form code.
|
||||
*/
|
||||
var DDIMAGEORTEXTFORMNAME = 'moodle-qtype_ddimageortext-form';
|
||||
var DDIMAGEORTEXT_FORM = function() {
|
||||
DDIMAGEORTEXT_FORM.superclass.constructor.apply(this, arguments);
|
||||
};
|
||||
|
||||
Y.extend(DDIMAGEORTEXT_FORM, M.qtype_ddimageortext.dd_base_class, {
|
||||
pendingid: '',
|
||||
fp: null,
|
||||
|
||||
initializer: function() {
|
||||
this.pendingid = 'qtype_ddimageortext-form-' + Math.random().toString(36).slice(2); // Random string.
|
||||
M.util.js_pending(this.pendingid);
|
||||
this.fp = this.file_pickers();
|
||||
var tn = Y.one(this.get('topnode'));
|
||||
tn.one('div.fcontainer').append('<div class="ddarea"><div class="droparea"></div><div class="dragitems"></div>' +
|
||||
'<div class="dropzones"></div></div>');
|
||||
this.doc = this.doc_structure(this);
|
||||
this.draw_dd_area();
|
||||
},
|
||||
|
||||
draw_dd_area: function() {
|
||||
var bgimageurl = this.fp.file('bgimage').href;
|
||||
this.stop_selector_events();
|
||||
this.set_options_for_drag_item_selectors();
|
||||
if (bgimageurl !== null) {
|
||||
this.doc.load_bg_img(bgimageurl);
|
||||
this.load_drag_homes();
|
||||
|
||||
var drop = new Y.DD.Drop({
|
||||
node: this.doc.bg_img()
|
||||
});
|
||||
// Listen for a drop:hit on the background image
|
||||
drop.on('drop:hit', function(e) {
|
||||
e.drag.get('node').setData('gooddrop', true);
|
||||
});
|
||||
|
||||
this.afterimageloaddone = false;
|
||||
this.doc.bg_img().on('load', this.constrain_image_size, this, 'bgimage');
|
||||
this.doc.drag_item_homes()
|
||||
.on('load', this.constrain_image_size, this, 'dragimage');
|
||||
this.doc.bg_img().after('load', this.poll_for_image_load, this,
|
||||
true, 0, this.after_all_images_loaded);
|
||||
this.doc.drag_item_homes().after('load', this.poll_for_image_load, this,
|
||||
true, 0, this.after_all_images_loaded);
|
||||
} else {
|
||||
this.setup_form_events();
|
||||
M.util.js_complete(this.pendingid);
|
||||
}
|
||||
this.update_visibility_of_file_pickers();
|
||||
},
|
||||
|
||||
after_all_images_loaded: function() {
|
||||
this.update_padding_sizes_all();
|
||||
this.update_drag_instances();
|
||||
this.reposition_drags_for_form();
|
||||
this.set_options_for_drag_item_selectors();
|
||||
this.setup_form_events();
|
||||
Y.later(500, this, this.reposition_drags_for_form, [], true);
|
||||
},
|
||||
|
||||
constrain_image_size: function(e, imagetype) {
|
||||
var maxsize = this.get('maxsizes')[imagetype];
|
||||
var reduceby = Math.max(e.target.get('width') / maxsize.width,
|
||||
e.target.get('height') / maxsize.height);
|
||||
if (reduceby > 1) {
|
||||
e.target.set('width', Math.floor(e.target.get('width') / reduceby));
|
||||
}
|
||||
e.target.addClass('constrained');
|
||||
e.target.detach('load', this.constrain_image_size);
|
||||
},
|
||||
|
||||
load_drag_homes: function() {
|
||||
// Set up drag items homes.
|
||||
for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
|
||||
this.load_drag_home(i);
|
||||
}
|
||||
},
|
||||
|
||||
load_drag_home: function(dragitemno) {
|
||||
var url = null;
|
||||
if ('image' === this.form.get_form_value('drags', [dragitemno, 'dragitemtype'])) {
|
||||
url = this.fp.file(this.form.to_name_with_index('dragitem', [dragitemno])).href;
|
||||
}
|
||||
this.doc.add_or_update_drag_item_home(dragitemno, url,
|
||||
this.form.get_form_value('draglabel', [dragitemno]),
|
||||
this.form.get_form_value('drags', [dragitemno, 'draggroup']));
|
||||
},
|
||||
|
||||
update_drag_instances: function() {
|
||||
// Set up drop zones.
|
||||
for (var i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
|
||||
var dragitemno = this.form.get_form_value('drops', [i, 'choice']);
|
||||
if (dragitemno !== '0' && (this.doc.drag_item(i) === null)) {
|
||||
var drag = this.doc.clone_new_drag_item(i, dragitemno - 1);
|
||||
if (drag !== null) {
|
||||
this.doc.draggable_for_form(drag);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
set_options_for_drag_item_selectors: function() {
|
||||
var dragitemsoptions = {'0': ''};
|
||||
for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
|
||||
var label = this.form.get_form_value('draglabel', [i]);
|
||||
var file = this.fp.file(this.form.to_name_with_index('dragitem', [i]));
|
||||
if ('image' === this.form.get_form_value('drags', [i, 'dragitemtype'])
|
||||
&& file.name !== null) {
|
||||
dragitemsoptions[i + 1] = (i + 1) + '. ' + label + ' (' + file.name + ')';
|
||||
} else if (label !== '') {
|
||||
dragitemsoptions[i + 1] = (i + 1) + '. ' + label;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < this.form.get_form_value('nodropzone', []); i++) {
|
||||
var selector = Y.one('#id_drops_' + i + '_choice');
|
||||
var selectedvalue = selector.get('value');
|
||||
selector.all('option').remove(true);
|
||||
for (var value in dragitemsoptions) {
|
||||
value = +value;
|
||||
var option = '<option value="' + value + '">' + dragitemsoptions[value] + '</option>';
|
||||
selector.append(option);
|
||||
var optionnode = selector.one('option[value="' + value + '"]');
|
||||
if (value === +selectedvalue) {
|
||||
optionnode.set('selected', true);
|
||||
} else {
|
||||
if (value !== 0) { // No item option is always selectable.
|
||||
var cbel = Y.one('#id_drags_' + (value - 1) + '_infinite');
|
||||
if (cbel && !cbel.get('checked')) {
|
||||
if (this.item_is_allocated_to_dropzone(value)) {
|
||||
optionnode.set('disabled', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
stop_selector_events: function() {
|
||||
Y.all('fieldset#id_dropzoneheader select').detachAll();
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the specified drag item is allocated to a dropzone.
|
||||
*
|
||||
* @method item_is_allocated_to_dropzone
|
||||
* @param {Number} value of the drag item to check
|
||||
* @return {Boolean} true if item is allocated to dropzone
|
||||
*/
|
||||
item_is_allocated_to_dropzone: function(itemvalue) {
|
||||
return Y.all('fieldset#id_dropzoneheader select').some(function(selectNode) {
|
||||
return Number(selectNode.get('value')) === itemvalue;
|
||||
});
|
||||
},
|
||||
|
||||
setup_form_events: function() {
|
||||
// Events triggered by changes to form data.
|
||||
|
||||
// X and y coordinates.
|
||||
Y.all('fieldset#id_dropzoneheader input').on('blur', function(e) {
|
||||
var name = e.target.getAttribute('name');
|
||||
var draginstanceno = this.form.from_name_with_index(name).indexes[0];
|
||||
var fromform = [this.form.get_form_value('drops', [draginstanceno, 'xleft']),
|
||||
this.form.get_form_value('drops', [draginstanceno, 'ytop'])];
|
||||
var constrainedxy = this.constrain_xy(draginstanceno, fromform);
|
||||
this.form.set_form_value('drops', [draginstanceno, 'xleft'], constrainedxy[0]);
|
||||
this.form.set_form_value('drops', [draginstanceno, 'ytop'], constrainedxy[1]);
|
||||
}, this);
|
||||
|
||||
// Change in selected item.
|
||||
Y.all('fieldset#id_dropzoneheader select').on('change', function(e) {
|
||||
var name = e.target.getAttribute('name');
|
||||
var draginstanceno = this.form.from_name_with_index(name).indexes[0];
|
||||
var old = this.doc.drag_item(draginstanceno);
|
||||
if (old !== null) {
|
||||
old.remove(true);
|
||||
}
|
||||
this.draw_dd_area();
|
||||
}, this);
|
||||
|
||||
for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
|
||||
// Change to group selector.
|
||||
Y.all('#fgroup_id_drags_' + i + ' select.draggroup').on('change', this.redraw_dd_area, this);
|
||||
Y.all('#fgroup_id_drags_' + i + ' select.dragitemtype').on('change', this.redraw_dd_area, this);
|
||||
Y.all('fieldset#draggableitemheader_' + i + ' input[type="text"]')
|
||||
.on('blur', this.set_options_for_drag_item_selectors, this);
|
||||
// Change to infinite checkbox.
|
||||
Y.all('fieldset#draggableitemheader_' + i + ' input[type="checkbox"]')
|
||||
.on('change', this.set_options_for_drag_item_selectors, this);
|
||||
}
|
||||
// Event on file picker new file selection.
|
||||
Y.after(function(e) {
|
||||
var name = this.fp.name(e.id);
|
||||
if (name !== 'bgimage') {
|
||||
this.doc.drag_items().remove(true);
|
||||
}
|
||||
this.draw_dd_area();
|
||||
}, M.form_filepicker, 'callback', this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Redraws drag and drop preview area.
|
||||
*
|
||||
* @method redraw_dd_area
|
||||
*/
|
||||
redraw_dd_area: function() {
|
||||
this.doc.drag_items().remove(true);
|
||||
this.draw_dd_area();
|
||||
},
|
||||
|
||||
update_visibility_of_file_pickers: function() {
|
||||
for (var i = 0; i < this.form.get_form_value('noitems', []); i++) {
|
||||
if ('image' === this.form.get_form_value('drags', [i, 'dragitemtype'])) {
|
||||
Y.one('input#id_dragitem_' + i).get('parentNode').get('parentNode')
|
||||
.setStyle('display', 'block');
|
||||
} else {
|
||||
Y.one('input#id_dragitem_' + i).get('parentNode').get('parentNode')
|
||||
.setStyle('display', 'none');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
reposition_drags_for_form: function() {
|
||||
this.doc.drag_items().each(function(drag) {
|
||||
var draginstanceno = drag.getData('draginstanceno');
|
||||
this.reposition_drag_for_form(draginstanceno);
|
||||
}, this);
|
||||
M.util.js_complete(this.pendingid);
|
||||
},
|
||||
|
||||
reposition_drag_for_form: function(draginstanceno) {
|
||||
var drag = this.doc.drag_item(draginstanceno);
|
||||
if (null !== drag && !drag.hasClass('yui3-dd-dragging')) {
|
||||
var fromform = [this.form.get_form_value('drops', [draginstanceno, 'xleft']),
|
||||
this.form.get_form_value('drops', [draginstanceno, 'ytop'])];
|
||||
if (fromform[0] === '' && fromform[1] === '') {
|
||||
var dragitemno = drag.getData('dragitemno');
|
||||
drag.setXY(this.doc.drag_item_home(dragitemno).getXY());
|
||||
} else {
|
||||
drag.setXY(this.convert_to_window_xy(fromform));
|
||||
}
|
||||
}
|
||||
},
|
||||
set_drag_xy: function(draginstanceno, xy) {
|
||||
xy = this.constrain_xy(draginstanceno, this.convert_to_bg_img_xy(xy));
|
||||
this.form.set_form_value('drops', [draginstanceno, 'xleft'], Math.round(xy[0]));
|
||||
this.form.set_form_value('drops', [draginstanceno, 'ytop'], Math.round(xy[1]));
|
||||
},
|
||||
reset_drag_xy: function(draginstanceno) {
|
||||
this.form.set_form_value('drops', [draginstanceno, 'xleft'], '');
|
||||
this.form.set_form_value('drops', [draginstanceno, 'ytop'], '');
|
||||
},
|
||||
|
||||
// make sure xy value is not out of bounds of bg image
|
||||
constrain_xy: function(draginstanceno, bgimgxy) {
|
||||
var drag = this.doc.drag_item(draginstanceno);
|
||||
var xleftconstrained =
|
||||
Math.min(bgimgxy[0], this.doc.bg_img().get('width') - drag.get('offsetWidth'));
|
||||
var ytopconstrained =
|
||||
Math.min(bgimgxy[1], this.doc.bg_img().get('height') - drag.get('offsetHeight'));
|
||||
xleftconstrained = Math.max(xleftconstrained, 0);
|
||||
ytopconstrained = Math.max(ytopconstrained, 0);
|
||||
return [xleftconstrained, ytopconstrained];
|
||||
},
|
||||
convert_to_bg_img_xy: function(windowxy) {
|
||||
return [Number(windowxy[0]) - this.doc.bg_img().getX() - 1,
|
||||
Number(windowxy[1]) - this.doc.bg_img().getY() - 1];
|
||||
},
|
||||
|
||||
/**
|
||||
* Low level operations on form.
|
||||
*/
|
||||
form: {
|
||||
to_name_with_index: function(name, indexes) {
|
||||
var indexstring = name;
|
||||
for (var i = 0; i < indexes.length; i++) {
|
||||
indexstring = indexstring + '[' + indexes[i] + ']';
|
||||
}
|
||||
return indexstring;
|
||||
},
|
||||
get_el: function(name, indexes) {
|
||||
var form = document.getElementById('mform1');
|
||||
return form.elements[this.to_name_with_index(name, indexes)];
|
||||
},
|
||||
get_form_value: function(name, indexes) {
|
||||
var el = this.get_el(name, indexes);
|
||||
if (el.type === 'checkbox') {
|
||||
return el.checked;
|
||||
} else {
|
||||
return el.value;
|
||||
}
|
||||
},
|
||||
set_form_value: function(name, indexes, value) {
|
||||
var el = this.get_el(name, indexes);
|
||||
if (el.type === 'checkbox') {
|
||||
el.checked = value;
|
||||
} else {
|
||||
el.value = value;
|
||||
}
|
||||
},
|
||||
from_name_with_index: function(name) {
|
||||
var toreturn = {};
|
||||
toreturn.indexes = [];
|
||||
var bracket = name.indexOf('[');
|
||||
toreturn.name = name.substring(0, bracket);
|
||||
while (bracket !== -1) {
|
||||
var end = name.indexOf(']', bracket + 1);
|
||||
toreturn.indexes.push(name.substring(bracket + 1, end));
|
||||
bracket = name.indexOf('[', end + 1);
|
||||
}
|
||||
return toreturn;
|
||||
}
|
||||
},
|
||||
|
||||
file_pickers: function() {
|
||||
var draftitemidstoname;
|
||||
var nametoparentnode;
|
||||
if (draftitemidstoname === undefined) {
|
||||
draftitemidstoname = {};
|
||||
nametoparentnode = {};
|
||||
var filepickers = Y.all('form.mform input.filepickerhidden');
|
||||
filepickers.each(function(filepicker) {
|
||||
draftitemidstoname[filepicker.get('value')] = filepicker.get('name');
|
||||
nametoparentnode[filepicker.get('name')] = filepicker.get('parentNode');
|
||||
}, this);
|
||||
}
|
||||
var toreturn = {
|
||||
file: function(name) {
|
||||
var parentnode = nametoparentnode[name];
|
||||
var fileanchor = parentnode.one('div.filepicker-filelist a');
|
||||
if (fileanchor) {
|
||||
return {href: fileanchor.get('href'), name: fileanchor.get('innerHTML')};
|
||||
} else {
|
||||
return {href: null, name: null};
|
||||
}
|
||||
},
|
||||
name: function(draftitemid) {
|
||||
return draftitemidstoname[draftitemid];
|
||||
}
|
||||
};
|
||||
return toreturn;
|
||||
}
|
||||
}, {NAME: DDIMAGEORTEXTFORMNAME, ATTRS: {maxsizes: {value: null}}});
|
||||
M.qtype_ddimageortext = M.qtype_ddimageortext || {};
|
||||
M.qtype_ddimageortext.init_form = function(config) {
|
||||
return new DDIMAGEORTEXT_FORM(config);
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"moodle-qtype_ddimageortext-form": {
|
||||
"requires": [
|
||||
"moodle-qtype_ddimageortext-dd",
|
||||
"form_filepicker"
|
||||
]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user