MDL-63944 Question: Convert toggle all to generic module
authorAndrew Nicols <andrew@nicols.co.uk>
Wed, 2 Jan 2019 07:22:09 +0000 (15:22 +0800)
committerVinhLe <Vinh.LeThe@Nashtechglobal.com>
Fri, 15 Feb 2019 03:07:08 +0000 (10:07 +0700)
lib/amd/build/checkbox-toggleall.min.js [new file with mode: 0644]
lib/amd/src/checkbox-toggleall.js [new file with mode: 0644]
question/amd/build/qbankmanager.min.js
question/amd/src/qbankmanager.js [changed mode: 0755->0644]
question/classes/bank/checkbox_column.php
question/classes/bank/view.php
question/tests/behat/select_questions.feature

diff --git a/lib/amd/build/checkbox-toggleall.min.js b/lib/amd/build/checkbox-toggleall.min.js
new file mode 100644 (file)
index 0000000..90e5ebc
Binary files /dev/null and b/lib/amd/build/checkbox-toggleall.min.js differ
diff --git a/lib/amd/src/checkbox-toggleall.js b/lib/amd/src/checkbox-toggleall.js
new file mode 100644 (file)
index 0000000..c6d4103
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+
+/**
+ * A module to help with toggle select/deselect all.
+ *
+ * @module     core/checkbox-toggleall
+ * @copyright  2019 Andrew Nicols <andrew@nicols.co.uk>
+ * @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,
+    };
+});
index 7e5809e..50dd343 100644 (file)
Binary files a/question/amd/build/qbankmanager.min.js and b/question/amd/build/qbankmanager.min.js differ
old mode 100755 (executable)
new mode 100644 (file)
index 07c41ee..78ff864
  * @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) {
 
-    return {
-        /**
-         * A reference to the header checkbox.
-         *
-         * @property _strings
-         * @type Node
-         * @private
-         */
-        _strings: null,
+    var registerListeners = function() {
+        PubSub.subscribe(ToggleAll.events.checkboxToggled, toggleButtonStates);
+    };
 
-        /**
-         * A reference to the add to quiz button.
-         *
-         * @property _buttons
-         * @type Node
-         * @private
-         */
-        _buttons: null,
+    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 {
         /**
          * 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);
-        },
-
-        /**
-         * 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));
+            setButtonState(false);
+            registerListeners();
         },
-        /**
-         * Get size all row checked of table.
-         * @method _getSizeChecked
-         * @return {Number}
-         * @private
-         */
-        _getSizeChecked: function() {
-            return $('#categoryquestions td.checkbox input[type="checkbox"]:checked').length;
-        }
     };
 });
index 98e020e..916ef30 100644 (file)
@@ -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 type="checkbox" disabled="disabled" id="qbheadercheckbox" name="qbheadercheckbox" />' .
-                '<label class="accesshide" for="qbheadercheckbox">' . get_string('selectall', 'moodle') . '</label>';
+        $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 '<input title="' . $this->strselect . '" type="checkbox" name="q' .
-                $question->id . '" id="checkq' . $question->id . '" value="1"/>';
+        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() {
index ec5b81f..b448a83 100644 (file)
@@ -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 '<div class="modulespecificbuttonscontainer">';
         if ($caneditall || $canmoveall || $canuseall) {
             echo '<strong>&nbsp;'.get_string('withselected', 'question').':</strong><br />';
index 9f481af..66ea78d 100644 (file)
@@ -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