MDL-78662 ddimageortext: Allow answer to support filter dynamic content.
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
@@ -24,12 +24,14 @@ define([
|
||||
'jquery',
|
||||
'core/dragdrop',
|
||||
'core/key_codes',
|
||||
'core_form/changechecker'
|
||||
'core_form/changechecker',
|
||||
'core_filters/events',
|
||||
], function(
|
||||
$,
|
||||
dragDrop,
|
||||
keys,
|
||||
FormChangeChecker
|
||||
FormChangeChecker,
|
||||
filterEvent
|
||||
) {
|
||||
|
||||
"use strict";
|
||||
@@ -45,6 +47,7 @@ define([
|
||||
function DragDropOntoImageQuestion(containerId, readOnly, places) {
|
||||
this.containerId = containerId;
|
||||
this.questionAnswer = {};
|
||||
this.questionDragDropWidthHeight = [];
|
||||
M.util.js_pending('qtype_ddimageortext-init-' + this.containerId);
|
||||
this.places = places;
|
||||
this.allImagesLoaded = false;
|
||||
@@ -61,6 +64,82 @@ define([
|
||||
this.waitForAllImagesToBeLoaded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Change all the drags and drops related to the item that has been changed by filter to correct size and content.
|
||||
*
|
||||
* @param {object} filteredElement the element has been modified by filter.
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.changeAllDragsAndDropsToFilteredContent = function(filteredElement) {
|
||||
let currentFilteredItem = $(filteredElement);
|
||||
const parentIsDD = currentFilteredItem.parent().closest('div').hasClass('placed') ||
|
||||
currentFilteredItem.parent().hasClass('draghome');
|
||||
const isDD = currentFilteredItem.hasClass('placed') || currentFilteredItem.hasClass('draghome');
|
||||
// The filtered element or parent element should a drag or drop item.
|
||||
if (!parentIsDD && !isDD) {
|
||||
return;
|
||||
}
|
||||
if (parentIsDD) {
|
||||
currentFilteredItem = currentFilteredItem.parent().closest('div');
|
||||
}
|
||||
if (this.getRoot().find(currentFilteredItem).length <= 0) {
|
||||
// If the DD item doesn't belong to this question
|
||||
// In case we have multiple questions in the same page.
|
||||
return;
|
||||
}
|
||||
const group = this.getGroup(currentFilteredItem),
|
||||
choice = this.getChoice(currentFilteredItem);
|
||||
let listOfModifiedDragDrop = [];
|
||||
// Get the list of drag and drop item within the same group and choice.
|
||||
this.getRoot().find('.group' + group + '.choice' + choice).each(function(i, node) {
|
||||
// Same modified item, skip it.
|
||||
if ($(node).get(0) === currentFilteredItem.get(0)) {
|
||||
return;
|
||||
}
|
||||
const originalClass = $(node).attr('class');
|
||||
const originalStyle = $(node).attr('style');
|
||||
// We want to keep all the handler and event for filtered item, so using clone is the only choice.
|
||||
const filteredDragDropClone = currentFilteredItem.clone();
|
||||
// Sometimes, for the question that has a lot of input groups and unlimited draggable items,
|
||||
// this 'clone' process takes longer than usual,it will not add the eventHandler for this cloned drag.
|
||||
// We need to make sure to add the eventHandler for the cloned drag too.
|
||||
questionManager.addEventHandlersToDrag(filteredDragDropClone);
|
||||
// Replace the class and style of the drag drop item we want to replace for the clone.
|
||||
filteredDragDropClone.attr('class', originalClass);
|
||||
filteredDragDropClone.attr('style', originalStyle);
|
||||
// Insert into DOM.
|
||||
$(node).before(filteredDragDropClone);
|
||||
// Add the item has been replaced to a list so we can remove it later.
|
||||
listOfModifiedDragDrop.push(node);
|
||||
});
|
||||
|
||||
listOfModifiedDragDrop.forEach(function(node) {
|
||||
$(node).remove();
|
||||
});
|
||||
// Save the current height and width.
|
||||
const currentHeight = currentFilteredItem.height();
|
||||
const currentWidth = currentFilteredItem.width();
|
||||
// Set to auto, so we can get the real height and width of the filtered item.
|
||||
currentFilteredItem.height('auto');
|
||||
currentFilteredItem.width('auto');
|
||||
// We need to set display block so we can get height and width.
|
||||
// Some browsers can't get the offsetWidth/Height if they are an inline element like span tag.
|
||||
if (!filteredElement.offsetWidth || !filteredElement.offsetHeight) {
|
||||
filteredElement.classList.add('d-block');
|
||||
}
|
||||
if (this.questionDragDropWidthHeight[group].maxWidth < Math.ceil(filteredElement.offsetWidth) ||
|
||||
this.questionDragDropWidthHeight[group].maxHeight < Math.ceil(0 + filteredElement.offsetHeight)) {
|
||||
// Remove the d-block class before calculation.
|
||||
filteredElement.classList.remove('d-block');
|
||||
// Now resize all the items in the same group if we have new maximum width or height.
|
||||
this.resizeAllDragsAndDropsInGroup(group);
|
||||
} else {
|
||||
currentFilteredItem.height(currentHeight);
|
||||
currentFilteredItem.width(currentWidth);
|
||||
}
|
||||
// Remove the d-block class after resize.
|
||||
filteredElement.classList.remove('d-block');
|
||||
};
|
||||
|
||||
/**
|
||||
* Waits until all images are loaded before calling setupQuestion().
|
||||
*
|
||||
@@ -94,6 +173,12 @@ define([
|
||||
// We now have all images. Carry on, but only after giving the layout a chance to settle down.
|
||||
this.allImagesLoaded = true;
|
||||
thisQ.setupQuestion();
|
||||
// Wait for all dynamic content loaded by filter to be completed.
|
||||
document.addEventListener(filterEvent.eventTypes.filterContentRenderingComplete, (elements) => {
|
||||
elements.detail.nodes.forEach((element) => {
|
||||
thisQ.changeAllDragsAndDropsToFilteredContent(element);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -135,7 +220,7 @@ define([
|
||||
var thisQ = this;
|
||||
this.getRoot().find('.draghomes > div').each(function(i, node) {
|
||||
thisQ.resizeAllDragsAndDropsInGroup(
|
||||
thisQ.getClassnameNumericSuffix($(node), 'dragitemgroup'));
|
||||
thisQ.getClassnameNumericSuffix($(node), 'dragitemgroup'));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -146,7 +231,7 @@ define([
|
||||
*/
|
||||
DragDropOntoImageQuestion.prototype.resizeAllDragsAndDropsInGroup = function(group) {
|
||||
var root = this.getRoot(),
|
||||
dragHomes = root.find('.dragitemgroup' + group + ' .draghome'),
|
||||
dragHomes = root.find(".draghome.group" + group),
|
||||
maxWidth = 0,
|
||||
maxHeight = 0;
|
||||
|
||||
@@ -159,18 +244,11 @@ define([
|
||||
// The size we will want to set is a bit bigger than this.
|
||||
maxWidth += 10;
|
||||
maxHeight += 10;
|
||||
this.questionDragDropWidthHeight[group] = {maxWidth, maxHeight};
|
||||
|
||||
// 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'
|
||||
});
|
||||
$(drag).width(maxWidth).height(maxHeight).css('lineHeight', maxHeight + 'px');
|
||||
});
|
||||
|
||||
// Create the drops and make them the right size.
|
||||
@@ -186,9 +264,11 @@ define([
|
||||
if (label === '') {
|
||||
label = M.util.get_string('blank', 'qtype_ddimageortext');
|
||||
}
|
||||
root.find('.dropzones').append('<div class="dropzone active group' + place.group +
|
||||
' place' + i + '" tabindex="0">' +
|
||||
if (root.find('.dropzones .dropzone.group' + place.group + '.place' + i).length === 0) {
|
||||
root.find('.dropzones').append('<div class="dropzone active group' + place.group +
|
||||
' place' + i + '" tabindex="0">' +
|
||||
'<span class="accesshide">' + label + '</span> </div>');
|
||||
}
|
||||
root.find('.dropzone.place' + i).width(maxWidth - 2).height(maxHeight - 2);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -52,7 +52,6 @@ class qtype_ddtoimage_renderer_base extends qtype_with_combined_feedback_rendere
|
||||
|
||||
public function formulation_and_controls(question_attempt $qa,
|
||||
question_display_options $options) {
|
||||
|
||||
$question = $qa->get_question();
|
||||
$response = $qa->get_last_qt_data();
|
||||
|
||||
@@ -92,6 +91,7 @@ class qtype_ddtoimage_renderer_base extends qtype_with_combined_feedback_rendere
|
||||
$classes[] = 'infinite';
|
||||
}
|
||||
if ($dragimageurl === null) {
|
||||
$dragimage->text = question_utils::format_question_fragment($dragimage->text, $this->page->context);
|
||||
$dragimagehomesgroup .= html_writer::div($dragimage->text, join(' ', $classes), ['src' => $dragimageurl]);
|
||||
} else {
|
||||
$dragimagehomesgroup .= html_writer::img($dragimageurl, $dragimage->text, ['class' => join(' ', $classes)]);
|
||||
|
||||
@@ -85,6 +85,10 @@ form.mform fieldset#id_previewareaheader .droppreview {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.que.ddimageortext .MathJax_Display {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.que.ddimageortext .draghomes .draghome.dragplaceholder.active {
|
||||
visibility: hidden;
|
||||
display: inline-block;
|
||||
|
||||
@@ -18,8 +18,9 @@ Feature: Preview a drag-drop onto image question
|
||||
| contextlevel | reference | name |
|
||||
| Course | C1 | Test questions |
|
||||
And the following "questions" exist:
|
||||
| questioncategory | qtype | name | template |
|
||||
| Test questions | ddimageortext | Drag onto image | xsection |
|
||||
| questioncategory | qtype | name | template |
|
||||
| Test questions | ddimageortext | Drag onto image | xsection |
|
||||
| Test questions | ddimageortext | Drag to mathjax equation | mathjax |
|
||||
|
||||
@javascript @_bug_phantomjs
|
||||
Scenario: Preview a question using the mouse.
|
||||
@@ -53,3 +54,12 @@ Feature: Preview a drag-drop onto image question
|
||||
And I press "Submit and finish"
|
||||
Then the state of "Identify the features" question is shown as "Correct"
|
||||
And I should see "Mark 1.00 out of 1.00"
|
||||
|
||||
@javascript
|
||||
Scenario: Preview a drag-drop into image question with mathjax question.
|
||||
Given the "mathjaxloader" filter is "on"
|
||||
And the "mathjaxloader" filter applies to "content and headings"
|
||||
And I am on the "Drag to mathjax equation" "core_question > preview" page logged in as teacher
|
||||
And I press "Fill in correct responses"
|
||||
When I press "Submit and finish"
|
||||
Then ".filter_mathjaxloader_equation" "css_element" should exist in the ".draghome" "css_element"
|
||||
|
||||
@@ -34,7 +34,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
*/
|
||||
class qtype_ddimageortext_test_helper extends question_test_helper {
|
||||
public function get_test_questions() {
|
||||
return array('fox', 'maths', 'xsection', 'mixedlang');
|
||||
return ['fox', 'maths', 'xsection', 'mixedlang', 'mathjax'];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -251,6 +251,54 @@ class qtype_ddimageortext_test_helper extends question_test_helper {
|
||||
return $fromform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data required to save a drag-drop into text question where the the answer contain equation
|
||||
*
|
||||
*
|
||||
* @return stdClass data to create a ddwtos question.
|
||||
*/
|
||||
public function get_ddimageortext_question_form_data_mathjax() {
|
||||
global $CFG, $USER;
|
||||
$fromform = new stdClass();
|
||||
|
||||
$bgdraftitemid = 0;
|
||||
file_prepare_draft_area($bgdraftitemid, null, null, null, null);
|
||||
$fs = get_file_storage();
|
||||
$filerecord = new stdClass();
|
||||
$filerecord->contextid = context_user::instance($USER->id)->id;
|
||||
$filerecord->component = 'user';
|
||||
$filerecord->filearea = 'draft';
|
||||
$filerecord->itemid = $bgdraftitemid;
|
||||
$filerecord->filepath = '/';
|
||||
$filerecord->filename = 'oceanfloorbase.jpg';
|
||||
$fs->create_file_from_pathname($filerecord, $CFG->dirroot .
|
||||
'/question/type/ddimageortext/tests/fixtures/oceanfloorbase.jpg');
|
||||
$fromform->name = 'Drag-and-drop words into image question with equation';
|
||||
$fromform->questiontext = ['text' => 'Fill in the correct mathjax equation: y = 2, x =4', 'format' => FORMAT_HTML];
|
||||
$fromform->defaultmark = 1.0;
|
||||
$fromform->generalfeedback = ['text' => 'The right answer is: "y = x^2"', 'format' => FORMAT_HTML];
|
||||
$fromform->drags = [
|
||||
['dragitemtype' => 'word', 'draggroup' => '1', 'infinite' => '0'],
|
||||
['dragitemtype' => 'word', 'draggroup' => '1', 'infinite' => '0'],
|
||||
];
|
||||
$fromform->bgimage = $bgdraftitemid;
|
||||
$fromform->dragitem = [0, 0];
|
||||
$fromform->draglabel =
|
||||
[
|
||||
'$$ y = x^2 $$',
|
||||
'$$ y = x^5 $$',
|
||||
];
|
||||
$fromform->drops = [
|
||||
['xleft' => '53', 'ytop' => '17', 'choice' => '1', 'droplabel' => ''],
|
||||
['xleft' => '172', 'ytop' => '2', 'choice' => '2', 'droplabel' => ''],
|
||||
];
|
||||
test_question_maker::set_standard_combined_feedback_form_data($fromform);
|
||||
$fromform->shownumcorrect = 0;
|
||||
$fromform->penalty = 0.3333333;
|
||||
$fromform->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY;
|
||||
return $fromform;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a test question where the drag items are a different language than the main question text.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user