diff --git a/lib/amd/build/checkbox-toggleall.min.js b/lib/amd/build/checkbox-toggleall.min.js new file mode 100644 index 00000000000..90e5ebca6b8 --- /dev/null +++ b/lib/amd/build/checkbox-toggleall.min.js @@ -0,0 +1 @@ +define(["jquery","core/pubsub"],function(a,b){var c=!1,d={checkboxToggled:"core/checkbox-toggleall:checkboxToggled"},e=function(a,b){return a.find('[data-action="toggle"][data-togglegroup="'+b+'"]')},f=function(a,b){return e(a,b).filter('[data-toggle="slave"]')},g=function(a,b){return e(a,b).filter('[data-toggle="master"]')},h=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup"),i=g.is(":checked"),k=f(e,h),l=k.filter(":checked");j(e,h,i),k.prop("checked",i),b.publish(d.checkboxToggled,{root:e,toggleGroupName:h,slaves:k,checkedSlaves:l,anyChecked:i})},i=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup"),i=f(e,h),k=i.filter(":checked"),l=i.length===k.length;j(e,h,l),b.publish(d.checkboxToggled,{root:e,toggleGroupName:h,slaves:i,checkedSlaves:k,anyChecked:!!k.length})},j=function(b,c,d){var e=g(b,c);e.prop("checked",d),e.each(function(c,e){e=a(e);var f,g=b.find('[for="'+e.attr("id")+'"]');g.length&&(f=d?e.data("toggle-deselectall"):e.data("toggle-selectall"),g.html()!==f&&g.html(f))})},k=function(){if(!c){c=!0;var b=a(document.body);b.on("change",'[data-action="toggle"][data-toggle="master"]',{root:b},h),b.on("change",'[data-action="toggle"][data-toggle="slave"]',{root:b},i)}};return{init:function(){k()},events:d}}); \ No newline at end of file diff --git a/lib/amd/src/checkbox-toggleall.js b/lib/amd/src/checkbox-toggleall.js new file mode 100644 index 00000000000..c6d4103fe95 --- /dev/null +++ b/lib/amd/src/checkbox-toggleall.js @@ -0,0 +1,126 @@ +// 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 . + +/** + * A module to help with toggle select/deselect all. + * + * @module core/checkbox-toggleall + * @copyright 2019 Andrew Nicols + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define(['jquery', 'core/pubsub'], function($, PubSub) { + + var registered = false; + + var events = { + checkboxToggled: 'core/checkbox-toggleall:checkboxToggled', + }; + + var getAllCheckboxes = function(root, toggleGroup) { + return root.find('[data-action="toggle"][data-togglegroup="' + toggleGroup + '"]'); + }; + + var getAllSlaveCheckboxes = function(root, toggleGroup) { + return getAllCheckboxes(root, toggleGroup).filter('[data-toggle="slave"]'); + }; + + var getControlCheckboxes = function(root, toggleGroup) { + return getAllCheckboxes(root, toggleGroup).filter('[data-toggle="master"]'); + }; + + var toggleSlavesFromMasters = function(e) { + var root = e.data.root; + var target = $(e.target); + + var toggleGroupName = target.data('togglegroup'); + var targetState = target.is(':checked'); + + var slaves = getAllSlaveCheckboxes(root, toggleGroupName); + var checkedSlaves = slaves.filter(':checked'); + + setMasterStates(root, toggleGroupName, targetState); + + // Set the slave checkboxes from the masters. + slaves.prop('checked', targetState); + + PubSub.publish(events.checkboxToggled, { + root: root, + toggleGroupName: toggleGroupName, + slaves: slaves, + checkedSlaves: checkedSlaves, + anyChecked: targetState, + }); + }; + + var toggleMastersFromSlaves = function(e) { + var root = e.data.root; + var target = $(e.target); + + var toggleGroupName = target.data('togglegroup'); + + var slaves = getAllSlaveCheckboxes(root, toggleGroupName); + var checkedSlaves = slaves.filter(':checked'); + var targetState = (slaves.length === checkedSlaves.length); + + setMasterStates(root, toggleGroupName, targetState); + + PubSub.publish(events.checkboxToggled, { + root: root, + toggleGroupName: toggleGroupName, + slaves: slaves, + checkedSlaves: checkedSlaves, + anyChecked: !!checkedSlaves.length, + }); + }; + + var setMasterStates = function(root, toggleGroupName, targetState) { + // Set the master checkboxes value and ARIA labels.. + var masters = getControlCheckboxes(root, toggleGroupName); + masters.prop('checked', targetState); + masters.each(function(i, masterCheckbox) { + masterCheckbox = $(masterCheckbox); + var masterLabel = root.find('[for="' + masterCheckbox.attr('id') + '"]'); + var targetString; + if (masterLabel.length) { + if (targetState) { + targetString = masterCheckbox.data('toggle-deselectall'); + } else { + targetString = masterCheckbox.data('toggle-selectall'); + } + + if (masterLabel.html() !== targetString) { + masterLabel.html(targetString); + } + } + }); + }; + + var registerListeners = function() { + if (!registered) { + registered = true; + + var root = $(document.body); + root.on('change', '[data-action="toggle"][data-toggle="master"]', {root: root}, toggleSlavesFromMasters); + root.on('change', '[data-action="toggle"][data-toggle="slave"]', {root: root}, toggleMastersFromSlaves); + } + }; + + return { + init: function() { + registerListeners(); + }, + events: events, + }; +}); diff --git a/question/amd/build/qbankmanager.min.js b/question/amd/build/qbankmanager.min.js index 7e5809e1a95..50dd343a365 100644 --- a/question/amd/build/qbankmanager.min.js +++ b/question/amd/build/qbankmanager.min.js @@ -1 +1 @@ -define(["jquery","core/str","core/notification"],function(a,b,c){return{_strings:null,_buttons:null,init:function(){var d=a("#qbheadercheckbox");if(0!=d.length){var e=this;b.get_strings([{key:"selectall",component:"moodle"},{key:"deselectall",component:"moodle"}]).then(function(b){e._strings=b,d.attr({disabled:!1,checked:0!=e._getSizeChecked(),title:b[0]}),d.click(e,e._headerClick),e._buttons=a(".modulespecificbuttonscontainer input, .modulespecificbuttonscontainer select, .modulespecificbuttonscontainer link, .modulespecificbuttonscontainer link"),e._buttons.attr("disabled",0==e._getSizeChecked()),e._buttons.length>0&&a(".categoryquestionscontainer").delegate('td.checkbox input[type="checkbox"]',"change",e,e._questionClick)}).fail(c.exception)}},_headerClick:function(b){var c=b.data,d=a("#qbheadercheckbox"),e=d.is(":checked"),f=e?1:0;a("#categoryquestions tbody [type=checkbox]").prop("checked",e),c._buttons.attr("disabled",0===c._getSizeChecked()),d.attr("title",c._strings[f])},_questionClick:function(b){var c=b.data,d=a("#qbheadercheckbox"),e=c._getSizeChecked(),f=a("#categoryquestions tbody [type=checkbox]").length,g=0!=e&&e==f;d.prop("checked",g),c._buttons.attr("disabled",0===e)},_getSizeChecked:function(){return a('#categoryquestions td.checkbox input[type="checkbox"]:checked').length}}}); \ No newline at end of file +define(["jquery","core/pubsub","core/checkbox-toggleall"],function(a,b,c){var d=function(){b.subscribe(c.events.checkboxToggled,e)},e=function(a){"qbank"===a.toggleGroupName&&f(a.anyChecked)},f=function(b){var c=a(".modulespecificbuttonscontainer").find("input, select, link");c.attr("disabled",!b)};return{init:function(){f(!1),d()}}}); \ No newline at end of file diff --git a/question/amd/src/qbankmanager.js b/question/amd/src/qbankmanager.js old mode 100755 new mode 100644 index 07c41ee6430..78ff864742e --- a/question/amd/src/qbankmanager.js +++ b/question/amd/src/qbankmanager.js @@ -22,107 +22,34 @@ * @copyright 2018 The Open University * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define(['jquery', 'core/str', 'core/notification'], function($, str, notification) { +define(['jquery', 'core/pubsub', 'core/checkbox-toggleall'], function($, PubSub, ToggleAll) { + + var registerListeners = function() { + PubSub.subscribe(ToggleAll.events.checkboxToggled, toggleButtonStates); + }; + + var toggleButtonStates = function(data) { + if ('qbank' !== data.toggleGroupName) { + return; + } + + setButtonState(data.anyChecked); + }; + + var setButtonState = function(state) { + var buttons = $('.modulespecificbuttonscontainer').find('input, select, link'); + buttons.attr('disabled', !state); + }; return { - /** - * A reference to the header checkbox. - * - * @property _strings - * @type Node - * @private - */ - _strings: null, - - /** - * A reference to the add to quiz button. - * - * @property _buttons - * @type Node - * @private - */ - _buttons: null, - /** * Set up the Question Bank Manager. * * @method init */ init: function() { - // Find the header checkbox, and set the initial values. - var header = $('#qbheadercheckbox'); - if (header.length == 0) { - return; - } - var self = this; - str.get_strings([ - {key: 'selectall', component: 'moodle'}, - {key: 'deselectall', component: 'moodle'}, - ]).then(function(strings) { - self._strings = strings; - header.attr({ - disabled: false, - checked: self._getSizeChecked() != 0, - title: strings[0] - }); - header.click(self, self._headerClick); - - self._buttons = $(".modulespecificbuttonscontainer input, .modulespecificbuttonscontainer select," + - " .modulespecificbuttonscontainer link, .modulespecificbuttonscontainer link"); - - self._buttons.attr('disabled', self._getSizeChecked() == 0); - - if (self._buttons.length > 0) { - $('.categoryquestionscontainer') - .delegate('td.checkbox input[type="checkbox"]', 'change', self, self._questionClick); - } - return; - }).fail(notification.exception); + setButtonState(false); + registerListeners(); }, - - /** - * Handle toggling of the header checkbox. - * - * @method _headerClick - * @param {Event} event of element. - * @private - */ - _headerClick: function(event) { - var self = event.data; - var header = $('#qbheadercheckbox'); - var isCheckedHeader = header.is(':checked'); - var indexStringTitle = isCheckedHeader ? 1 : 0; - - $("#categoryquestions tbody [type=checkbox]").prop("checked", isCheckedHeader); - self._buttons.attr('disabled', self._getSizeChecked() === 0); - header.attr('title', self._strings[indexStringTitle]); - }, - - /** - * Handle toggling of a question checkbox. - * - * @method _questionClick - * @param {Event} event of element. - * @private - */ - _questionClick: function(event) { - var self = event.data; - var header = $('#qbheadercheckbox'); - var areChecked = self._getSizeChecked(); - var lengthCheckbox = $("#categoryquestions tbody [type=checkbox]").length; - var ischeckboxHeader = (areChecked != 0) && areChecked == lengthCheckbox; - - header.prop('checked', ischeckboxHeader); - self._buttons.attr('disabled', (areChecked === 0)); - }, - /** - * Get size all row checked of table. - * @method _getSizeChecked - * @return {Number} - * @private - */ - _getSizeChecked: function() { - return $('#categoryquestions td.checkbox input[type="checkbox"]:checked').length; - } }; }); diff --git a/question/classes/bank/checkbox_column.php b/question/classes/bank/checkbox_column.php index 98e020ea343..916ef305024 100644 --- a/question/classes/bank/checkbox_column.php +++ b/question/classes/bank/checkbox_column.php @@ -26,7 +26,9 @@ class checkbox_column extends column_base { protected $strselect; public function init() { - $this->strselect = get_string('select'); + global $PAGE; + + $PAGE->requires->js_call_amd('core/checkbox-toggleall', 'init'); } public function get_name() { @@ -34,22 +36,42 @@ class checkbox_column extends column_base { } protected function get_title() { - return '' . - ''; + $input = \html_writer::empty_tag('input', [ + 'id' => 'qbheadercheckbox', + 'title' => get_string('select'), + 'name' => 'qbheadercheckbox', + 'type' => 'checkbox', + 'value' => '1', + 'data-action' => 'toggle', + 'data-toggle' => 'master', + 'data-togglegroup' => 'qbank', + 'data-toggle-selectall' => get_string('selectall', 'moodle'), + 'data-toggle-deselectall' => get_string('deselectall', 'moodle'), + ]); + + $label = \html_writer::tag('label', get_string('selectall', 'moodle'), [ + 'class' => 'accesshide', + 'for' => 'qbheadercheckbox', + ]); + + return $input . $label; } protected function get_title_tip() { - global $PAGE; - $PAGE->requires->strings_for_js(array('selectall', 'deselectall'), 'moodle'); - $PAGE->requires->js_call_amd('core_question/qbankmanager', 'init'); return get_string('selectquestionsforbulk', 'question'); - } protected function display_content($question, $rowclasses) { - global $PAGE; - echo ''; + echo \html_writer::empty_tag('input', [ + 'title' => get_string('select'), + 'type' => 'checkbox', + 'name' => "q{$question->id}", + 'id' => "checkq{$question->id}", + 'value' => '1', + 'data-action' => 'toggle', + 'data-toggle' => 'slave', + 'data-togglegroup' => 'qbank', + ]); } public function get_required_fields() { diff --git a/question/classes/bank/view.php b/question/classes/bank/view.php index ec5b81f5c99..b448a838171 100644 --- a/question/classes/bank/view.php +++ b/question/classes/bank/view.php @@ -773,10 +773,14 @@ class view { * @param array $addcontexts contexts where the user is allowed to add new questions. */ protected function display_bottom_controls($totalnumber, $recurse, $category, \context $catcontext, array $addcontexts) { + global $PAGE; + $caneditall = has_capability('moodle/question:editall', $catcontext); $canuseall = has_capability('moodle/question:useall', $catcontext); $canmoveall = has_capability('moodle/question:moveall', $catcontext); + $PAGE->requires->js_call_amd('core_question/qbankmanager', 'init'); + echo '
'; if ($caneditall || $canmoveall || $canuseall) { echo ' '.get_string('withselected', 'question').':
'; diff --git a/question/tests/behat/select_questions.feature b/question/tests/behat/select_questions.feature index 9f481afc17c..66ea78d7560 100644 --- a/question/tests/behat/select_questions.feature +++ b/question/tests/behat/select_questions.feature @@ -24,7 +24,7 @@ Feature: The questions in the question bank can be selected in various ways | Test questions | numerical | C question 3 name | teacher1 | Question 3 text | And I log in as "teacher1" And I am on "Course 1" course homepage - And I navigate to "Questions" node in "Course administration > Question bank" + And I navigate to "Questions" in current page administration @javascript Scenario: The question text can be chosen all in the list of questions @@ -33,7 +33,7 @@ Feature: The questions in the question bank can be selected in various ways Then the field "A question 1 name" matches value "1" And the field "B question 2 name" matches value "1" And the field "C question 3 name" matches value "1" - When I click on "Select all" "checkbox" + When I click on "Deselect all" "checkbox" Then the field "A question 1 name" matches value "" And the field "B question 2 name" matches value "" And the field "C question 3 name" matches value "" @@ -45,7 +45,7 @@ Feature: The questions in the question bank can be selected in various ways Then the field "Select all" matches value "" When I click on "B question 2 name" "checkbox" And I click on "C question 3 name" "checkbox" - Then the field "Select all" matches value "1" + Then the field "Deselect all" matches value "1" @javascript Scenario: The action button can be disabled when the question not be chosen in the list of questions