Skip to content

Commit

Permalink
MDL-63944 Question: Convert toggle all to generic module
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewnicols authored and VinhLe committed Feb 15, 2019
1 parent b65bc97 commit 270fd3f
Show file tree
Hide file tree
Showing 7 changed files with 186 additions and 106 deletions.
1 change: 1 addition & 0 deletions lib/amd/build/checkbox-toggleall.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

126 changes: 126 additions & 0 deletions lib/amd/src/checkbox-toggleall.js
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
* @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,
};
});
2 changes: 1 addition & 1 deletion question/amd/build/qbankmanager.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

111 changes: 19 additions & 92 deletions question/amd/src/qbankmanager.js
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -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) {

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;
}
};
});
42 changes: 32 additions & 10 deletions question/classes/bank/checkbox_column.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,52 @@ 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() {
return 'checkbox';
}

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() {
Expand Down
4 changes: 4 additions & 0 deletions question/classes/bank/view.php
Original file line number Diff line number Diff line change
Expand Up @@ -773,10 +773,14 @@ protected function display_question_list($contexts, $pageurl, $categoryandcontex
* @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 />';
Expand Down
6 changes: 3 additions & 3 deletions question/tests/behat/select_questions.feature
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 ""
Expand All @@ -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
Expand Down

0 comments on commit 270fd3f

Please sign in to comment.