diff --git a/admin/tool/capability/classes/settings_form.php b/admin/tool/capability/classes/settings_form.php index 1bdf548dd04..c0984d1e8a1 100644 --- a/admin/tool/capability/classes/settings_form.php +++ b/admin/tool/capability/classes/settings_form.php @@ -60,12 +60,12 @@ class tool_capability_settings_form extends moodleform { $form->addElement('select', 'roles', get_string('roleslabel', 'tool_capability'), $roles, $attributes); $form->setType('roles', PARAM_TEXT); - $form->addElement('checkbox', 'onlydiff', - get_string('filters', 'tool_capability'), - get_string('onlydiff', 'tool_capability')); + $filters = []; + $filters[] = $form->createElement('checkbox', 'onlydiff', get_string('onlydiff', 'tool_capability')); $form->setType('onlydiff', PARAM_BOOL); + $form->addGroup($filters, 'filters', get_string('filters', 'tool_capability'), array('
'), false); $form->addElement('submit', 'submitbutton', get_string('getreport', 'tool_capability')); } -} \ No newline at end of file +} diff --git a/lib/form/templates/element-advcheckbox.mustache b/lib/form/templates/element-advcheckbox.mustache index 7524c003eb2..0e927e088ad 100644 --- a/lib/form/templates/element-advcheckbox.mustache +++ b/lib/form/templates/element-advcheckbox.mustache @@ -8,7 +8,6 @@
- + {{/text}}
{{#required}}
diff --git a/lib/form/templates/element-checkbox.mustache b/lib/form/templates/element-checkbox.mustache index b3497a4d117..ce1cd2e7bba 100644 --- a/lib/form/templates/element-checkbox.mustache +++ b/lib/form/templates/element-checkbox.mustache @@ -8,7 +8,6 @@
- + {{/text}}
{{#required}}
diff --git a/mod/lti/edit_form.php b/mod/lti/edit_form.php index d0f06fd6905..fdbb03c3840 100644 --- a/mod/lti/edit_form.php +++ b/mod/lti/edit_form.php @@ -279,7 +279,7 @@ class mod_lti_edit_types_form extends moodleform { $mform->setDefault('lti_acceptgrades', '2'); $mform->addHelpButton('lti_acceptgrades', 'accept_grades_admin', 'lti'); - $mform->addElement('checkbox', 'lti_forcessl', ' ', ' ' . get_string('force_ssl', 'lti'), $options); + $mform->addElement('checkbox', 'lti_forcessl', get_string('force_ssl', 'lti'), '', $options); $mform->setType('lti_forcessl', PARAM_BOOL); if (!empty($CFG->mod_lti_forcessl)) { $mform->setDefault('lti_forcessl', '1'); diff --git a/mod/lti/mod_form.php b/mod/lti/mod_form.php index adc17baf771..27fee1a0afc 100644 --- a/mod/lti/mod_form.php +++ b/mod/lti/mod_form.php @@ -104,12 +104,12 @@ class mod_lti_mod_form extends moodleform_mod { $mform->setAdvanced('showdescription'); - $mform->addElement('checkbox', 'showtitlelaunch', ' ', ' ' . get_string('display_name', 'lti')); + $mform->addElement('checkbox', 'showtitlelaunch', get_string('display_name', 'lti')); $mform->setAdvanced('showtitlelaunch'); $mform->setDefault('showtitlelaunch', true); $mform->addHelpButton('showtitlelaunch', 'display_name', 'lti'); - $mform->addElement('checkbox', 'showdescriptionlaunch', ' ', ' ' . get_string('display_description', 'lti')); + $mform->addElement('checkbox', 'showdescriptionlaunch', get_string('display_description', 'lti')); $mform->setAdvanced('showdescriptionlaunch'); $mform->addHelpButton('showdescriptionlaunch', 'display_description', 'lti'); @@ -281,17 +281,17 @@ class mod_lti_mod_form extends moodleform_mod { // Add privacy preferences fieldset where users choose whether to send their data. $mform->addElement('header', 'privacy', get_string('privacy', 'lti')); - $mform->addElement('advcheckbox', 'instructorchoicesendname', ' ', ' ' . get_string('share_name', 'lti')); + $mform->addElement('advcheckbox', 'instructorchoicesendname', get_string('share_name', 'lti')); $mform->setDefault('instructorchoicesendname', '1'); $mform->addHelpButton('instructorchoicesendname', 'share_name', 'lti'); $mform->disabledIf('instructorchoicesendname', 'typeid', 'in', $toolproxy); - $mform->addElement('advcheckbox', 'instructorchoicesendemailaddr', ' ', ' ' . get_string('share_email', 'lti')); + $mform->addElement('advcheckbox', 'instructorchoicesendemailaddr', get_string('share_email', 'lti')); $mform->setDefault('instructorchoicesendemailaddr', '1'); $mform->addHelpButton('instructorchoicesendemailaddr', 'share_email', 'lti'); $mform->disabledIf('instructorchoicesendemailaddr', 'typeid', 'in', $toolproxy); - $mform->addElement('advcheckbox', 'instructorchoiceacceptgrades', ' ', ' ' . get_string('accept_grades', 'lti')); + $mform->addElement('advcheckbox', 'instructorchoiceacceptgrades', get_string('accept_grades', 'lti')); $mform->setDefault('instructorchoiceacceptgrades', '1'); $mform->addHelpButton('instructorchoiceacceptgrades', 'accept_grades', 'lti'); $mform->disabledIf('instructorchoiceacceptgrades', 'typeid', 'in', $toolproxy); diff --git a/question/type/ddimageortext/edit_ddimageortext_form.php b/question/type/ddimageortext/edit_ddimageortext_form.php index 9b9f1d831e7..7d499e7f5b9 100644 --- a/question/type/ddimageortext/edit_ddimageortext_form.php +++ b/question/type/ddimageortext/edit_ddimageortext_form.php @@ -116,8 +116,7 @@ class qtype_ddimageortext_edit_form extends qtype_ddtoimage_edit_form_base { protected function definition_draggable_items($mform, $itemrepeatsatstart) { $mform->addElement('header', 'draggableitemheader', get_string('draggableitems', 'qtype_ddimageortext')); - $mform->addElement('advcheckbox', 'shuffleanswers', ' ', - get_string('shuffleimages', 'qtype_'.$this->qtype())); + $mform->addElement('advcheckbox', 'shuffleanswers', get_string('shuffleimages', 'qtype_'.$this->qtype())); $mform->setDefault('shuffleanswers', 0); $this->repeat_elements($this->draggable_item($mform), $itemrepeatsatstart, $this->draggable_items_repeated_options(), @@ -143,8 +142,7 @@ class qtype_ddimageortext_edit_form extends qtype_ddtoimage_edit_form_base { get_string('group', 'qtype_gapselect'), $options, array('class' => 'draggroup')); - $grouparray[] = $mform->createElement('advcheckbox', 'infinite', ' ', - get_string('infinite', 'qtype_ddimageortext')); + $grouparray[] = $mform->createElement('advcheckbox', 'infinite', get_string('infinite', 'qtype_ddimageortext')); $draggableimageitem[] = $mform->createElement('group', 'drags', get_string('draggableitemheader', 'qtype_ddimageortext', '{no}'), $grouparray); diff --git a/question/type/ddmarker/edit_ddmarker_form.php b/question/type/ddmarker/edit_ddmarker_form.php index 8242691a437..15ff5864e7c 100644 --- a/question/type/ddmarker/edit_ddmarker_form.php +++ b/question/type/ddmarker/edit_ddmarker_form.php @@ -44,8 +44,7 @@ class qtype_ddmarker_edit_form extends qtype_ddtoimage_edit_form_base { } protected function definition_inner($mform) { - $mform->addElement('advcheckbox', 'showmisplaced', ' ', - get_string('showmisplaced', 'qtype_ddmarker')); + $mform->addElement('advcheckbox', 'showmisplaced', get_string('showmisplaced', 'qtype_ddmarker')); parent::definition_inner($mform); $mform->addHelpButton('drops[0]', 'dropzones', 'qtype_ddmarker'); @@ -60,8 +59,7 @@ class qtype_ddmarker_edit_form extends qtype_ddtoimage_edit_form_base { protected function definition_draggable_items($mform, $itemrepeatsatstart) { $mform->addElement('header', 'draggableitemheader', get_string('markers', 'qtype_ddmarker')); - $mform->addElement('advcheckbox', 'shuffleanswers', ' ', - get_string('shuffleimages', 'qtype_'.$this->qtype())); + $mform->addElement('advcheckbox', 'shuffleanswers', get_string('shuffleimages', 'qtype_'.$this->qtype())); $mform->setDefault('shuffleanswers', 0); $this->repeat_elements($this->draggable_item($mform), $itemrepeatsatstart, $this->draggable_items_repeated_options(), diff --git a/question/type/ddwtos/edit_ddwtos_form.php b/question/type/ddwtos/edit_ddwtos_form.php index f42b439d0db..84479dd84f8 100644 --- a/question/type/ddwtos/edit_ddwtos_form.php +++ b/question/type/ddwtos/edit_ddwtos_form.php @@ -49,8 +49,7 @@ class qtype_ddwtos_edit_form extends qtype_gapselect_edit_form_base { protected function choice_group($mform) { $grouparray = parent::choice_group($mform); - $grouparray[] = $mform->createElement('checkbox', 'infinite', ' ', - get_string('infinite', 'qtype_ddwtos'), null, + $grouparray[] = $mform->createElement('checkbox', 'infinite', get_string('infinite', 'qtype_ddwtos'), '', null, array('size' => 1, 'class' => 'tweakcss')); return $grouparray; } diff --git a/theme/boost/amd/build/form-display-errors.min.js b/theme/boost/amd/build/form-display-errors.min.js index e169aac1e44..fa6511c5681 100644 --- a/theme/boost/amd/build/form-display-errors.min.js +++ b/theme/boost/amd/build/form-display-errors.min.js @@ -1,2 +1,2 @@ -define ("theme_boost/form-display-errors",["jquery","core/event"],function(a,b){return{enhance:function enhance(c){var d=document.getElementById(c);if(!d){return}a(d).on(b.Events.FORM_FIELD_VALIDATION,function(b,c){b.preventDefault();var e=a(d).closest(".form-group"),f=e.find(".form-control-feedback");if("TEXTAREA"==a(d).prop("tagName")&&e.find("[contenteditable]")){d=e.find("[contenteditable]")}if(""!==c){e.addClass("has-danger");e.data("client-validation-error",!0);a(d).addClass("is-invalid");a(d).attr("aria-describedby",f.attr("id"));a(d).attr("aria-invalid",!0);f.attr("tabindex",0);f.html(c);if(!f.is(":visible")){f.show();f.focus()}}else{if(!0===e.data("client-validation-error")){e.removeClass("has-danger");e.data("client-validation-error",!1);a(d).removeClass("is-invalid");a(d).removeAttr("aria-describedby");a(d).attr("aria-invalid",!1);f.hide()}}});var e=d.closest("form");if(e&&!("boostFormErrorsEnhanced"in e.dataset)){e.addEventListener("submit",function(){var b=a(".form-control-feedback:visible");if(b.length){b[0].focus()}});e.dataset.boostFormErrorsEnhanced=1}}}}); +define ("theme_boost/form-display-errors",["jquery","core/event"],function(a,b){return{enhance:function enhance(c){var d=document.getElementById(c);if(!d){return}a(d).on(b.Events.FORM_FIELD_VALIDATION,function(b,c){b.preventDefault();var e=a(d).closest(".form-group"),f=e.find(".form-control-feedback"),g=f.attr("id"),h=a(d).attr("aria-describedby");if("undefined"==typeof h){h=""}var i=[];if(h.length){i=h.split(" ")}var j=i.indexOf(g);if("TEXTAREA"==a(d).prop("tagName")&&e.find("[contenteditable]")){d=e.find("[contenteditable]")}if(""!==c){e.addClass("has-danger");e.data("client-validation-error",!0);a(d).addClass("is-invalid");if(-1===j){i.push(g);a(d).attr("aria-describedby",i.join(" "))}a(d).attr("aria-invalid",!0);f.attr("tabindex",0);f.html(c);if(!f.is(":visible")){f.show();f.focus()}}else{if(!0===e.data("client-validation-error")){e.removeClass("has-danger");e.data("client-validation-error",!1);a(d).removeClass("is-invalid");if(-1.\n\n/**\n * Custom form error event handler to manipulate the bootstrap markup and show\n * nicely styled errors in an mform.\n *\n * @module theme_boost/form-display-errors\n * @copyright 2016 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/event'], function($, Event) {\n return {\n enhance: function(elementid) {\n var element = document.getElementById(elementid);\n if (!element) {\n // Some elements (e.g. static) don't have a form field.\n // Hence there is no validation. So, no setup required here.\n return;\n }\n\n $(element).on(Event.Events.FORM_FIELD_VALIDATION, function(event, msg) {\n event.preventDefault();\n var parent = $(element).closest('.form-group');\n var feedback = parent.find('.form-control-feedback');\n\n // Sometimes (atto) we have a hidden textarea backed by a real contenteditable div.\n if (($(element).prop(\"tagName\") == 'TEXTAREA') && parent.find('[contenteditable]')) {\n element = parent.find('[contenteditable]');\n }\n if (msg !== '') {\n parent.addClass('has-danger');\n parent.data('client-validation-error', true);\n $(element).addClass('is-invalid');\n $(element).attr('aria-describedby', feedback.attr('id'));\n $(element).attr('aria-invalid', true);\n feedback.attr('tabindex', 0);\n feedback.html(msg);\n\n // Only display and focus when the error was not already visible.\n // This is so that, when tabbing around the form, you don't get stuck.\n if (!feedback.is(':visible')) {\n feedback.show();\n feedback.focus();\n }\n\n } else {\n if (parent.data('client-validation-error') === true) {\n parent.removeClass('has-danger');\n parent.data('client-validation-error', false);\n $(element).removeClass('is-invalid');\n $(element).removeAttr('aria-describedby');\n $(element).attr('aria-invalid', false);\n feedback.hide();\n }\n }\n });\n\n var form = element.closest('form');\n if (form && !('boostFormErrorsEnhanced' in form.dataset)) {\n form.addEventListener('submit', function() {\n var visibleError = $('.form-control-feedback:visible');\n if (visibleError.length) {\n visibleError[0].focus();\n }\n });\n form.dataset.boostFormErrorsEnhanced = 1;\n }\n }\n };\n});\n"],"file":"form-display-errors.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/form-display-errors.js"],"names":["define","$","Event","enhance","elementid","element","document","getElementById","on","Events","FORM_FIELD_VALIDATION","event","msg","preventDefault","parent","closest","feedback","find","feedbackId","attr","describedBy","describedByIds","length","split","feedbackIndex","indexOf","prop","addClass","data","push","join","html","is","show","focus","removeClass","splice","removeAttr","hide","form","dataset","addEventListener","visibleError","boostFormErrorsEnhanced"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Custom form error event handler to manipulate the bootstrap markup and show\n * nicely styled errors in an mform.\n *\n * @module theme_boost/form-display-errors\n * @copyright 2016 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery', 'core/event'], function($, Event) {\n return {\n enhance: function(elementid) {\n var element = document.getElementById(elementid);\n if (!element) {\n // Some elements (e.g. static) don't have a form field.\n // Hence there is no validation. So, no setup required here.\n return;\n }\n\n $(element).on(Event.Events.FORM_FIELD_VALIDATION, function(event, msg) {\n event.preventDefault();\n var parent = $(element).closest('.form-group');\n var feedback = parent.find('.form-control-feedback');\n const feedbackId = feedback.attr('id');\n\n // Get current aria-describedby value.\n let describedBy = $(element).attr('aria-describedby');\n if (typeof describedBy === \"undefined\") {\n describedBy = '';\n }\n // Split aria-describedby attribute into an array of IDs if necessary.\n let describedByIds = [];\n if (describedBy.length) {\n describedByIds = describedBy.split(\" \");\n }\n // Find the the feedback container in the aria-describedby attribute.\n const feedbackIndex = describedByIds.indexOf(feedbackId);\n\n // Sometimes (atto) we have a hidden textarea backed by a real contenteditable div.\n if (($(element).prop(\"tagName\") == 'TEXTAREA') && parent.find('[contenteditable]')) {\n element = parent.find('[contenteditable]');\n }\n if (msg !== '') {\n parent.addClass('has-danger');\n parent.data('client-validation-error', true);\n $(element).addClass('is-invalid');\n // Append the feedback ID to the aria-describedby attribute if it doesn't exist yet.\n if (feedbackIndex === -1) {\n describedByIds.push(feedbackId);\n $(element).attr('aria-describedby', describedByIds.join(\" \"));\n }\n $(element).attr('aria-invalid', true);\n feedback.attr('tabindex', 0);\n feedback.html(msg);\n\n // Only display and focus when the error was not already visible.\n // This is so that, when tabbing around the form, you don't get stuck.\n if (!feedback.is(':visible')) {\n feedback.show();\n feedback.focus();\n }\n\n } else {\n if (parent.data('client-validation-error') === true) {\n parent.removeClass('has-danger');\n parent.data('client-validation-error', false);\n $(element).removeClass('is-invalid');\n // If the aria-describedby attribute contains the error container's ID, remove it.\n if (feedbackIndex > -1) {\n describedByIds.splice(feedbackIndex, 1);\n }\n // Check the remaining element IDs in the aria-describedby attribute.\n if (describedByIds.length) {\n // If there's at least one, combine them with a blank space and update the aria-describedby attribute.\n describedBy = describedByIds.join(\" \");\n // Put back the new describedby attribute.\n $(element).attr('aria-describedby', describedBy);\n } else {\n // If there's none, remove the aria-describedby attribute.\n $(element).removeAttr('aria-describedby');\n }\n $(element).attr('aria-invalid', false);\n feedback.hide();\n }\n }\n });\n\n var form = element.closest('form');\n if (form && !('boostFormErrorsEnhanced' in form.dataset)) {\n form.addEventListener('submit', function() {\n var visibleError = $('.form-control-feedback:visible');\n if (visibleError.length) {\n visibleError[0].focus();\n }\n });\n form.dataset.boostFormErrorsEnhanced = 1;\n }\n }\n };\n});\n"],"file":"form-display-errors.min.js"} \ No newline at end of file diff --git a/theme/boost/amd/src/form-display-errors.js b/theme/boost/amd/src/form-display-errors.js index a8391847a5a..4820397e7f2 100644 --- a/theme/boost/amd/src/form-display-errors.js +++ b/theme/boost/amd/src/form-display-errors.js @@ -35,6 +35,20 @@ define(['jquery', 'core/event'], function($, Event) { event.preventDefault(); var parent = $(element).closest('.form-group'); var feedback = parent.find('.form-control-feedback'); + const feedbackId = feedback.attr('id'); + + // Get current aria-describedby value. + let describedBy = $(element).attr('aria-describedby'); + if (typeof describedBy === "undefined") { + describedBy = ''; + } + // Split aria-describedby attribute into an array of IDs if necessary. + let describedByIds = []; + if (describedBy.length) { + describedByIds = describedBy.split(" "); + } + // Find the the feedback container in the aria-describedby attribute. + const feedbackIndex = describedByIds.indexOf(feedbackId); // Sometimes (atto) we have a hidden textarea backed by a real contenteditable div. if (($(element).prop("tagName") == 'TEXTAREA') && parent.find('[contenteditable]')) { @@ -44,7 +58,11 @@ define(['jquery', 'core/event'], function($, Event) { parent.addClass('has-danger'); parent.data('client-validation-error', true); $(element).addClass('is-invalid'); - $(element).attr('aria-describedby', feedback.attr('id')); + // Append the feedback ID to the aria-describedby attribute if it doesn't exist yet. + if (feedbackIndex === -1) { + describedByIds.push(feedbackId); + $(element).attr('aria-describedby', describedByIds.join(" ")); + } $(element).attr('aria-invalid', true); feedback.attr('tabindex', 0); feedback.html(msg); @@ -61,7 +79,20 @@ define(['jquery', 'core/event'], function($, Event) { parent.removeClass('has-danger'); parent.data('client-validation-error', false); $(element).removeClass('is-invalid'); - $(element).removeAttr('aria-describedby'); + // If the aria-describedby attribute contains the error container's ID, remove it. + if (feedbackIndex > -1) { + describedByIds.splice(feedbackIndex, 1); + } + // Check the remaining element IDs in the aria-describedby attribute. + if (describedByIds.length) { + // If there's at least one, combine them with a blank space and update the aria-describedby attribute. + describedBy = describedByIds.join(" "); + // Put back the new describedby attribute. + $(element).attr('aria-describedby', describedBy); + } else { + // If there's none, remove the aria-describedby attribute. + $(element).removeAttr('aria-describedby'); + } $(element).attr('aria-invalid', false); feedback.hide(); }