Merge branch 'MDL-68382-master' of https://github.com/HuongNV13/moodle
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -51,7 +51,8 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes'], function($, dragDro
|
||||
if (this.shape.getCoordinates() === coordinates) {
|
||||
return;
|
||||
}
|
||||
if (!this.shape.parse(coordinates)) {
|
||||
// We don't need to scale the shape for editing form.
|
||||
if (!this.shape.parse(coordinates, 1)) {
|
||||
// Invalid coordinates. Don't update the preview.
|
||||
return;
|
||||
}
|
||||
@@ -70,6 +71,8 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes'], function($, dragDro
|
||||
// Simple update.
|
||||
this.updateSvgEl();
|
||||
}
|
||||
// Update the rounded coordinates if needed.
|
||||
this.setCoordinatesInForm();
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,56 +30,44 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
* Object to handle one drag-drop markers question.
|
||||
*
|
||||
* @param {String} containerId id of the outer div for this question.
|
||||
* @param {String} bgImgUrl the URL of the background image.
|
||||
* @param {boolean} readOnly whether the question is being displayed read-only.
|
||||
* @param {Object[]} visibleDropZones the geometry of any drop-zones to show.
|
||||
* Objects have fields shape, coords and markertext.
|
||||
* @constructor
|
||||
*/
|
||||
function DragDropMarkersQuestion(containerId, bgImgUrl, readOnly, visibleDropZones) {
|
||||
function DragDropMarkersQuestion(containerId, readOnly, visibleDropZones) {
|
||||
var thisQ = this;
|
||||
this.containerId = containerId;
|
||||
this.visibleDropZones = visibleDropZones;
|
||||
this.shapes = [];
|
||||
this.shapeSVGs = [];
|
||||
this.isPrinting = false;
|
||||
if (readOnly) {
|
||||
this.getRoot().addClass('qtype_ddmarker-readonly');
|
||||
}
|
||||
this.loadImage(bgImgUrl);
|
||||
thisQ.cloneDrags();
|
||||
thisQ.repositionDrags();
|
||||
thisQ.drawDropzones();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the background image is loaded, then do the rest of the display.
|
||||
*
|
||||
* @param {String} bgImgUrl the URL of the background image.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.loadImage = function(bgImgUrl) {
|
||||
var thisQ = this;
|
||||
this.getRoot().find('.dropbackground')
|
||||
.one('load', function() {
|
||||
if (thisQ.visibleDropZones.length > 0) {
|
||||
thisQ.drawDropzones();
|
||||
}
|
||||
thisQ.repositionDrags();
|
||||
})
|
||||
.attr('src', bgImgUrl)
|
||||
.css({'border': '1px solid #000', 'max-width': 'none'});
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws the svg shapes of any drop zones that should be visible for feedback purposes.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.drawDropzones = function() {
|
||||
var bgImage = this.getRoot().find('img.dropbackground');
|
||||
if (this.visibleDropZones.length > 0) {
|
||||
var bgImage = this.bgImage();
|
||||
|
||||
this.getRoot().find('div.dropzones').html('<svg xmlns="http://www.w3.org/2000/svg" class="dropzones" ' +
|
||||
'width="' + bgImage.outerWidth() + '" ' +
|
||||
'height="' + bgImage.outerHeight() + '"></svg>');
|
||||
var svg = this.getRoot().find('svg.dropzones');
|
||||
svg.css('position', 'absolute');
|
||||
this.getRoot().find('div.dropzones').html('<svg xmlns="http://www.w3.org/2000/svg" class="dropzones" ' +
|
||||
'width="' + bgImage.outerWidth() + '" ' +
|
||||
'height="' + bgImage.outerHeight() + '"></svg>');
|
||||
var svg = this.getRoot().find('svg.dropzones');
|
||||
|
||||
var nextColourIndex = 0;
|
||||
for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) {
|
||||
var colourClass = 'color' + nextColourIndex;
|
||||
nextColourIndex = (nextColourIndex + 1) % 8;
|
||||
this.addDropzone(svg, dropZoneNo, colourClass);
|
||||
var nextColourIndex = 0;
|
||||
for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) {
|
||||
var colourClass = 'color' + nextColourIndex;
|
||||
nextColourIndex = (nextColourIndex + 1) % 8;
|
||||
this.addDropzone(svg, dropZoneNo, colourClass);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -93,8 +81,9 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
DragDropMarkersQuestion.prototype.addDropzone = function(svg, dropZoneNo, colourClass) {
|
||||
var dropZone = this.visibleDropZones[dropZoneNo],
|
||||
shape = Shapes.make(dropZone.shape, ''),
|
||||
existingmarkertext;
|
||||
if (!shape.parse(dropZone.coords)) {
|
||||
existingmarkertext,
|
||||
bgRatio = this.bgRatio();
|
||||
if (!shape.parse(dropZone.coords, bgRatio)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -109,40 +98,26 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
var classnames = 'markertext markertext' + dropZoneNo;
|
||||
this.getRoot().find('div.markertexts').append('<span class="' + classnames + '">' +
|
||||
dropZone.markertext + '</span>');
|
||||
var markerspan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo);
|
||||
if (markerspan.length) {
|
||||
var handles = shape.getHandlePositions();
|
||||
var positionLeft = handles.moveHandle.x - (markerspan.outerWidth() / 2) - 4;
|
||||
var positionTop = handles.moveHandle.y - (markerspan.outerHeight() / 2);
|
||||
markerspan
|
||||
.css('left', positionLeft)
|
||||
.css('top', positionTop);
|
||||
markerspan
|
||||
.data('originX', markerspan.position().left / bgRatio)
|
||||
.data('originY', markerspan.position().top / bgRatio);
|
||||
this.handleElementScale(markerspan, 'center');
|
||||
}
|
||||
}
|
||||
|
||||
var shapeSVG = shape.makeSvg(svg[0]);
|
||||
shapeSVG.setAttribute('class', 'dropzone ' + colourClass);
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws the drag items on the page (and drop zones if required).
|
||||
* The idea is to re-draw all the drags and drops whenever there is a change
|
||||
* like a widow resize or an item dropped in place.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.repositionDropZones = function() {
|
||||
var svg = this.getRoot().find('svg.dropzones');
|
||||
if (svg.length === 0) {
|
||||
return;
|
||||
}
|
||||
var bgPosition = this.convertToWindowXY(new Shapes.Point(-1, 0));
|
||||
svg.offset({'left': bgPosition.x, 'top': bgPosition.y});
|
||||
|
||||
for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) {
|
||||
var markerspan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo);
|
||||
if (markerspan.length === 0) {
|
||||
continue;
|
||||
}
|
||||
var dropZone = this.visibleDropZones[dropZoneNo],
|
||||
shape = Shapes.make(dropZone.shape, '');
|
||||
if (!shape.parse(dropZone.coords)) {
|
||||
continue;
|
||||
}
|
||||
var handles = shape.getHandlePositions(),
|
||||
textPos = this.convertToWindowXY(handles.moveHandle.offset(
|
||||
-markerspan.outerWidth() / 2, -markerspan.outerHeight() / 2));
|
||||
markerspan.offset({'left': textPos.x - 4, 'top': textPos.y});
|
||||
}
|
||||
this.shapes[this.shapes.length] = shape;
|
||||
this.shapeSVGs[this.shapeSVGs.length] = shapeSVG;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -154,37 +129,25 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
var root = this.getRoot(),
|
||||
thisQ = this;
|
||||
|
||||
root.find('div.dragitems .dragitem').each(function(key, item) {
|
||||
root.find('div.draghomes .marker').not('.dragplaceholder').each(function(key, item) {
|
||||
$(item).addClass('unneeded');
|
||||
});
|
||||
|
||||
root.find('input.choices').each(function(key, input) {
|
||||
var choiceNo = thisQ.getChoiceNoFromElement(input),
|
||||
coords = thisQ.getCoords(input),
|
||||
dragHome = thisQ.dragHome(choiceNo);
|
||||
for (var i = 0; i < coords.length; i++) {
|
||||
var drag = thisQ.dragItem(choiceNo, i);
|
||||
if (!drag.length || drag.hasClass('beingdragged')) {
|
||||
drag = thisQ.cloneNewDragItem(dragHome, i);
|
||||
} else {
|
||||
drag.removeClass('unneeded');
|
||||
coords = thisQ.getCoords(input);
|
||||
if (coords.length) {
|
||||
var drag = thisQ.getRoot().find('.draghomes' + ' span.marker' + '.choice' + choiceNo).not('.dragplaceholder');
|
||||
drag.remove();
|
||||
for (var i = 0; i < coords.length; i++) {
|
||||
var dragInDrop = drag.clone();
|
||||
dragInDrop.data('pagex', coords[i].x).data('pagey', coords[i].y);
|
||||
thisQ.sendDragToDrop(dragInDrop, false);
|
||||
}
|
||||
drag.offset({'left': coords[i].x, 'top': coords[i].y});
|
||||
thisQ.getDragClone(drag).addClass('active');
|
||||
thisQ.cloneDragIfNeeded(drag);
|
||||
}
|
||||
});
|
||||
|
||||
root.find('div.dragitems .dragitem').each(function(key, itm) {
|
||||
var item = $(itm);
|
||||
if (item.hasClass('unneeded') && !item.hasClass('beingdragged')) {
|
||||
item.remove();
|
||||
}
|
||||
});
|
||||
|
||||
this.repositionDropZones();
|
||||
|
||||
var bgImage = this.bgImage(),
|
||||
bgPosition = bgImage.offset();
|
||||
bgImage.data('prev-top', bgPosition.top).data('prev-left', bgPosition.left);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -197,11 +160,7 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
* @returns {Point[]} coordinates of however many copies of the drag item should be shown.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.getCoords = function(inputNode) {
|
||||
var root = this.getRoot(),
|
||||
choiceNo = this.getChoiceNoFromElement(inputNode),
|
||||
noOfDrags = Number(this.getClassnameNumericSuffix(inputNode, 'noofdrags')),
|
||||
dragging = root.find('span.dragitem.beingdragged.choice' + choiceNo).length > 0,
|
||||
coords = [],
|
||||
var coords = [],
|
||||
val = $(inputNode).val();
|
||||
if (val !== '') {
|
||||
var coordsStrings = val.split(';');
|
||||
@@ -209,10 +168,6 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
coords[i] = this.convertToWindowXY(Shapes.Point.parse(coordsStrings[i]));
|
||||
}
|
||||
}
|
||||
var displayeddrags = coords.length + (dragging ? 1 : 0);
|
||||
if ($(inputNode).hasClass('infinite') || (displayeddrags < noOfDrags)) {
|
||||
coords[coords.length] = this.dragHomeXY(choiceNo);
|
||||
}
|
||||
return coords;
|
||||
};
|
||||
|
||||
@@ -252,19 +207,10 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.coordsInBgImg = function(point) {
|
||||
var bgImage = this.bgImage();
|
||||
return point.x > 0 && point.x <= bgImage.width() &&
|
||||
point.y > 0 && point.y <= bgImage.height();
|
||||
};
|
||||
var bgPosition = bgImage.offset();
|
||||
|
||||
/**
|
||||
* Returns coordinates for the home position of a choice.
|
||||
*
|
||||
* @param {Number} choiceNo
|
||||
* @returns {Point} coordinates
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.dragHomeXY = function(choiceNo) {
|
||||
var dragItemHome = this.dragHome(choiceNo);
|
||||
return new Shapes.Point(dragItemHome.offset().left, dragItemHome.offset().top);
|
||||
return point.x >= bgPosition.left && point.x < bgPosition.left + bgImage.width()
|
||||
&& point.y >= bgPosition.top && point.y < bgPosition.top + bgImage.height();
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -283,52 +229,28 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
return this.getRoot().find('img.dropbackground');
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the DOM node for this choice's home position.
|
||||
* @param {Number} choiceNo
|
||||
* @returns {jQuery} containing the home.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.dragHome = function(choiceNo) {
|
||||
return this.getRoot().find('div.dragitems span.draghome.choice' + choiceNo);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the DOM node for a particular instance of a particular choice.
|
||||
* @param {Number} choiceNo
|
||||
* @param {Number} itemNo
|
||||
* @returns {jQuery} containing the item.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.dragItem = function(choiceNo, itemNo) {
|
||||
return this.getRoot().find('div.dragitems span.dragitem.choice' + choiceNo + '.item' + itemNo);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a draggable copy of the drag item.
|
||||
*
|
||||
* @param {jQuery} dragHome to clone
|
||||
* @param {Number} itemNo new item number
|
||||
* @return {jQuery} drag
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.cloneNewDragItem = function(dragHome, itemNo) {
|
||||
var drag = dragHome.clone(true);
|
||||
drag.removeClass('draghome').addClass('dragitem').addClass('item' + itemNo);
|
||||
dragHome.after(drag);
|
||||
drag.attr('tabIndex', 0);
|
||||
return drag;
|
||||
};
|
||||
|
||||
DragDropMarkersQuestion.prototype.handleDragStart = function(e) {
|
||||
var thisQ = this,
|
||||
dragged = $(e.target).closest('.dragitem');
|
||||
dragged = $(e.target).closest('.marker');
|
||||
|
||||
var info = dragDrop.prepare(e);
|
||||
if (!info.start) {
|
||||
return;
|
||||
}
|
||||
|
||||
dragged.addClass('beingdragged');
|
||||
dragged.addClass('beingdragged').css('transform', '');
|
||||
|
||||
var placed = !dragged.hasClass('unneeded');
|
||||
if (!placed) {
|
||||
var hiddenDrag = thisQ.getDragClone(dragged);
|
||||
if (hiddenDrag.length) {
|
||||
hiddenDrag.addClass('active');
|
||||
dragged.offset(hiddenDrag.offset());
|
||||
}
|
||||
}
|
||||
|
||||
dragDrop.start(e, dragged, function() {
|
||||
void (1); // Nothing to do, but we need a function.
|
||||
void (1);
|
||||
}, function(x, y, dragged) {
|
||||
thisQ.dragEnd(dragged);
|
||||
});
|
||||
@@ -339,52 +261,56 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
* @param {jQuery} dragged the marker that was dragged.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.dragEnd = function(dragged) {
|
||||
dragged.removeClass('beingdragged');
|
||||
var choiceNo = this.getChoiceNoFromElement(dragged);
|
||||
this.saveCoordsForChoice(choiceNo, dragged);
|
||||
this.repositionDrags();
|
||||
var placed = false,
|
||||
choiceNo = this.getChoiceNoFromElement(dragged),
|
||||
bgRatio = this.bgRatio(),
|
||||
dragXY;
|
||||
|
||||
dragged.data('pagex', dragged.offset().left).data('pagey', dragged.offset().top);
|
||||
dragXY = new Shapes.Point(dragged.data('pagex'), dragged.data('pagey'));
|
||||
if (this.coordsInBgImg(dragXY)) {
|
||||
this.sendDragToDrop(dragged, true);
|
||||
placed = true;
|
||||
|
||||
// It seems that the dragdrop sometimes leaves the drag
|
||||
// one pixel out of position. Put it in exactly the right place.
|
||||
var bgImgXY = this.convertToBgImgXY(dragXY);
|
||||
bgImgXY = new Shapes.Point(bgImgXY.x / bgRatio, bgImgXY.y / bgRatio);
|
||||
dragged.data('originX', bgImgXY.x).data('originY', bgImgXY.y);
|
||||
}
|
||||
|
||||
if (!placed) {
|
||||
this.sendDragHome(dragged);
|
||||
this.removeDragIfNeeded(dragged);
|
||||
} else {
|
||||
this.cloneDragIfNeeded(dragged);
|
||||
}
|
||||
|
||||
this.saveCoordsForChoice(choiceNo);
|
||||
};
|
||||
|
||||
/**
|
||||
* Save the coordinates for a dropped item in the form field.
|
||||
* @param {Number} choiceNo which copy of the choice this was.
|
||||
* @param {jQuery} dropped the choice that was dropped here.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.saveCoordsForChoice = function(choiceNo, dropped) {
|
||||
DragDropMarkersQuestion.prototype.saveCoordsForChoice = function(choiceNo) {
|
||||
var coords = [],
|
||||
numItems = this.getRoot().find('span.dragitem.choice' + choiceNo).length,
|
||||
bgImgXY,
|
||||
addme = true;
|
||||
items = this.getRoot().find('div.droparea span.marker.choice' + choiceNo),
|
||||
thiQ = this,
|
||||
bgRatio = this.bgRatio();
|
||||
|
||||
// Re-build the coords array based on data in the ddform inputs.
|
||||
// While long winded and unnecessary if there is only one drop item
|
||||
// for a choice, it does account for moving any one of several drop items
|
||||
// within a choice that have already been placed.
|
||||
for (var i = 0; i <= numItems; i++) {
|
||||
var drag = this.dragItem(choiceNo, i);
|
||||
if (drag.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!drag.hasClass('beingdragged')) {
|
||||
bgImgXY = this.convertToBgImgXY(new Shapes.Point(drag.offset().left, drag.offset().top));
|
||||
if (this.coordsInBgImg(bgImgXY)) {
|
||||
coords[coords.length] = bgImgXY;
|
||||
if (items.length) {
|
||||
items.each(function() {
|
||||
var drag = $(this);
|
||||
if (!drag.hasClass('beingdragged')) {
|
||||
var dragXY = new Shapes.Point(drag.data('pagex'), drag.data('pagey'));
|
||||
if (thiQ.coordsInBgImg(dragXY)) {
|
||||
var bgImgXY = thiQ.convertToBgImgXY(dragXY);
|
||||
bgImgXY = new Shapes.Point(bgImgXY.x / bgRatio, bgImgXY.y / bgRatio);
|
||||
coords[coords.length] = bgImgXY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dropped && dropped.length !== 0 && (dropped[0].innerText === drag[0].innerText)) {
|
||||
addme = false;
|
||||
}
|
||||
}
|
||||
|
||||
// If dropped has been passed it is because a new item has been dropped onto the background image
|
||||
// so add its coordinates to the array.
|
||||
if (addme) {
|
||||
bgImgXY = this.convertToBgImgXY(new Shapes.Point(dropped.offset().left, dropped.offset().top));
|
||||
if (this.coordsInBgImg(bgImgXY)) {
|
||||
coords[coords.length] = bgImgXY;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.getRoot().find('input.choice' + choiceNo).val(coords.join(';'));
|
||||
@@ -395,7 +321,7 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
* @param {KeyboardEvent} e
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.handleKeyPress = function(e) {
|
||||
var drag = $(e.target).closest('.dragitem'),
|
||||
var drag = $(e.target).closest('.marker'),
|
||||
point = new Shapes.Point(drag.offset().left, drag.offset().top),
|
||||
choiceNo = this.getChoiceNoFromElement(drag);
|
||||
|
||||
@@ -427,12 +353,28 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
|
||||
if (point !== null) {
|
||||
point = this.constrainToBgImg(point);
|
||||
drag.offset({'left': point.x, 'top': point.y});
|
||||
drag.data('pagex', drag.offset().left).data('pagey', drag.offset().top);
|
||||
var dragXY = this.convertToBgImgXY(new Shapes.Point(drag.data('pagex'), drag.data('pagey')));
|
||||
drag.data('originX', dragXY.x / this.bgRatio()).data('originY', dragXY.y / this.bgRatio());
|
||||
if (this.coordsInBgImg(new Shapes.Point(drag.offset().left, drag.offset().top))) {
|
||||
if (drag.hasClass('unneeded')) {
|
||||
this.sendDragToDrop(drag, true);
|
||||
var hiddenDrag = this.getDragClone(drag);
|
||||
if (hiddenDrag.length) {
|
||||
hiddenDrag.addClass('active');
|
||||
}
|
||||
this.cloneDragIfNeeded(drag);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
point = this.dragHomeXY(choiceNo);
|
||||
drag.css('left', '').css('top', '');
|
||||
drag.data('pagex', drag.offset().left).data('pagey', drag.offset().top);
|
||||
this.sendDragHome(drag);
|
||||
this.removeDragIfNeeded(drag);
|
||||
}
|
||||
drag.offset({'left': point.x, 'top': point.y});
|
||||
this.saveCoordsForChoice(choiceNo, drag);
|
||||
this.repositionDrags();
|
||||
drag.focus();
|
||||
this.saveCoordsForChoice(choiceNo);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -488,27 +430,204 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
* Handle when the window is resized.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.handleResize = function() {
|
||||
this.repositionDrags();
|
||||
var thisQ = this,
|
||||
bgRatio = this.bgRatio();
|
||||
if (this.isPrinting) {
|
||||
bgRatio = 1;
|
||||
}
|
||||
|
||||
this.getRoot().find('div.droparea .marker').not('.beingdragged').each(function(key, drag) {
|
||||
$(drag)
|
||||
.css('left', parseFloat($(drag).data('originX')) * parseFloat(bgRatio))
|
||||
.css('top', parseFloat($(drag).data('originY')) * parseFloat(bgRatio));
|
||||
thisQ.handleElementScale(drag, 'left top');
|
||||
});
|
||||
|
||||
this.getRoot().find('div.droparea svg.dropzones')
|
||||
.width(this.bgImage().width())
|
||||
.height(this.bgImage().height());
|
||||
|
||||
for (var dropZoneNo = 0; dropZoneNo < this.visibleDropZones.length; dropZoneNo++) {
|
||||
var dropZone = thisQ.visibleDropZones[dropZoneNo];
|
||||
var originCoords = dropZone.coords;
|
||||
var shape = thisQ.shapes[dropZoneNo];
|
||||
var shapeSVG = thisQ.shapeSVGs[dropZoneNo];
|
||||
shape.parse(originCoords, bgRatio);
|
||||
shape.updateSvg(shapeSVG);
|
||||
|
||||
var handles = shape.getHandlePositions();
|
||||
var markerSpan = this.getRoot().find('div.ddarea div.markertexts span.markertext' + dropZoneNo);
|
||||
markerSpan
|
||||
.css('left', handles.moveHandle.x - (markerSpan.outerWidth() / 2) - 4)
|
||||
.css('top', handles.moveHandle.y - (markerSpan.outerHeight() / 2));
|
||||
thisQ.handleElementScale(markerSpan, 'center');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check to see if the background image has moved. If so, refresh the layout.
|
||||
* Clone the drag.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.fixLayoutIfBackgroundMoved = function() {
|
||||
var bgImage = this.bgImage(),
|
||||
bgPosition = bgImage.offset(),
|
||||
prevTop = bgImage.data('prev-top'),
|
||||
prevLeft = bgImage.data('prev-left');
|
||||
if (prevLeft === undefined || prevTop === undefined) {
|
||||
// Question is not set up yet. Nothing to do.
|
||||
return;
|
||||
DragDropMarkersQuestion.prototype.cloneDrags = function() {
|
||||
var thisQ = this;
|
||||
this.getRoot().find('div.draghomes span.marker').each(function(index, draghome) {
|
||||
var drag = $(draghome);
|
||||
var placeHolder = drag.clone();
|
||||
placeHolder.removeClass();
|
||||
placeHolder.addClass('marker choice' +
|
||||
thisQ.getChoiceNoFromElement(drag) + ' dragno' + thisQ.getDragNo(drag) + ' dragplaceholder');
|
||||
drag.before(placeHolder);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the drag number of a drag.
|
||||
*
|
||||
* @param {jQuery} drag the drag.
|
||||
* @returns {Number} the drag number.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.getDragNo = function(drag) {
|
||||
return this.getClassnameNumericSuffix(drag, 'dragno');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get drag clone for a given drag.
|
||||
*
|
||||
* @param {jQuery} drag the drag.
|
||||
* @returns {jQuery} the drag's clone.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.getDragClone = function(drag) {
|
||||
return this.getRoot().find('.draghomes' + ' span.marker' +
|
||||
'.choice' + this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag) + '.dragplaceholder');
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the drop area element.
|
||||
* @returns {jQuery} droparea element.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.dropArea = function() {
|
||||
return this.getRoot().find('div.droparea');
|
||||
};
|
||||
|
||||
/**
|
||||
* Animate a drag back to its home.
|
||||
*
|
||||
* @param {jQuery} drag the item being moved.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.sendDragHome = function(drag) {
|
||||
drag.removeClass('beingdragged')
|
||||
.addClass('unneeded')
|
||||
.css('top', '')
|
||||
.css('left', '')
|
||||
.css('transform', '');
|
||||
var placeHolder = this.getDragClone(drag);
|
||||
placeHolder.after(drag);
|
||||
placeHolder.removeClass('active');
|
||||
};
|
||||
|
||||
/**
|
||||
* Animate a drag item into a given place.
|
||||
*
|
||||
* @param {jQuery} drag the item to place.
|
||||
* @param {boolean} isScaling Scaling or not
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.sendDragToDrop = function(drag, isScaling) {
|
||||
var dropArea = this.dropArea(),
|
||||
bgRatio = this.bgRatio();
|
||||
drag.removeClass('beingdragged').removeClass('unneeded');
|
||||
var dragXY = this.convertToBgImgXY(new Shapes.Point(drag.data('pagex'), drag.data('pagey')));
|
||||
if (isScaling) {
|
||||
drag.data('originX', dragXY.x / bgRatio).data('originY', dragXY.y / bgRatio);
|
||||
drag.css('left', dragXY.x).css('top', dragXY.y);
|
||||
} else {
|
||||
drag.data('originX', dragXY.x).data('originY', dragXY.y);
|
||||
drag.css('left', dragXY.x * bgRatio).css('top', dragXY.y * bgRatio);
|
||||
}
|
||||
if (prevTop === bgPosition.top && prevLeft === bgPosition.left) {
|
||||
// Things have not moved.
|
||||
return;
|
||||
dropArea.append(drag);
|
||||
this.handleElementScale(drag, 'left top');
|
||||
};
|
||||
|
||||
/**
|
||||
* Clone the drag at the draghome area if needed.
|
||||
*
|
||||
* @param {jQuery} drag the item to place.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.cloneDragIfNeeded = function(drag) {
|
||||
var inputNode = this.getInput(drag),
|
||||
noOfDrags = Number(this.getClassnameNumericSuffix(inputNode, 'noofdrags')),
|
||||
displayedDragsInDropArea = this.getRoot().find('div.droparea .marker.choice' +
|
||||
this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).length,
|
||||
displayedDragsInDragHomes = this.getRoot().find('div.draghomes .marker.choice' +
|
||||
this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').length;
|
||||
|
||||
if (displayedDragsInDropArea < noOfDrags && displayedDragsInDragHomes === 0) {
|
||||
var dragclone = drag.clone();
|
||||
dragclone.addClass('unneeded')
|
||||
.css('top', '')
|
||||
.css('left', '')
|
||||
.css('transform', '');
|
||||
this.getDragClone(drag)
|
||||
.removeClass('active')
|
||||
.after(dragclone);
|
||||
}
|
||||
// We need to reposition things.
|
||||
this.repositionDrags();
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove the clone drag at the draghome area if needed.
|
||||
*
|
||||
* @param {jQuery} drag the item to place.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.removeDragIfNeeded = function(drag) {
|
||||
var displayeddrags = this.getRoot().find('div.draghomes .marker.choice' +
|
||||
this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').length;
|
||||
if (displayeddrags > 1) {
|
||||
this.getRoot().find('div.draghomes .marker.choice' +
|
||||
this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').first().remove();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the input belong to drag.
|
||||
*
|
||||
* @param {jQuery} drag the item to place.
|
||||
* @returns {jQuery} input element.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.getInput = function(drag) {
|
||||
var choiceNo = this.getChoiceNoFromElement(drag);
|
||||
return this.getRoot().find('input.choices.choice' + choiceNo);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the background ratio.
|
||||
*
|
||||
* @returns {number} Background ratio.
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.bgRatio = function() {
|
||||
var bgImg = this.bgImage();
|
||||
var bgImgNaturalWidth = bgImg.get(0).naturalWidth;
|
||||
var bgImgClientWidth = bgImg.width();
|
||||
|
||||
return bgImgClientWidth / bgImgNaturalWidth;
|
||||
};
|
||||
|
||||
/**
|
||||
* Scale the drag if needed.
|
||||
*
|
||||
* @param {jQuery} element the item to place.
|
||||
* @param {String} type scaling type
|
||||
*/
|
||||
DragDropMarkersQuestion.prototype.handleElementScale = function(element, type) {
|
||||
var bgRatio = parseFloat(this.bgRatio());
|
||||
if (this.isPrinting) {
|
||||
bgRatio = 1;
|
||||
}
|
||||
$(element).css({
|
||||
'-webkit-transform': 'scale(' + bgRatio + ')',
|
||||
'-moz-transform': 'scale(' + bgRatio + ')',
|
||||
'-ms-transform': 'scale(' + bgRatio + ')',
|
||||
'-o-transform': 'scale(' + bgRatio + ')',
|
||||
'transform': 'scale(' + bgRatio + ')',
|
||||
'transform-origin': type
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -524,6 +643,16 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
*/
|
||||
eventHandlersInitialised: false,
|
||||
|
||||
/**
|
||||
* {boolean} is printing or not.
|
||||
*/
|
||||
isPrinting: false,
|
||||
|
||||
/**
|
||||
* {boolean} is keyboard navigation.
|
||||
*/
|
||||
isKeyboardNavigation: false,
|
||||
|
||||
/**
|
||||
* {Object} all the questions on this page, indexed by containerId (id on the .que div).
|
||||
*/
|
||||
@@ -533,13 +662,12 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
* Initialise one question.
|
||||
*
|
||||
* @param {String} containerId the id of the div.que that contains this question.
|
||||
* @param {String} bgImgUrl URL fo the background image.
|
||||
* @param {boolean} readOnly whether the question is read-only.
|
||||
* @param {Object[]} visibleDropZones data on any drop zones to draw as part of the feedback.
|
||||
*/
|
||||
init: function(containerId, bgImgUrl, readOnly, visibleDropZones) {
|
||||
init: function(containerId, readOnly, visibleDropZones) {
|
||||
questionManager.questions[containerId] =
|
||||
new DragDropMarkersQuestion(containerId, bgImgUrl, readOnly, visibleDropZones);
|
||||
new DragDropMarkersQuestion(containerId, readOnly, visibleDropZones);
|
||||
if (!questionManager.eventHandlersInitialised) {
|
||||
questionManager.setupEventHandlers();
|
||||
questionManager.eventHandlersInitialised = true;
|
||||
@@ -550,14 +678,45 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
* Set up the event handlers that make this question type work. (Done once per page.)
|
||||
*/
|
||||
setupEventHandlers: function() {
|
||||
$('body').on('mousedown touchstart',
|
||||
'.que.ddmarker:not(.qtype_ddmarker-readonly) div.dragitems .dragitem',
|
||||
questionManager.handleDragStart)
|
||||
$('body')
|
||||
.on('mousedown touchstart',
|
||||
'.que.ddmarker:not(.qtype_ddmarker-readonly) div.draghomes .marker', questionManager.handleDragStart)
|
||||
.on('mousedown touchstart',
|
||||
'.que.ddmarker:not(.qtype_ddmarker-readonly) div.droparea .marker', questionManager.handleDragStart)
|
||||
.on('keydown keypress',
|
||||
'.que.ddmarker:not(.qtype_ddmarker-readonly) div.dragitems .dragitem',
|
||||
questionManager.handleKeyPress);
|
||||
$(window).on('resize', questionManager.handleWindowResize);
|
||||
setTimeout(questionManager.fixLayoutIfThingsMoved, 100);
|
||||
'.que.ddmarker:not(.qtype_ddmarker-readonly) div.draghomes .marker', questionManager.handleKeyPress)
|
||||
.on('keydown keypress',
|
||||
'.que.ddmarker:not(.qtype_ddmarker-readonly) div.droparea .marker', questionManager.handleKeyPress)
|
||||
.on('focusin',
|
||||
'.que.ddmarker:not(.qtype_ddmarker-readonly) div.draghomes .marker', function(e) {
|
||||
questionManager.handleKeyboardFocus(e, true);
|
||||
})
|
||||
.on('focusin',
|
||||
'.que.ddmarker:not(.qtype_ddmarker-readonly) div.droparea .marker', function(e) {
|
||||
questionManager.handleKeyboardFocus(e, true);
|
||||
})
|
||||
.on('focusout',
|
||||
'.que.ddmarker:not(.qtype_ddmarker-readonly) div.draghomes .marker', function(e) {
|
||||
questionManager.handleKeyboardFocus(e, false);
|
||||
})
|
||||
.on('focusout',
|
||||
'.que.ddmarker:not(.qtype_ddmarker-readonly) div.droparea .marker', function(e) {
|
||||
questionManager.handleKeyboardFocus(e, false);
|
||||
});
|
||||
$(window).on('resize', function() {
|
||||
questionManager.handleWindowResize(false);
|
||||
});
|
||||
window.addEventListener('beforeprint', function() {
|
||||
questionManager.isPrinting = true;
|
||||
questionManager.handleWindowResize(questionManager.isPrinting);
|
||||
});
|
||||
window.addEventListener('afterprint', function() {
|
||||
questionManager.isPrinting = false;
|
||||
questionManager.handleWindowResize(questionManager.isPrinting);
|
||||
});
|
||||
setTimeout(function() {
|
||||
questionManager.fixLayoutIfThingsMoved();
|
||||
}, 100);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -585,31 +744,41 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
|
||||
|
||||
/**
|
||||
* Handle when the window is resized.
|
||||
* @param {boolean} isPrinting
|
||||
*/
|
||||
handleWindowResize: function() {
|
||||
handleWindowResize: function(isPrinting) {
|
||||
for (var containerId in questionManager.questions) {
|
||||
if (questionManager.questions.hasOwnProperty(containerId)) {
|
||||
questionManager.questions[containerId].isPrinting = isPrinting;
|
||||
questionManager.questions[containerId].handleResize();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle focus lost events on markers.
|
||||
* @param {Event} e
|
||||
* @param {boolean} isNavigating
|
||||
*/
|
||||
handleKeyboardFocus: function(e, isNavigating) {
|
||||
questionManager.isKeyboardNavigation = isNavigating;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sometimes, despite our best efforts, things change in a way that cannot
|
||||
* be specifically caught (e.g. dock expanding or collapsing in Boost).
|
||||
* Therefore, we need to periodically check everything is in the right position.
|
||||
*/
|
||||
fixLayoutIfThingsMoved: function() {
|
||||
for (var containerId in questionManager.questions) {
|
||||
if (questionManager.questions.hasOwnProperty(containerId)) {
|
||||
questionManager.questions[containerId].fixLayoutIfBackgroundMoved();
|
||||
}
|
||||
if (!questionManager.isKeyboardNavigation) {
|
||||
this.handleWindowResize(questionManager.isPrinting);
|
||||
}
|
||||
|
||||
// We use setTimeout after finishing work, rather than setInterval,
|
||||
// in case positioning things is slow. We want 100 ms gap
|
||||
// between executions, not what setInterval does.
|
||||
setTimeout(questionManager.fixLayoutIfThingsMoved, 100);
|
||||
setTimeout(function() {
|
||||
questionManager.fixLayoutIfThingsMoved(questionManager.isPrinting);
|
||||
}, 100);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -126,10 +126,11 @@ define(function() {
|
||||
* Update the shape from the string representation.
|
||||
*
|
||||
* @param {String} coordinates in the form returned by getCoordinates.
|
||||
* @param {number} ratio Ratio to scale.
|
||||
* @return {boolean} true if the string could be parsed and the shape updated, else false.
|
||||
*/
|
||||
Shape.prototype.parse = function(coordinates) {
|
||||
void (coordinates);
|
||||
Shape.prototype.parse = function(coordinates, ratio) {
|
||||
void (coordinates, ratio);
|
||||
throw new Error('Not implemented.');
|
||||
};
|
||||
|
||||
@@ -264,14 +265,16 @@ define(function() {
|
||||
svgEl.childNodes[1].textContent = this.label;
|
||||
};
|
||||
|
||||
Circle.prototype.parse = function(coordinates) {
|
||||
if (!coordinates.match(/^\d+,\d+;\d+$/)) {
|
||||
Circle.prototype.parse = function(coordinates, ratio) {
|
||||
if (!coordinates.match(/^\d+(\.\d+)?,\d+(\.\d+)?;\d+(\.\d+)?$/)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var bits = coordinates.split(';');
|
||||
this.centre = Point.parse(bits[0]);
|
||||
this.radius = Math.round(bits[1]);
|
||||
this.centre.x = this.centre.x * parseFloat(ratio);
|
||||
this.centre.y = this.centre.y * parseFloat(ratio);
|
||||
this.radius = Math.round(bits[1]) * parseFloat(ratio);
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -384,16 +387,18 @@ define(function() {
|
||||
svgEl.childNodes[1].textContent = this.label;
|
||||
};
|
||||
|
||||
Rectangle.prototype.parse = function(coordinates) {
|
||||
if (!coordinates.match(/^\d+,\d+;\d+,\d+$/)) {
|
||||
Rectangle.prototype.parse = function(coordinates, ratio) {
|
||||
if (!coordinates.match(/^\d+(\.\d+)?,\d+(\.\d+)?;\d+(\.\d+)?,\d+(\.\d+)?$/)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var bits = coordinates.split(';');
|
||||
this.centre = Point.parse(bits[0]);
|
||||
this.centre.x = this.centre.x * parseFloat(ratio);
|
||||
this.centre.y = this.centre.y * parseFloat(ratio);
|
||||
var size = Point.parse(bits[1]);
|
||||
this.width = size.x;
|
||||
this.height = size.y;
|
||||
this.width = size.x * parseFloat(ratio);
|
||||
this.height = size.y * parseFloat(ratio);
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -479,6 +484,7 @@ define(function() {
|
||||
Shape.call(this, label, 0, 0);
|
||||
this.points = points ? points.slice() : [new Point(10, 10), new Point(40, 10), new Point(10, 40)];
|
||||
this.normalizeShape();
|
||||
this.ratio = 1;
|
||||
}
|
||||
Polygon.prototype = new Shape();
|
||||
|
||||
@@ -502,13 +508,14 @@ define(function() {
|
||||
|
||||
Polygon.prototype.updateSvg = function(svgEl) {
|
||||
svgEl.childNodes[0].setAttribute('points', this.getCoordinates().replace(/[,;]/g, ' '));
|
||||
svgEl.childNodes[0].setAttribute('transform', 'scale(' + parseFloat(this.ratio) + ')');
|
||||
svgEl.childNodes[1].setAttribute('x', this.centre.x);
|
||||
svgEl.childNodes[1].setAttribute('y', this.centre.y + 15);
|
||||
svgEl.childNodes[1].textContent = this.label;
|
||||
};
|
||||
|
||||
Polygon.prototype.parse = function(coordinates) {
|
||||
if (!coordinates.match(/^\d+,\d+(?:;\d+,\d+)*$/)) {
|
||||
Polygon.prototype.parse = function(coordinates, ratio) {
|
||||
if (!coordinates.match(/^\d+(\.\d+)?,\d+(\.\d+)?(?:;\d+(\.\d+)?,\d+(\.\d+)?)*$/)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -521,6 +528,7 @@ define(function() {
|
||||
this.points = points;
|
||||
this.centre.x = 0;
|
||||
this.centre.y = 0;
|
||||
this.ratio = ratio;
|
||||
this.normalizeShape();
|
||||
|
||||
return true;
|
||||
@@ -636,6 +644,9 @@ define(function() {
|
||||
editHandles.push(this.points[i].offset(this.centre.x, this.centre.y));
|
||||
}
|
||||
|
||||
this.centre.x = this.centre.x * parseFloat(this.ratio);
|
||||
this.centre.y = this.centre.y * parseFloat(this.ratio);
|
||||
|
||||
return {
|
||||
moveHandle: this.centre,
|
||||
editHandles: editHandles
|
||||
|
||||
@@ -42,88 +42,86 @@ class qtype_ddmarker_renderer extends qtype_ddtoimage_renderer_base {
|
||||
|
||||
$question = $qa->get_question();
|
||||
$response = $qa->get_last_qt_data();
|
||||
$componentname = $question->qtype->plugin_name();
|
||||
|
||||
$questiontext = $question->format_questiontext($qa);
|
||||
|
||||
$output = html_writer::tag('div', $questiontext, array('class' => 'qtext'));
|
||||
$dropareaclass = 'droparea';
|
||||
$draghomesclass = 'draghomes';
|
||||
if ($options->readonly) {
|
||||
$dropareaclass .= ' readonly';
|
||||
$draghomesclass .= ' readonly';
|
||||
}
|
||||
|
||||
$bgimage = self::get_url_for_image($qa, 'bgimage');
|
||||
$output = html_writer::div($questiontext, 'qtext');
|
||||
|
||||
$img = html_writer::empty_tag('img', array(
|
||||
'class' => 'dropbackground',
|
||||
'alt' => get_string('dropbackground', 'qtype_ddmarker')));
|
||||
$output .= html_writer::start_div('ddarea');
|
||||
$output .= html_writer::start_div($dropareaclass);
|
||||
$output .= html_writer::img(self::get_url_for_image($qa, 'bgimage'), get_string('dropbackground', 'qtype_ddmarker'),
|
||||
['class' => 'dropbackground img-responsive img-fluid']);
|
||||
|
||||
$droparea = html_writer::tag('div', $img, array('class' => 'droparea'));
|
||||
$output .= html_writer::div('', 'dropzones');
|
||||
$output .= html_writer::div('', 'markertexts');
|
||||
|
||||
$output .= html_writer::end_div();
|
||||
$output .= html_writer::start_div($draghomesclass);
|
||||
|
||||
$draghomes = '';
|
||||
$orderedgroup = $question->get_ordered_choices(1);
|
||||
$componentname = $question->qtype->plugin_name();
|
||||
$hiddenfields = '';
|
||||
foreach ($orderedgroup as $choiceno => $drag) {
|
||||
$classes = array('draghome',
|
||||
"choice{$choiceno}");
|
||||
$classes = ['marker', 'choice' . $choiceno];
|
||||
$attr = [];
|
||||
if ($drag->infinite) {
|
||||
$classes[] = 'infinite';
|
||||
} else {
|
||||
$classes[] = 'dragno'.$drag->noofdrags;
|
||||
$classes[] = 'dragno' . $drag->noofdrags;
|
||||
}
|
||||
$targeticonhtml =
|
||||
$this->output->image_icon('crosshairs', '', $componentname, array('class' => 'target'));
|
||||
|
||||
$markertextattrs = array('class' => 'markertext');
|
||||
$markertext = html_writer::tag('span', $drag->text, $markertextattrs);
|
||||
$draghomesattrs = array('class' => join(' ', $classes));
|
||||
$draghomes .= html_writer::tag('span', $targeticonhtml . $markertext, $draghomesattrs);
|
||||
if (!$options->readonly) {
|
||||
$attr['tabindex'] = 0;
|
||||
}
|
||||
$dragoutput = html_writer::start_span(join(' ', $classes), $attr);
|
||||
$targeticonhtml = $this->output->image_icon('crosshairs', '', $componentname, ['class' => 'target']);
|
||||
$markertext = html_writer::span($drag->text, 'markertext');
|
||||
$dragoutput .= $targeticonhtml . $markertext;
|
||||
$dragoutput .= html_writer::end_span();
|
||||
$output .= $dragoutput;
|
||||
$hiddenfields .= $this->hidden_field_choice($qa, $choiceno, $drag->infinite, $drag->noofdrags);
|
||||
}
|
||||
|
||||
$dragitemsclass = 'dragitems';
|
||||
if ($options->readonly) {
|
||||
$dragitemsclass .= ' readonly';
|
||||
}
|
||||
|
||||
$dragitems = html_writer::tag('div', $draghomes, array('class' => $dragitemsclass));
|
||||
$dropzones = html_writer::tag('div', '', array('class' => 'dropzones'));
|
||||
$texts = html_writer::tag('div', '', array('class' => 'markertexts'));
|
||||
$output .= html_writer::tag('div',
|
||||
$droparea.$dragitems.$dropzones . $texts,
|
||||
array('class' => 'ddarea'));
|
||||
$output .= html_writer::end_div();
|
||||
$output .= html_writer::end_div();
|
||||
|
||||
if ($question->showmisplaced && $qa->get_state()->is_finished()) {
|
||||
$visibledropzones = $question->get_drop_zones_without_hit($response);
|
||||
} else {
|
||||
$visibledropzones = array();
|
||||
$visibledropzones = [];
|
||||
}
|
||||
|
||||
$this->page->requires->js_call_amd('qtype_ddmarker/question', 'init',
|
||||
[$qa->get_outer_question_div_unique_id(), $bgimage, $options->readonly, $visibledropzones]);
|
||||
|
||||
if ($qa->get_state() == question_state::$invalid) {
|
||||
$output .= html_writer::nonempty_tag('div',
|
||||
$question->get_validation_error($qa->get_last_qt_data()),
|
||||
array('class' => 'validationerror'));
|
||||
$output .= html_writer::div($question->get_validation_error($qa->get_last_qt_data()), 'validationerror');
|
||||
}
|
||||
|
||||
if ($question->showmisplaced && $qa->get_state()->is_finished()) {
|
||||
$wrongparts = $question->get_drop_zones_without_hit($response);
|
||||
if (count($wrongparts) !== 0) {
|
||||
$wrongpartsstringspans = array();
|
||||
$wrongpartsstringspans = [];
|
||||
foreach ($wrongparts as $wrongpart) {
|
||||
$wrongpartsstringspans[] = html_writer::nonempty_tag('span',
|
||||
$wrongpart->markertext, array('class' => 'wrongpart'));
|
||||
$wrongpartsstringspans[] = html_writer::span($wrongpart->markertext, 'wrongpart');
|
||||
}
|
||||
$wrongpartsstring = join(', ', $wrongpartsstringspans);
|
||||
$output .= html_writer::nonempty_tag('span',
|
||||
get_string('followingarewrongandhighlighted',
|
||||
'qtype_ddmarker',
|
||||
$wrongpartsstring),
|
||||
array('class' => 'wrongparts'));
|
||||
$output .= html_writer::span(get_string('followingarewrongandhighlighted', 'qtype_ddmarker', $wrongpartsstring),
|
||||
'wrongparts');
|
||||
}
|
||||
}
|
||||
|
||||
$output .= html_writer::tag('div', $hiddenfields, array('class' => 'ddform'));
|
||||
$output .= html_writer::div($hiddenfields, 'ddform');
|
||||
|
||||
$this->page->requires->js_call_amd('qtype_ddmarker/question', 'init',
|
||||
[$qa->get_outer_question_div_unique_id(), $options->readonly, $visibledropzones]);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
protected function hidden_field_choice(question_attempt $qa, $choiceno, $infinite, $noofdrags, $value = null) {
|
||||
$varname = 'c'.$choiceno;
|
||||
$classes = array('choices', 'choice'.$choiceno, 'noofdrags'.$noofdrags);
|
||||
|
||||
@@ -3,29 +3,46 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
.que.ddmarker .draghome img,
|
||||
.que.ddmarker .draghome span {
|
||||
visibility: hidden;
|
||||
.que.ddmarker .droparea {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.que.ddmarker .dragitems .dragitem {
|
||||
cursor: move;
|
||||
.que.ddmarker .droparea .dropzones,
|
||||
.que.ddmarker .droparea .markertexts {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.que.ddmarker .dragitems .draghome {
|
||||
.que.ddmarker .draghomes .marker,
|
||||
.que.ddmarker .droparea .marker {
|
||||
vertical-align: top;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.que.ddmarker .draghomes.readonly .marker,
|
||||
.que.ddmarker .droparea.readonly .marker {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.que.ddmarker .droparea .marker {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.que.ddmarker .draghomes .marker {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin: 10px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.que.ddmarker .dragitems {
|
||||
margin-top: 10px;
|
||||
.que.ddmarker .draghomes .marker.dragplaceholder {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.que.ddmarker .dragitems.readonly .dragitem {
|
||||
cursor: auto;
|
||||
.que.ddmarker .draghomes .marker.dragplaceholder.active {
|
||||
visibility: hidden;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.que.ddmarker div.ddarea,
|
||||
@@ -41,9 +58,16 @@ form.mform fieldset#id_previewareaheader div.ddarea .markertexts {
|
||||
form.mform fieldset#id_previewareaheader .dropbackground {
|
||||
margin: 0 auto;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
form.mform fieldset#id_previewareaheader .dropbackground {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.que.ddmarker .dropbackground.img-responsive.img-fluid {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.que.ddmarker div.dragitems div.draghome,
|
||||
.que.ddmarker div.dragitems div.dragitem,
|
||||
form.mform fieldset#id_previewareaheader div.draghome,
|
||||
@@ -51,7 +75,8 @@ form.mform fieldset#id_previewareaheader div.drag {
|
||||
font: 13px/1.231 arial, helvetica, clean, sans-serif;
|
||||
}
|
||||
|
||||
.que.ddmarker div.dragitems span.markertext,
|
||||
.que.ddmarker .droparea .marker span.markertext,
|
||||
.que.ddmarker .draghomes .marker span.markertext,
|
||||
.que.ddmarker div.markertexts span.markertext,
|
||||
form.mform fieldset#id_previewareaheader div.markertexts span.markertext {
|
||||
margin: 0 5px;
|
||||
@@ -66,11 +91,17 @@ form.mform fieldset#id_previewareaheader div.markertexts span.markertext {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.que.ddmarker .droparea .marker span.markertext,
|
||||
.que.ddmarker .draghomes .marker span.markertext {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.que.ddmarker div.markertexts span.markertext {
|
||||
z-index: 2;
|
||||
background-color: yellow;
|
||||
border: 2px solid khaki;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.que.ddmarker span.wrongpart {
|
||||
@@ -84,7 +115,8 @@ form.mform fieldset#id_previewareaheader div.markertexts span.markertext {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.que.ddmarker div.dragitems img.target {
|
||||
.que.ddmarker .droparea .marker img.target,
|
||||
.que.ddmarker .draghomes .marker img.target {
|
||||
position: absolute;
|
||||
left: -7px; /* This must be half the size of the target image, minus 0.5. */
|
||||
top: -7px; /* In other words, this works for a 15x15 cross-hair. */
|
||||
@@ -94,7 +126,11 @@ form.mform fieldset#id_previewareaheader div.markertexts span.markertext {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.que.ddmarker .dragitem.beingdragged span.markertext {
|
||||
.que.ddmarker .marker.beingdragged {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.que.ddmarker .marker.beingdragged span.markertext {
|
||||
z-index: 3;
|
||||
box-shadow: 3px 3px 4px #000;
|
||||
}
|
||||
@@ -196,3 +232,7 @@ body#page-question-type-ddmarker div[id^=fitem_id_][id*=hintclearwrong_] {
|
||||
body#page-question-type-ddmarker #fitem_id_penalty {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
body#page-question-type-ddmarker .ddarea.que.ddmarker {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
@@ -37,22 +37,20 @@ class behat_qtype_ddmarker extends behat_base {
|
||||
|
||||
/**
|
||||
* Get the xpath for a given drag item.
|
||||
* @param string $dragitem the text of the item to drag.
|
||||
*
|
||||
* @param string $marker the text of the item to drag.
|
||||
* @param bool $iskeyboard is using keyboard or not.
|
||||
* @return string the xpath expression.
|
||||
*/
|
||||
protected function marker_xpath($marker, $item = 0) {
|
||||
return '//span[contains(@class, " dragitem ") and contains(@class, " item' . $item .
|
||||
'") and span[@class = "markertext" and contains(normalize-space(.), "' .
|
||||
$this->escape($marker) . '")]]';
|
||||
}
|
||||
|
||||
protected function parse_marker_name($marker) {
|
||||
$item = 0;
|
||||
if (preg_match('~,(\d+)$~', $marker, $matches)) {
|
||||
$item = $matches[1];
|
||||
$marker = substr($marker, 0, -1 - strlen($item));
|
||||
protected function marker_xpath($marker, $iskeyboard = false) {
|
||||
if ($iskeyboard) {
|
||||
return '//span[contains(@class, "marker") and not(contains(@class, "dragplaceholder")) ' .
|
||||
'and span[@class = "markertext" and contains(normalize-space(.), "' .
|
||||
$this->escape($marker) . '")]]';
|
||||
}
|
||||
return array($marker, $item);
|
||||
return '//span[contains(@class, "marker") and contains(@class, "unneeded") ' .
|
||||
'and not(contains(@class, "dragplaceholder")) and span[@class = "markertext" and contains(normalize-space(.), "' .
|
||||
$this->escape($marker) . '")]]';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +62,6 @@ class behat_qtype_ddmarker extends behat_base {
|
||||
* @Given /^I drag "(?P<marker>[^"]*)" to "(?P<coordinates>\d+,\d+)" in the drag and drop markers question$/
|
||||
*/
|
||||
public function i_drag_to_in_the_drag_and_drop_markers_question($marker, $coordinates) {
|
||||
list($marker, $item) = $this->parse_marker_name($marker);
|
||||
list($x, $y) = explode(',', $coordinates);
|
||||
|
||||
// This is a bit nasty, but Behat (indeed Selenium) will only drag on
|
||||
@@ -81,7 +78,6 @@ class behat_qtype_ddmarker extends behat_base {
|
||||
var target = document.createElement('div');
|
||||
target.setAttribute('id', 'target-{$x}-{$y}');
|
||||
var container = document.querySelector('.droparea');
|
||||
container.style.setProperty('position', 'relative');
|
||||
container.insertBefore(target, image);
|
||||
var xadjusted = {$x} + (container.offsetWidth - image.offsetWidth) / 2;
|
||||
var yadjusted = {$y} + (container.offsetHeight - image.offsetHeight) / 2;
|
||||
@@ -93,7 +89,7 @@ class behat_qtype_ddmarker extends behat_base {
|
||||
}())");
|
||||
|
||||
$generalcontext = behat_context_helper::get('behat_general');
|
||||
$generalcontext->i_drag_and_i_drop_it_in($this->marker_xpath($marker, $item),
|
||||
$generalcontext->i_drag_and_i_drop_it_in($this->marker_xpath($marker),
|
||||
'xpath_element', "#target-{$x}-{$y}", 'css_element');
|
||||
}
|
||||
|
||||
@@ -113,8 +109,7 @@ class behat_qtype_ddmarker extends behat_base {
|
||||
'left' => chr(37),
|
||||
'right' => chr(39),
|
||||
);
|
||||
list($marker, $item) = $this->parse_marker_name($marker);
|
||||
$node = $this->get_selected_node('xpath_element', $this->marker_xpath($marker, $item));
|
||||
$node = $this->get_selected_node('xpath_element', $this->marker_xpath($marker, true));
|
||||
$this->ensure_node_is_visible($node);
|
||||
for ($i = 0; $i < $repeats; $i++) {
|
||||
$node->keyDown($keycodes[$direction]);
|
||||
|
||||
@@ -33,10 +33,10 @@ Feature: Preview a drag-drop marker question
|
||||
And I change window size to "large"
|
||||
And I wait "2" seconds
|
||||
# Odd, but the <br>s go to nothing, not a space.
|
||||
And I drag "OU" to "342,230" in the drag and drop markers question
|
||||
And I drag "Railway station" to "254,197" in the drag and drop markers question
|
||||
And I drag "Railway station,1" to "326,319" in the drag and drop markers question
|
||||
And I drag "Railway station,2" to "203,101" in the drag and drop markers question
|
||||
And I drag "OU" to "345,230" in the drag and drop markers question
|
||||
And I drag "Railway station" to "262,197" in the drag and drop markers question
|
||||
And I drag "Railway station" to "334,319" in the drag and drop markers question
|
||||
And I drag "Railway station" to "211,101" in the drag and drop markers question
|
||||
And I press "Submit and finish"
|
||||
Then the state of "Please place the markers on the map of Milton Keynes" question is shown as "Correct"
|
||||
And I should see "Mark 1.00 out of 1.00"
|
||||
|
||||
@@ -46,7 +46,7 @@ class qtype_ddmarker_walkthrough_test extends qbehaviour_walkthrough_test_base {
|
||||
* @return question_contains_tag_with_attributes the expectation.
|
||||
*/
|
||||
protected function get_contains_draggable_marker_home_expectation($choice, $infinite) {
|
||||
$class = 'draghome choice'.$choice;
|
||||
$class = 'marker choice'.$choice;
|
||||
if ($infinite) {
|
||||
$class .= ' infinite';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user