This commit is contained in:
Andrew Nicols 2024-07-04 12:17:36 +08:00
commit 10d1a28dda
No known key found for this signature in database
GPG Key ID: 6D1E3157C8CFBF14
23 changed files with 471 additions and 51 deletions

View File

@ -1134,6 +1134,20 @@ abstract class question_utils {
return $editoroptions;
}
/**
* Format question fragment string and apply filtering,
*
* @param string $text current text that we want to be apply filters.
* @param context $context of the page question are in.
* @return string result has been modified by filters.
*/
public static function format_question_fragment(string $text, context $context): string {
global $PAGE;
$filtermanager = \filter_manager::instance();
$filtermanager->setup_page_for_filters($PAGE, $context);
return $filtermanager->filter_string($text, $context);
}
}

View File

@ -226,4 +226,32 @@ class questionutils_test extends \advanced_testcase {
$this->assertSame(-1.5, question_utils::clean_param_mark('-1.5'));
$this->assertSame(-1.5, question_utils::clean_param_mark('-1,5'));
}
/**
* Test the content is being filtered by filters.
*
* @covers ::format_question_fragment
*/
public function test_format_question_fragment(): void {
global $CFG;
require_once($CFG->libdir . '/filterlib.php');
$this->resetAfterTest();
// Set few filters on.
filter_set_global_state('multilang', TEXTFILTER_ON);
filter_set_global_state('mathjaxloader', TEXTFILTER_ON);
filter_set_applies_to_strings('multilang', 1);
filter_set_applies_to_strings('mathjaxloader', 1);
$systemcontext = \context_system::instance();
$input = 'Some inline math \\( y = x^2 \\) and multi lang with html tag
<span lang="en" class="multilang"><b>English</b></span><span lang="fr" class="multilang">Français</span>';
$expected = question_utils::format_question_fragment($input, $systemcontext);
// The data should only be filtered by mathjax and multi lang filter. HTML tags should not be affeacted.
$this->assertStringContainsString('<span class="filter_mathjaxloader_equation">Some inline math', $expected);
$this->assertStringContainsString('<span class="nolink">\( y = x^2 \)</span>', $expected);
$this->assertStringNotContainsString('<span lang="en" class="multilang">', $expected);
$this->assertStringContainsString('<b>English</b>', $expected);
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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>&nbsp;</div>');
}
root.find('.dropzone.place' + i).width(maxWidth - 2).height(maxHeight - 2);
}
};

View File

@ -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)]);

View File

@ -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;

View File

@ -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"

View File

