Skip to content

Commit

Permalink
UI improvement to fix issue #18
Browse files Browse the repository at this point in the history
  • Loading branch information
srobotta committed Jan 12, 2024
1 parent 215a599 commit 9287ec3
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 12 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ at https://docs.moodle.org/en/Multi-language_content_filter.

## History

### Current main branch
- Extend dialogue UI with error messages when input fields contain invalid
errors. This fixes [issue #18](https://github.com/srobotta/moodle-tiny_cloze/issues/18).

### v1.4
- Extend tests to cover functionality of the whole plugin.
- Custom grade values for answers (this addresses [issue #16](https://github.com/srobotta/moodle-tiny_cloze/issues/16))
Expand Down
2 changes: 1 addition & 1 deletion amd/build/ui.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion amd/build/ui.min.js.map

Large diffs are not rendered by default.

51 changes: 41 additions & 10 deletions amd/src/ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ const TEMPLATE = {
'<img class="icon_smallicon" src="' +
M.util.image_url('t/add', 'core') + '" alt="{{STR.addmoreanswerblanks}}"></a>' +
'</div>' +
'<div class="msg-error hidden"></div>' +
'</div>' +
'<div class="{{CSS.ANSWERS}} mb-3">' +
'<ol class="pl-3">{{#answerdata}}' +
Expand Down Expand Up @@ -232,6 +233,9 @@ const getStr = async() => {
{key: 'insert', component},
{key: 'pluginname', component},
{key: 'customgrade', component},
{key: 'err_custom_rate', component},
{key: 'err_empty_answer', component},
{key: 'err_none_correct', component},
]).then(function() {
const args = Array.from(arguments);
[
Expand Down Expand Up @@ -269,6 +273,9 @@ const getStr = async() => {
'btn_insert',
'title',
'custom_grade',
'err_custom_rate',
'err_empty_answer',
'err_none_correct',
].map((l, i) => {
STR[l] = args[0][i];
return ''; // Make the linter happy.
Expand Down Expand Up @@ -920,10 +927,18 @@ const _cancel = function(e) {
*/
const _setSubquestion = function(e) {
e.preventDefault();
if (!_processFormData(true)) {
// Check if there are any errors and if so, fill the error container with the
// messages and return without going any further and closing the dialogue.
const errMsg = _form.querySelector('.msg-error');
const formErrors = _processFormData(true);
if (formErrors.length > 0) {
const unique = formErrors.filter((value, index, array) => array.indexOf(value) === index);
errMsg.innerHTML = '<ul><li>' + unique.join('</li><li>') + '</li></ul>';
errMsg.classList.remove('hidden');
return;
} else {
errMsg.classList.add('hidden');
}

// Build the parser function from the data, that is going to be placed into the editor content.
let question = '{' + _marks + ':' + _qtype + ':';

Expand Down Expand Up @@ -961,19 +976,21 @@ const _setSubquestion = function(e) {
*
* @method _processFormData
* @param {boolean} validate
* @return {boolean}
* @return {Array}
* @private
*/
const _processFormData = function(validate) {
_answerdata = [];
let answer;
let hasError = false;
let hasErrors= [];
let foundCorrect = false;
const answers = _form.querySelectorAll('.' + CSS.ANSWER);
const feedbacks = _form.querySelectorAll('.' + CSS.FEEDBACK);
const fractions = _form.querySelectorAll('.' + CSS.FRACTION);
const customGrades = _form.querySelectorAll('.' + CSS.FRAC_CUSTOM);
const tolerances = _form.querySelectorAll('.' + CSS.TOLERANCE);
for (let i = 0; i < answers.length; i++) {
answers.item(i).classList.remove('error');
customGrades.item(i).classList.remove('error');
answer = answers.item(i).value;
if (_qtype === 'NM' || _qtype === 'NUMERICAL') {
Expand All @@ -988,16 +1005,30 @@ const _processFormData = function(validate) {
tolerance: !isNull(tolerances.item(i)) ? tolerances.item(i).value : 0,
isCustomGrade: fractions.item(i).value === selectCustomPercent
};
if (validate && currentAnswer.isCustomGrade &&
(isNaN(currentAnswer.fraction) || currentAnswer.fraction < -100 || currentAnswer.fraction > 100
|| currentAnswer.fraction.trim() === '')) {
hasError = true;
customGrades.item(i).classList.add('error');
if (validate) {
if (currentAnswer.isCustomGrade &&
(isNaN(currentAnswer.fraction) || currentAnswer.fraction < -100 || currentAnswer.fraction > 100
|| currentAnswer.fraction.trim() === '')
) {
hasErrors.push(STR.err_custom_rate);
customGrades.item(i).classList.add('error');
}
// Check for non-empty value in the original input.
if (answers.item(i).value.trim() === '') {
answers.item(i).classList.add('error');
hasErrors.push(STR.err_empty_answer);
}
if (currentAnswer.fraction === '100' || currentAnswer.fraction === '=') {
foundCorrect = true;
}
}
_answerdata.push(currentAnswer);
_marks = _form.querySelector('.' + CSS.MARKS).value;
}
return !hasError;
if (validate && !foundCorrect) {
hasErrors.push(STR.err_none_correct);
}
return hasErrors;
};

/**
Expand Down
3 changes: 3 additions & 0 deletions lang/en/tiny_cloze.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@

$string['cloze:viewplugin'] = 'Helper to edit cloze questions';
$string['customgrade'] = 'Custom';
$string['err_custom_rate'] = 'Invalid value for custom percent rate.';
$string['err_empty_answer'] = 'Empty answer.';
$string['err_none_correct'] = 'No correct answer found.';
$string['helplinktext'] = 'Cloze question editor';
$string['pluginname'] = 'Cloze question editor';
$string['privacy:metadata'] = 'Cloze Editor does not store any personal data';
Expand Down
4 changes: 4 additions & 0 deletions styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
border-color: red;
}

.tiny_cloze .msg-error {
color: red;
}

.tiny_cloze_answers {
height: 375px;
overflow-x: hidden;
Expand Down
96 changes: 96 additions & 0 deletions tests/behat/dialogue_error.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
@editor @tiny @editor_tiny @tiny_html @tiny_cloze @javascript
Feature: Test the cloze question dialgoue with error messages when not all fields are filled correctly.

Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher | Mark | Allright | teacher@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher | C1 | editingteacher |
Given the following "user preferences" exist:
| user | preference | value |
| teacher | htmleditor | tiny |

Scenario: Create a MULTICHOICE_VS question with errors
When I am on the "Course 1" "core_question > course question bank" page logged in as teacher
And I press "Create a new question ..."
And I set the field "Embedded answers (Cloze)" to "1"
And I click on "Add" "button" in the "Choose a question type to add" "dialogue"
And I set the field "Question name" to "multianswer-001"
And I click on "Cloze question editor" "button"
And I set the field "MULTICHOICE_VS" to "1"
And I click on "Select question type" "button"
When I click on "Insert question" "button"
Then I should see "Empty answer."
And I should see "No correct answer found."

When I set the field with xpath "//form[@name='tiny_cloze_form']//li[1]//select[contains(@class, 'tiny_cloze_fraction')]" to "Correct"
And I click on "Insert question" "button"

Then I should see "Empty answer."
And I should not see "No correct answer found."

When I set the field with xpath "//form[@name='tiny_cloze_form']//li[1]//input[contains(@class, 'tiny_cloze_answer')]" to "dog"
And I set the field with xpath "//form[@name='tiny_cloze_form']//li[2]//input[contains(@class, 'tiny_cloze_answer')]" to "cat"
And I set the field with xpath "//form[@name='tiny_cloze_form']//li[3]//input[contains(@class, 'tiny_cloze_answer')]" to "mouse"
And I click on "Insert question" "button"
#And I click on the "View > Source code" menu item for the "Question text" TinyMCE editor
#Then I should see "<p>{1:MULTICHOICE_VS:=dog~cat~mouse}</p>" source code for the "Question text" TinyMCE editor
And I click on "Save changes and continue editing" "button"
Then the field "Question text" matches multiline:
"""
<p><span class="cloze-question-marker" contenteditable="false">{1:MULTICHOICE_VS:=dog~cat~mouse}</span></p>
"""

Scenario: Create a NUMERICAL question with errors
When I am on the "Course 1" "core_question > course question bank" page logged in as teacher
And I press "Create a new question ..."
And I set the field "Embedded answers (Cloze)" to "1"
And I click on "Add" "button" in the "Choose a question type to add" "dialogue"
And I set the field "Question name" to "multianswer-001"
And I click on "Cloze question editor" "button"
And I set the field "NUMERICAL" to "1"
And I click on "Select question type" "button"
When I click on "Insert question" "button"
Then I should see "Empty answer."

When I set the field with xpath "//form[@name='tiny_cloze_form']//li[1]//input[contains(@class, 'tiny_cloze_answer')]" to "0"
And I click on "Insert question" "button"
#And I click on the "View > Source code" menu item for the "Question text" TinyMCE editor
#Then I should see "<p>{1:NUMERICAL:=0:0}</p>" source code for the "Question text" TinyMCE editor
And I click on "Save changes and continue editing" "button"
Then the field "Question text" matches multiline:
"""
<p><span class="cloze-question-marker" contenteditable="false">{1:NUMERICAL:=0:0}</span></p>
"""

Scenario: Create a MULTIRESPONSE question with errors
When I am on the "Course 1" "core_question > course question bank" page logged in as teacher
And I press "Create a new question ..."
And I set the field "Embedded answers (Cloze)" to "1"
And I click on "Add" "button" in the "Choose a question type to add" "dialogue"
And I set the field "Question name" to "multianswer-001"
And I click on "Cloze question editor" "button"
And I set the field "MULTIRESPONSE" to "1"
And I click on "Select question type" "button"
And I set the field with xpath "//form[@name='tiny_cloze_form']//li[2]//select[contains(@class, 'tiny_cloze_fraction')]" to "Custom"
When I click on "Insert question" "button"
Then I should see "Empty answer."
And I should see "No correct answer found."
And I should see "Invalid value for custom percent rate."

When I set the field with xpath "//form[@name='tiny_cloze_form']//li[2]//input[contains(@class, 'tiny_cloze_frac_custom')]" to "-101"
And I click on "Insert question" "button"
Then I should see "Empty answer."
And I should see "No correct answer found."
And I should see "Invalid value for custom percent rate."

When I set the field with xpath "//form[@name='tiny_cloze_form']//li[2]//input[contains(@class, 'tiny_cloze_frac_custom')]" to "-100"
And I click on "Insert question" "button"
Then I should see "Empty answer."
And I should see "No correct answer found."
And I should not see "Invalid value for custom percent rate."

0 comments on commit 9287ec3

Please sign in to comment.