--- /dev/null
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Manages 'Clear my choice' functionality actions.
+ *
+ * @module qtype_multichoice/clearchoice
+ * @copyright 2019 Simey Lameze <simey@moodle.com>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since 3.7
+ */
+define(['jquery', 'core/custom_interaction_events'], function($, CustomEvents) {
+
+ var SELECTORS = {
+ CHOICE_ELEMENT: '.answer input',
+ CLEAR_CHOICE_ELEMENT: 'div[class="qtype_multichoice_clearchoice"]'
+ };
+
+ /**
+ * Mark clear choice radio as checked.
+ *
+ * @param {Object} clearChoiceContainer The clear choice option container.
+ */
+ var checkClearChoiceRadio = function(clearChoiceContainer) {
+ clearChoiceContainer.find('input[type="radio"]').prop('checked', true);
+ };
+
+ /**
+ * Get the clear choice div container.
+ *
+ * @param {Object} root The question root element.
+ * @param {string} fieldPrefix The question outer div prefix.
+ * @returns {Object} The clear choice div container.
+ */
+ var getClearChoiceElement = function(root, fieldPrefix) {
+ return root.find('div[id="' + fieldPrefix + '"]');
+ };
+
+ /**
+ * Hide clear choice option.
+ *
+ * @param {Object} clearChoiceContainer The clear choice option container.
+ */
+ var hideClearChoiceOption = function(clearChoiceContainer) {
+ clearChoiceContainer.addClass('hidden');
+ };
+
+ /**
+ * Shows clear choice option.
+ *
+ * @param {Object} clearChoiceContainer The clear choice option container.
+ */
+ var showClearChoiceOption = function(clearChoiceContainer) {
+ clearChoiceContainer.removeClass('hidden');
+ };
+
+ /**
+ * Register event listeners for the clear choice module.
+ *
+ * @param {Object} root The question outer div prefix.
+ * @param {string} fieldPrefix The "Clear choice" div prefix.
+ */
+ var registerEventListeners = function(root, fieldPrefix) {
+ var clearChoiceContainer = getClearChoiceElement(root, fieldPrefix);
+
+ root.on(CustomEvents.events.activate, SELECTORS.CLEAR_CHOICE_ELEMENT, function() {
+ // Mark the clear choice radio element as checked.
+ checkClearChoiceRadio(clearChoiceContainer);
+ // Now that the hidden radio has been checked, hide the clear choice option.
+ hideClearChoiceOption(clearChoiceContainer);
+ });
+
+ root.on(CustomEvents.events.activate, SELECTORS.CHOICE_ELEMENT, function() {
+ // If the event has been triggered by any other choice, show the clear choice option.
+ showClearChoiceOption(clearChoiceContainer);
+ });
+ };
+
+ /**
+ * Initialise clear choice module.
+
+ * @param {string} root The question outer div prefix.
+ * @param {string} fieldPrefix The "Clear choice" div prefix.
+ */
+ var init = function(root, fieldPrefix) {
+ root = $('#' + root);
+ registerEventListeners(root, fieldPrefix);
+ };
+
+ return {
+ init: init
+ };
+});
$string['answersingleyes'] = 'One answer only';
$string['choiceno'] = 'Choice {$a}';
$string['choices'] = 'Available choices';
+$string['clearchoice'] = 'Clear my choice';
$string['clozeaid'] = 'Enter missing word';
$string['correctansweris'] = 'The correct answer is: {$a}';
$string['correctanswersare'] = 'The correct answers are: {$a}';
}
public function summarise_response(array $response) {
- if (!array_key_exists('answer', $response) ||
- !array_key_exists($response['answer'], $this->order)) {
+ if (!$this->is_complete_response($response)) {
return null;
}
$ansid = $this->order[$response['answer']];
}
public function classify_response(array $response) {
- if (!array_key_exists('answer', $response) ||
- !array_key_exists($response['answer'], $this->order)) {
+ if (!$this->is_complete_response($response)) {
return array($this->id => question_classified_response::no_response());
}
$choiceid = $this->order[$response['answer']];
}
public function is_same_response(array $prevresponse, array $newresponse) {
+ if (!$this->is_complete_response($prevresponse)) {
+ $prevresponse = [];
+ }
+ if (!$this->is_complete_response($newresponse)) {
+ $newresponse = [];
+ }
return question_utils::arrays_same_at_key($prevresponse, $newresponse, 'answer');
}
public function is_complete_response(array $response) {
- return array_key_exists('answer', $response) && $response['answer'] !== '';
+ return array_key_exists('answer', $response) && $response['answer'] !== ''
+ && (string) $response['answer'] !== '-1';
}
public function is_gradable_response(array $response) {
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class qtype_multichoice_renderer_base extends qtype_with_combined_feedback_renderer {
+
+ /**
+ * Method to generating the bits of output after question choices.
+ *
+ * @param question_attempt $qa The question attempt object.
+ * @param question_display_options $options controls what should and should not be displayed.
+ *
+ * @return string HTML output.
+ */
+ protected abstract function after_choices(question_attempt $qa, question_display_options $options);
+
protected abstract function get_input_type();
protected abstract function get_input_name(question_attempt $qa, $value);
}
$result .= html_writer::end_tag('div'); // Answer.
+ $result .= $this->after_choices($qa, $options);
+
$result .= html_writer::end_tag('div'); // Ablock.
if ($qa->get_state() == question_state::$invalid) {
}
return $this->correct_choices($right);
}
+
+ public function after_choices(question_attempt $qa, question_display_options $options) {
+ // Only load the clear choice feature if it's not read only.
+ if ($options->readonly) {
+ return '';
+ }
+
+ $question = $qa->get_question();
+ $response = $question->get_response($qa);
+ $hascheckedchoice = false;
+ foreach ($question->get_order($qa) as $value => $ansid) {
+ if ($question->is_choice_selected($response, $value)) {
+ $hascheckedchoice = true;
+ break;
+ }
+ }
+
+ $clearchoiceid = $this->get_input_id($qa, -1);
+ $clearchoicefieldname = $qa->get_qt_field_name('clearchoice');
+ $clearchoiceradioattrs = [
+ 'type' => $this->get_input_type(),
+ 'name' => $qa->get_qt_field_name('answer'),
+ 'id' => $clearchoiceid,
+ 'value' => -1
+ ];
+
+ $cssclass = 'qtype_multichoice_clearchoice';
+ // When no choice selected during rendering, then hide the clear choice option.
+ if (!$hascheckedchoice && $response == -1) {
+ $cssclass .= ' hidden';
+ $clearchoiceradioattrs['checked'] = 'checked';
+ }
+ // Adds an hidden radio that will be checked to give the impression the choice has been cleared.
+ $clearchoiceradio = html_writer::empty_tag('input', $clearchoiceradioattrs);
+ $clearchoiceradio .= html_writer::tag('label', get_string('clearchoice', 'qtype_multichoice'),
+ ['for' => $clearchoiceid, 'role' => 'button', 'tabindex' => 0]);
+
+ // Now wrap the radio and label inside a div.
+ $result = html_writer::tag('div', $clearchoiceradio, ['id' => $clearchoicefieldname, 'class' => $cssclass]);
+
+ // Load required clearchoice AMD module.
+ $this->page->requires->js_call_amd('qtype_multichoice/clearchoice', 'init',
+ [$qa->get_outer_question_div_unique_id(), $clearchoicefieldname]);
+
+ return $result;
+ }
+
}
/**
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class qtype_multichoice_multi_renderer extends qtype_multichoice_renderer_base {
+ protected function after_choices(question_attempt $qa, question_display_options $options) {
+ return '';
+ }
+
protected function get_input_type() {
return 'checkbox';
}