@ -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.
*

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -26,13 +26,15 @@ define([
'core/dragdrop',
'qtype_ddmarker/shapes',
'core/key_codes',
'core_form/changechecker'
'core_form/changechecker',
'core_filters/events',
], function(
$,
dragDrop,
Shapes,
keys,
FormChangeChecker
FormChangeChecker,
filterEvent
) {
"use strict";
@ -757,7 +759,7 @@ define([
* a time-out, because image on-loads are allegedly unreliable.
*/
DragDropMarkersQuestion.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) {
@ -784,6 +786,64 @@ define([
this.cloneDrags();
this.repositionDrags();
this.drawDropzones();
// Wait for all dynamic content loaded by filter to be completed.
document.addEventListener(filterEvent.eventTypes.filterContentRenderingComplete, (elements) => {
elements.detail.nodes.forEach((element) => {
thisQ.changeAllMakerToFilteredContent(element);
});
});
};
/**
* Change all the maker 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.
*/
DragDropMarkersQuestion.prototype.changeAllMakerToFilteredContent = function(filteredElement) {
let currentFilteredItem = $(filteredElement);
const parentIsMarker = currentFilteredItem.parent().closest('span.marker');
const isMarker = currentFilteredItem.hasClass('marker');
const root = this.getRoot();
// The filtered element or parent element should a drag or drop item.
if (!parentIsMarker && !isMarker) {
return;
}
if (parentIsMarker) {
currentFilteredItem = currentFilteredItem.parent().closest('span.marker');
}
if (root.find(currentFilteredItem).length <= 0) {
// If the maker doesn't belong to this question
// In case we have multiple questions in the same page.
return;
}
const dragNo = this.getDragNo(currentFilteredItem);
const choiceNo = this.getChoiceNoFromElement(currentFilteredItem);
const listOfContainerToBeModifed = [
'div.draghomes .marker:not(.dragplaceholder).dragno' + dragNo + '.choice' + choiceNo,
'div.droparea .marker:not(.dragplaceholder).dragno' + dragNo + '.choice' + choiceNo,
'div.draghomes .marker:not(.dragplaceholder).infinite.choice' + choiceNo,
'div.droparea .marker:not(.dragplaceholder).infinite.choice' + choiceNo
];
let listOfModifiedDragDrop = [];
// We want to keep all the handler and event for filtered item, so using clone is the only choice.
const filteredDragDropClone = currentFilteredItem.clone();
listOfContainerToBeModifed.forEach(function(selector) {
root.find(selector).each(function(i, node) {
const originalClass = $(node).attr('class');
const originalStyle = $(node).attr('style');
// Replace the class and style of the maker we want to replace for the clone.
filteredDragDropClone.attr('class', originalClass);
filteredDragDropClone.attr('style', originalStyle);
// Add event for the clone.
questionManager.addEventHandlersToMarker(filteredDragDropClone);
// Insert into DOM.
$(node).before(filteredDragDropClone);
listOfModifiedDragDrop.push(node);
});
});
listOfModifiedDragDrop.forEach(function(node) {
$(node).remove();
});
};
/**

View File

@ -68,6 +68,7 @@ class qtype_ddmarker_renderer extends qtype_ddtoimage_renderer_base {
$orderedgroup = $question->get_ordered_choices(1);
$hiddenfields = '';
$dragitems = '';
foreach ($orderedgroup as $choiceno => $drag) {
$classes = ['marker', 'user-select-none', 'choice' . $choiceno];
$attr = [];
@ -81,14 +82,17 @@ class qtype_ddmarker_renderer extends qtype_ddtoimage_renderer_base {
}
$dragoutput = html_writer::start_span(join(' ', $classes), $attr);
$targeticonhtml = $this->output->image_icon('crosshairs', '', $componentname, ['class' => 'target']);
$markertext = html_writer::span($drag->text, 'markertext');
$markertext = html_writer::span(question_utils::format_question_fragment($drag->text, $this->page->context),
'markertext');
$dragoutput .= $targeticonhtml . $markertext;
$dragoutput .= html_writer::end_span();
$output .= $dragoutput;
$dragitems .= $dragoutput;
$hiddenfields .= $this->hidden_field_choice($qa, $choiceno, $drag->infinite, $drag->noofdrags);
}
$output .= $dragitems;
$output .= html_writer::end_div();
// Add extra hidden drag items so we can make sure the filter will be applied.
$output .= html_writer::div($dragitems, 'dd-original d-none');
$output .= html_writer::end_div();
if ($question->showmisplaced && $qa->get_state()->is_finished()) {

View File

@ -18,8 +18,9 @@ Feature: Preview a drag-drop marker question
| contextlevel | reference | name |
| Course | C1 | Test questions |
And the following "questions" exist:
| questioncategory | qtype | name | template |
| Test questions | ddmarker | Drag markers | mkmap |
| questioncategory | qtype | name | template |
| Test questions | ddmarker | Drag markers | mkmap |
| Test questions | ddmarker | Drag to mathjax equation | mathjax |
@javascript @_bug_phantomjs
Scenario: Preview a question using the mouse
@ -56,3 +57,12 @@ Feature: Preview a drag-drop marker 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"
@javascript
Scenario: Preview a drag-drop marker 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 ".droparea" "css_element"

View File

@ -35,7 +35,7 @@ defined('MOODLE_INTERNAL') || die();
*/
class qtype_ddmarker_test_helper extends question_test_helper {
public function get_test_questions() {
return array('fox', 'maths', 'mkmap', 'zerodrag');
return ['fox', 'maths', 'mkmap', 'zerodrag', 'mathjax'];
}
/**
@ -198,6 +198,50 @@ class qtype_ddmarker_test_helper extends question_test_helper {
return $fromform;
}
/**
* Return the test data needed by the question generator to create question tempplate.
*
* @return stdClass date to create a ddmarkers question.
*/
public function get_ddmarker_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 = 'mkmap.png';
$fs->create_file_from_pathname($filerecord, $CFG->dirroot .
'/question/type/ddmarker/tests/fixtures/mkmap.png');
$fromform->name = 'Drag-and-drop into maker question with equation';
$fromform->questiontext = ['text' => 'Fill in the correct mathjax equation: y = 2, x =4', 'format' => FORMAT_HTML];
$fromform->defaultmark = 1;
$fromform->generalfeedback = ['text' => 'The right answer is: "y = x^2"', 'format' => FORMAT_HTML];
$fromform->bgimage = $bgdraftitemid;
$fromform->shuffleanswers = 0;
$fromform->drags = [
['label' => '$$ y = x^2 $$', 'noofdrags' => '1'],
['label' => '$$ y = x^5 $$', 'noofdrags' => '1'],
];
$fromform->drops = [
['shape' => 'circle', 'coords' => '322,213;10', 'choice' => 1],
['shape' => 'circle', 'coords' => '144,84;10', 'choice' => 2],
];
test_question_maker::set_standard_combined_feedback_form_data($fromform);
$fromform->penalty = '0.3333333';
$fromform->status = \core_question\local\bank\question_version_status::QUESTION_STATUS_READY;
return $fromform;
}
/**
* Return the test data needed by the question generator (the data that
* would come from saving the editing form).

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -41,12 +41,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";
@ -59,14 +61,22 @@ define([
* @constructor
*/
function DragDropToTextQuestion(containerId, readOnly) {
const thisQ = this;
this.containerId = containerId;
this.questionAnswer = {};
this.questionDragDropWidthHeight = [];
if (readOnly) {
this.getRoot().addClass('qtype_ddwtos-readonly');
}
this.resizeAllDragsAndDrops();
this.cloneDrags();
this.positionDrags();
// 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);
});
});
}
/**
@ -87,12 +97,12 @@ define([
*/
DragDropToTextQuestion.prototype.resizeAllDragsAndDropsInGroup = function(group) {
var thisQ = this,
dragHomes = this.getRoot().find('.draggrouphomes' + group + ' span.draghome'),
dragDropItems = this.getRoot().find('span.group' + group),
maxWidth = 0,
maxHeight = 0;
// Find the maximum size of any drag in this groups.
dragHomes.each(function(i, drag) {
dragDropItems.each(function(i, drag) {
maxWidth = Math.max(maxWidth, Math.ceil(drag.offsetWidth));
maxHeight = Math.max(maxHeight, Math.ceil(0 + drag.offsetHeight));
});
@ -100,16 +110,85 @@ define([
// The size we will want to set is a bit bigger than this.
maxWidth += 8;
maxHeight += 2;
thisQ.questionDragDropWidthHeight[group] = {maxWidth: maxWidth, maxHeight: maxHeight};
// Set each drag home to that size.
dragHomes.each(function(i, drag) {
dragDropItems.each(function(i, drag) {
thisQ.setElementSize(drag, maxWidth, maxHeight);
});
};
// Set each drop to that size.
this.getRoot().find('span.drop.group' + group).each(function(i, drop) {
thisQ.setElementSize(drop, maxWidth, maxHeight);
/**
* 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.
*/
DragDropToTextQuestion.prototype.changeAllDragsAndDropsToFilteredContent = function(filteredElement) {
let currentFilteredItem = $(filteredElement);
const parentIsDD = currentFilteredItem.parent().closest('span').hasClass('placed') ||
currentFilteredItem.parent().closest('span').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('span');
}
const thisQ = this;
if (thisQ.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 = thisQ.getGroup(currentFilteredItem),
choice = thisQ.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();
// 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 browser 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 (thisQ.questionDragDropWidthHeight[group].maxWidth < Math.ceil(filteredElement.offsetWidth) ||
thisQ.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.
thisQ.resizeAllDragsAndDropsInGroup(group);
} else {
// Return the original height and width in case the real height and width is not the maximum.
currentFilteredItem.height(currentHeight);
currentFilteredItem.width(currentWidth);
}
// Remove the d-block class after resize.
filteredElement.classList.remove('d-block');
};
/**

View File

@ -107,11 +107,9 @@ class qtype_ddwtos_renderer extends qtype_elements_embedded_in_question_text_ren
protected function drag_boxes($qa, $group, $choices, question_display_options $options) {
$boxes = '';
foreach ($choices as $key => $choice) {
// Bug 8632: long text entry causes bug in drag and drop field in IE.
$content = str_replace('-', '&#x2011;', $choice->text);
$content = str_replace(' ', '&#160;', $content);
foreach ($choices as $key => $choice) {
$content = question_utils::format_question_fragment($choice->text, $this->page->context);
$infinite = '';
if ($choice->infinite) {
$infinite = ' infinite';

View File

@ -126,3 +126,7 @@
.que.ddwtos sub {
bottom: -0.2em;
}
.que.ddwtos .MathJax_Display {
margin: 0;
}

View File

@ -18,9 +18,10 @@ Feature: Preview a drag-drop into text question
| contextlevel | reference | name |
| Course | C1 | Test questions |
And the following "questions" exist:
| questioncategory | qtype | name | template |
| Test questions | ddwtos | Drag to text | fox |
| Test questions | ddwtos | Drag to text infinite | infinite |
| questioncategory | qtype | name | template |
| Test questions | ddwtos | Drag to text | fox |
| Test questions | ddwtos | Drag to text infinite | infinite |
| Test questions | ddwtos | Drag to mathjax equation | mathjax |
@javascript @_bug_phantomjs
Scenario: Preview a question using the mouse.
@ -78,3 +79,12 @@ Feature: Preview a drag-drop into text question
Then I should see "Option1" in the home area of drag and drop into text question
And I should see "Option2" in the home area of drag and drop into text question
And I should see "Option3" in the home area of drag and drop into text question
@javascript
Scenario: Preview a drag-drop into text 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"

View File

@ -34,7 +34,7 @@ defined('MOODLE_INTERNAL') || die();
*/
class qtype_ddwtos_test_helper extends question_test_helper {
public function get_test_questions() {
return array('fox', 'maths', 'oddgroups', 'missingchoiceno', 'infinite');
return ['fox', 'maths', 'oddgroups', 'missingchoiceno', 'infinite', 'mathjax'];
}
/**
@ -156,6 +156,30 @@ class qtype_ddwtos_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_ddwtos_question_form_data_mathjax() {
$fromform = new stdClass();
$fromform->name = 'Drag-and-drop words into sentences question with equation';
$fromform->questiontext = ['text' => 'Fill in the correct mathjax equation: y = 2, x =4 : [[1]]', 'format' => FORMAT_HTML];
$fromform->defaultmark = 1.0;
$fromform->generalfeedback = ['text' => 'The right answer is: "y = x^2"', 'format' => FORMAT_HTML];
$fromform->choices = [
['answer' => '$$ y = x^2 $$', 'choicegroup' => '1'],
['answer' => '$$ y = x^5 $$', 'choicegroup' => '1'],
];
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;
}
/**
* @return qtype_ddwtos_question
*/

View File

@ -88,6 +88,9 @@ This files describes API changes for code that uses the question API.
.question-bank-table. This applies to the same styles to preview on the qbank_columnsortorder admin screen. It is important
that the styles match on these pages so that the defaults have the expected result in the question bank.
10) A new utility function format_question_fragment is created.
format_question_fragment is added so that question content can filter base on filters.
=== 4.2 ===
1) The question/qengine.js has been deprecated. We create core_question/question_engine