From 686ab8cc74837cd703cd98cd111591ce36446fe0 Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Mon, 11 Mar 2019 16:46:28 +0800 Subject: [PATCH 1/9] MDL-65928 core_amd: Add support for action elements Enable/disable action elements that are associated with the toggle group. --- lib/amd/build/checkbox-toggleall.min.js | 2 +- lib/amd/src/checkbox-toggleall.js | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/amd/build/checkbox-toggleall.min.js b/lib/amd/build/checkbox-toggleall.min.js index 90e5ebca6b8..ad7ec6a6a6d 100644 --- a/lib/amd/build/checkbox-toggleall.min.js +++ b/lib/amd/build/checkbox-toggleall.min.js @@ -1 +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 +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(a,b){return e(a,b).filter('[data-toggle="action"]')},i=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup"),i=g.is(":checked"),j=f(e,h),m=j.filter(":checked");l(e,h,i),j.prop("checked",i),k(e,h,!i),b.publish(d.checkboxToggled,{root:e,toggleGroupName:h,slaves:j,checkedSlaves:m,anyChecked:i})},j=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup"),i=f(e,h),j=i.filter(":checked"),m=i.length===j.length;l(e,h,m),k(e,h,!j.length),b.publish(d.checkboxToggled,{root:e,toggleGroupName:h,slaves:i,checkedSlaves:j,anyChecked:!!j.length})},k=function(a,b,c){h(a,b).prop("disabled",c)},l=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))})},m=function(){if(!c){c=!0;var b=a(document.body);b.on("change",'[data-action="toggle"][data-toggle="master"]',{root:b},i),b.on("change",'[data-action="toggle"][data-toggle="slave"]',{root:b},j)}};return{init:function(){m()},events:d}}); \ No newline at end of file diff --git a/lib/amd/src/checkbox-toggleall.js b/lib/amd/src/checkbox-toggleall.js index c6d4103fe95..84de2339cc4 100644 --- a/lib/amd/src/checkbox-toggleall.js +++ b/lib/amd/src/checkbox-toggleall.js @@ -28,16 +28,20 @@ define(['jquery', 'core/pubsub'], function($, PubSub) { checkboxToggled: 'core/checkbox-toggleall:checkboxToggled', }; - var getAllCheckboxes = function(root, toggleGroup) { + var getToggleGroupElements = 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"]'); + return getToggleGroupElements(root, toggleGroup).filter('[data-toggle="slave"]'); }; var getControlCheckboxes = function(root, toggleGroup) { - return getAllCheckboxes(root, toggleGroup).filter('[data-toggle="master"]'); + return getToggleGroupElements(root, toggleGroup).filter('[data-toggle="master"]'); + }; + + var getActionElements = function(root, toggleGroup) { + return getToggleGroupElements(root, toggleGroup).filter('[data-toggle="action"]'); }; var toggleSlavesFromMasters = function(e) { @@ -55,6 +59,9 @@ define(['jquery', 'core/pubsub'], function($, PubSub) { // Set the slave checkboxes from the masters. slaves.prop('checked', targetState); + // Enable action elements when master checkboxes have been checked. Disable otherwise. + setActionElementStates(root, toggleGroupName, !targetState); + PubSub.publish(events.checkboxToggled, { root: root, toggleGroupName: toggleGroupName, @@ -76,6 +83,9 @@ define(['jquery', 'core/pubsub'], function($, PubSub) { setMasterStates(root, toggleGroupName, targetState); + // Enable action elements when there's at least one checkbox checked. Disable otherwise. + setActionElementStates(root, toggleGroupName, !checkedSlaves.length); + PubSub.publish(events.checkboxToggled, { root: root, toggleGroupName: toggleGroupName, @@ -85,6 +95,10 @@ define(['jquery', 'core/pubsub'], function($, PubSub) { }); }; + var setActionElementStates = function(root, toggleGroupName, disableActionElements) { + getActionElements(root, toggleGroupName).prop('disabled', disableActionElements); + }; + var setMasterStates = function(root, toggleGroupName, targetState) { // Set the master checkboxes value and ARIA labels.. var masters = getControlCheckboxes(root, toggleGroupName); From 0a3d30635819678184138960c5dd5f04ae03962f Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Tue, 12 Mar 2019 16:54:40 +0800 Subject: [PATCH 2/9] MDL-65928 core_amd: Let checkboxes belong to multiple toggle groups --- lib/amd/build/checkbox-toggleall.min.js | 2 +- lib/amd/src/checkbox-toggleall.js | 58 +++++++++++++++---------- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/lib/amd/build/checkbox-toggleall.min.js b/lib/amd/build/checkbox-toggleall.min.js index ad7ec6a6a6d..3c9c2ec5053 100644 --- a/lib/amd/build/checkbox-toggleall.min.js +++ b/lib/amd/build/checkbox-toggleall.min.js @@ -1 +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(a,b){return e(a,b).filter('[data-toggle="action"]')},i=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup"),i=g.is(":checked"),j=f(e,h),m=j.filter(":checked");l(e,h,i),j.prop("checked",i),k(e,h,!i),b.publish(d.checkboxToggled,{root:e,toggleGroupName:h,slaves:j,checkedSlaves:m,anyChecked:i})},j=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup"),i=f(e,h),j=i.filter(":checked"),m=i.length===j.length;l(e,h,m),k(e,h,!j.length),b.publish(d.checkboxToggled,{root:e,toggleGroupName:h,slaves:i,checkedSlaves:j,anyChecked:!!j.length})},k=function(a,b,c){h(a,b).prop("disabled",c)},l=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))})},m=function(){if(!c){c=!0;var b=a(document.body);b.on("change",'[data-action="toggle"][data-toggle="master"]',{root:b},i),b.on("change",'[data-action="toggle"][data-toggle="slave"]',{root:b},j)}};return{init:function(){m()},events:d}}); \ No newline at end of file +define(["jquery","core/pubsub"],function(a,b){var c=!1,d={checkboxToggled:"core/checkbox-toggleall:checkboxToggled"},e=function(a,b,c){return c?a.find('[data-action="toggle"][data-togglegroup="'+b+'"]'):a.find('[data-action="toggle"][data-togglegroup*="'+b+'"]')},f=function(a,b){return e(a,b).filter('[data-toggle="slave"]')},g=function(a,b,c){return e(a,b,c).filter('[data-toggle="master"]')},h=function(a,b){return e(a,b,!0).filter('[data-toggle="action"]')},i=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup"),i=g.is(":checked"),j=f(e,h),k=j.filter(":checked");l(e,h,i),j.prop("checked",i),j.trigger("change"),b.publish(d.checkboxToggled,{root:e,toggleGroupName:h,slaves:j,checkedSlaves:k,anyChecked:i})},j=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup").split(" "),i=[],j="";h.forEach(function(a){j+=" "+a,i.push(j.trim())}),i.forEach(function(a){var c=f(e,a),g=c.filter(":checked"),h=c.length===g.length;l(e,a,h,!0),k(e,a,!g.length),b.publish(d.checkboxToggled,{root:e,toggleGroupName:a,slaves:c,checkedSlaves:g,anyChecked:!!g.length})})},k=function(a,b,c){h(a,b).prop("disabled",c)},l=function(b,c,d,e){var f=g(b,c,e);f.prop("checked",d),f.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))})},m=function(){if(!c){c=!0;var b=a(document.body);b.on("change",'[data-action="toggle"][data-toggle="master"]',{root:b},i),b.on("change",'[data-action="toggle"][data-toggle="slave"]',{root:b},j)}};return{init:function(){m()},events:d}}); \ No newline at end of file diff --git a/lib/amd/src/checkbox-toggleall.js b/lib/amd/src/checkbox-toggleall.js index 84de2339cc4..6b5c58ac1d6 100644 --- a/lib/amd/src/checkbox-toggleall.js +++ b/lib/amd/src/checkbox-toggleall.js @@ -28,20 +28,24 @@ define(['jquery', 'core/pubsub'], function($, PubSub) { checkboxToggled: 'core/checkbox-toggleall:checkboxToggled', }; - var getToggleGroupElements = function(root, toggleGroup) { - return root.find('[data-action="toggle"][data-togglegroup="' + toggleGroup + '"]'); + var getToggleGroupElements = function(root, toggleGroup, exactMatch) { + if (exactMatch) { + return root.find('[data-action="toggle"][data-togglegroup="' + toggleGroup + '"]'); + } else { + return root.find('[data-action="toggle"][data-togglegroup*="' + toggleGroup + '"]'); + } }; var getAllSlaveCheckboxes = function(root, toggleGroup) { return getToggleGroupElements(root, toggleGroup).filter('[data-toggle="slave"]'); }; - var getControlCheckboxes = function(root, toggleGroup) { - return getToggleGroupElements(root, toggleGroup).filter('[data-toggle="master"]'); + var getControlCheckboxes = function(root, toggleGroup, exactMatch) { + return getToggleGroupElements(root, toggleGroup, exactMatch).filter('[data-toggle="master"]'); }; var getActionElements = function(root, toggleGroup) { - return getToggleGroupElements(root, toggleGroup).filter('[data-toggle="action"]'); + return getToggleGroupElements(root, toggleGroup, true).filter('[data-toggle="action"]'); }; var toggleSlavesFromMasters = function(e) { @@ -58,9 +62,8 @@ define(['jquery', 'core/pubsub'], function($, PubSub) { // Set the slave checkboxes from the masters. slaves.prop('checked', targetState); - - // Enable action elements when master checkboxes have been checked. Disable otherwise. - setActionElementStates(root, toggleGroupName, !targetState); + // Trigger 'change' event to toggle other master checkboxes (e.g. parent master checkboxes) and action elements. + slaves.trigger('change'); PubSub.publish(events.checkboxToggled, { root: root, @@ -75,23 +78,32 @@ define(['jquery', 'core/pubsub'], function($, PubSub) { var root = e.data.root; var target = $(e.target); - var toggleGroupName = target.data('togglegroup'); + var toggleGroups = target.data('togglegroup').split(' '); + var toggleGroupLevels = []; + var toggleGroupLevel = ''; + toggleGroups.forEach(function(toggleGroupName) { + toggleGroupLevel += ' ' + toggleGroupName; + toggleGroupLevels.push(toggleGroupLevel.trim()); + }); - var slaves = getAllSlaveCheckboxes(root, toggleGroupName); - var checkedSlaves = slaves.filter(':checked'); - var targetState = (slaves.length === checkedSlaves.length); + toggleGroupLevels.forEach(function(toggleGroupName) { + var slaves = getAllSlaveCheckboxes(root, toggleGroupName); + var checkedSlaves = slaves.filter(':checked'); + var targetState = (slaves.length === checkedSlaves.length); - setMasterStates(root, toggleGroupName, targetState); + // Make sure to toggle the exact master checkbox. + setMasterStates(root, toggleGroupName, targetState, true); - // Enable action elements when there's at least one checkbox checked. Disable otherwise. - setActionElementStates(root, toggleGroupName, !checkedSlaves.length); + // Enable action elements when there's at least one checkbox checked. Disable otherwise. + setActionElementStates(root, toggleGroupName, !checkedSlaves.length); - PubSub.publish(events.checkboxToggled, { - root: root, - toggleGroupName: toggleGroupName, - slaves: slaves, - checkedSlaves: checkedSlaves, - anyChecked: !!checkedSlaves.length, + PubSub.publish(events.checkboxToggled, { + root: root, + toggleGroupName: toggleGroupName, + slaves: slaves, + checkedSlaves: checkedSlaves, + anyChecked: !!checkedSlaves.length, + }); }); }; @@ -99,9 +111,9 @@ define(['jquery', 'core/pubsub'], function($, PubSub) { getActionElements(root, toggleGroupName).prop('disabled', disableActionElements); }; - var setMasterStates = function(root, toggleGroupName, targetState) { + var setMasterStates = function(root, toggleGroupName, targetState, exactMatch) { // Set the master checkboxes value and ARIA labels.. - var masters = getControlCheckboxes(root, toggleGroupName); + var masters = getControlCheckboxes(root, toggleGroupName, exactMatch); masters.prop('checked', targetState); masters.each(function(i, masterCheckbox) { masterCheckbox = $(masterCheckbox); From 6259a9d065a6f14fbd665c68d874474935eed03b Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Fri, 1 Mar 2019 14:43:25 +0800 Subject: [PATCH 3/9] MDL-65928 core: Create a checkbox_toggleall renderable --- lib/classes/output/checkbox_toggleall.php | 103 ++++++++++++++++++++++ lib/outputrenderers.php | 10 +++ lib/templates/checkbox-toggleall.mustache | 62 +++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 lib/classes/output/checkbox_toggleall.php create mode 100644 lib/templates/checkbox-toggleall.mustache diff --git a/lib/classes/output/checkbox_toggleall.php b/lib/classes/output/checkbox_toggleall.php new file mode 100644 index 00000000000..bbbd3e5427c --- /dev/null +++ b/lib/classes/output/checkbox_toggleall.php @@ -0,0 +1,103 @@ +. + +/** + * The renderable for core/checkbox-toggleall. + * + * @package core + * @copyright 2019 Jun Pataleta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core\output; +defined('MOODLE_INTERNAL') || die(); + +use renderable; +use renderer_base; +use stdClass; +use templatable; + +/** + * The checkbox-toggleall renderable class. + * + * @package core + * @copyright 2019 Jun Pataleta + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class checkbox_toggleall implements renderable, templatable { + + /** @var string The name of the group of checkboxes to be toggled. */ + protected $togglegroup; + + /** @var bool $ismaster Whether we're rendering for a master checkbox or a slave checkbox. */ + protected $ismaster; + + /** @var array $options The options for the checkbox. */ + protected $options; + + /** + * Constructor. + * + * @param string $togglegroup The name of the group of checkboxes to be toggled. + * @param bool $ismaster Whether we're rendering for a master checkbox or a slave checkbox. + * @param array $options The options for the checkbox. Valid options are: + *
    + *
  • id string - The element ID.
  • + *
  • name string - The element name.
  • + *
  • classes string - CSS classes that you want to add for your checkbox.
  • + *
  • value string|int - The element's value.
  • + *
  • checked boolean - Whether to render this initially as checked.
  • + *
  • label string - The label for the checkbox element.
  • + *
  • labelclasses string - CSS classes that you want to add for your label.
  • + *
  • selectall string - Master only. The language string that will be used to indicate that clicking on + * the master will select all of the slave checkboxes. Defaults to "Select all".
  • + *
  • deselectall string - Master only. The language string that will be used to indicate that clicking on + * the master will select all of the slave checkboxes. Defaults to "Deselect all".
  • + *
+ */ + public function __construct(string $togglegroup, bool $ismaster, $options = []) { + $this->togglegroup = $togglegroup; + $this->ismaster = $ismaster; + $this->options = $options; + } + + /** + * Export for template. + * + * @param renderer_base $output The renderer. + * @return stdClass + */ + public function export_for_template(renderer_base $output) { + $data = (object)[ + 'togglegroup' => $this->togglegroup, + 'master' => $this->ismaster, + 'id' => $this->options['id'] ?? null, + 'name' => $this->options['name'] ?? null, + 'value' => $this->options['value'] ?? null, + 'classes' => $this->options['classes'] ?? null, + 'label' => $this->options['label'] ?? null, + 'labelclasses' => $this->options['labelclasses'] ?? null, + 'checked' => $this->options['checked'] ?? false, + ]; + + if ($this->ismaster) { + $data->selectall = $this->options['selectall'] ?? get_string('selectall'); + $data->deselectall = $this->options['deselectall'] ?? get_string('deselectall'); + } + + return $data; + } +} diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 0f309bf6408..c75ddf1a171 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -4711,6 +4711,16 @@ EOD; $data = $bar->export_for_template($this); return $this->render_from_template('core/progress_bar', $data); } + + /** + * Renders element for a toggle-all checkbox. + * + * @param \core\output\checkbox_toggleall $element + * @return string + */ + public function render_checkbox_toggleall(\core\output\checkbox_toggleall $element) { + return $this->render_from_template('core/checkbox-toggleall', $element->export_for_template($this)); + } } /** diff --git a/lib/templates/checkbox-toggleall.mustache b/lib/templates/checkbox-toggleall.mustache new file mode 100644 index 00000000000..5834dd84214 --- /dev/null +++ b/lib/templates/checkbox-toggleall.mustache @@ -0,0 +1,62 @@ +{{! + 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 . +}} +{{! + @template core/checkbox-toggleall + + Template for master/slave checkboxes. The master checkbox toggles the checked states of the slave checkboxes. + + Example context (json): + { + "id": "select-all", + "name": "select-all", + "master": true, + "togglegroup": "toggle-group", + "label": "Select everything!" + } +}} +{{#master}} + + {{#label}} + + {{/label}} +{{/master}} +{{^master}} + + {{#label}} + + {{/label}} +{{/master}} + +{{#js}} + {{#master}} + require(['core/checkbox-toggleall'], function(ToggleAll) { + ToggleAll.init(); + }); + {{/master}} +{{/js}} From 5943fc3f957bf87adc5e38764e5044dc2c03b77c Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Fri, 22 Mar 2019 16:29:07 +0800 Subject: [PATCH 4/9] MDL-65928 core: Render master element as button too --- lib/amd/build/checkbox-toggleall.min.js | 2 +- lib/amd/src/checkbox-toggleall.js | 40 +++++++++++++++-------- lib/classes/output/checkbox_toggleall.php | 9 ++++- lib/templates/checkbox-toggleall.mustache | 36 +++++++++++++------- 4 files changed, 60 insertions(+), 27 deletions(-) diff --git a/lib/amd/build/checkbox-toggleall.min.js b/lib/amd/build/checkbox-toggleall.min.js index 3c9c2ec5053..7effb6994b2 100644 --- a/lib/amd/build/checkbox-toggleall.min.js +++ b/lib/amd/build/checkbox-toggleall.min.js @@ -1 +1 @@ -define(["jquery","core/pubsub"],function(a,b){var c=!1,d={checkboxToggled:"core/checkbox-toggleall:checkboxToggled"},e=function(a,b,c){return c?a.find('[data-action="toggle"][data-togglegroup="'+b+'"]'):a.find('[data-action="toggle"][data-togglegroup*="'+b+'"]')},f=function(a,b){return e(a,b).filter('[data-toggle="slave"]')},g=function(a,b,c){return e(a,b,c).filter('[data-toggle="master"]')},h=function(a,b){return e(a,b,!0).filter('[data-toggle="action"]')},i=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup"),i=g.is(":checked"),j=f(e,h),k=j.filter(":checked");l(e,h,i),j.prop("checked",i),j.trigger("change"),b.publish(d.checkboxToggled,{root:e,toggleGroupName:h,slaves:j,checkedSlaves:k,anyChecked:i})},j=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup").split(" "),i=[],j="";h.forEach(function(a){j+=" "+a,i.push(j.trim())}),i.forEach(function(a){var c=f(e,a),g=c.filter(":checked"),h=c.length===g.length;l(e,a,h,!0),k(e,a,!g.length),b.publish(d.checkboxToggled,{root:e,toggleGroupName:a,slaves:c,checkedSlaves:g,anyChecked:!!g.length})})},k=function(a,b,c){h(a,b).prop("disabled",c)},l=function(b,c,d,e){var f=g(b,c,e);f.prop("checked",d),f.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))})},m=function(){if(!c){c=!0;var b=a(document.body);b.on("change",'[data-action="toggle"][data-toggle="master"]',{root:b},i),b.on("change",'[data-action="toggle"][data-toggle="slave"]',{root:b},j)}};return{init:function(){m()},events:d}}); \ No newline at end of file +define(["jquery","core/pubsub"],function(a,b){var c=!1,d={checkboxToggled:"core/checkbox-toggleall:checkboxToggled"},e=function(a,b,c){return c?a.find('[data-action="toggle"][data-togglegroup="'+b+'"]'):a.find('[data-action="toggle"][data-togglegroup*="'+b+'"]')},f=function(a,b){return e(a,b).filter('[data-toggle="slave"]')},g=function(a,b,c){return e(a,b,c).filter('[data-toggle="master"]')},h=function(a,b){return e(a,b,!0).filter('[data-toggle="action"]')},i=function(c){var e,g=c.data.root,h=a(c.target),i=h.data("togglegroup");e=h.is(":checkbox")?h.is(":checked"):1===h.data("checkall");var j=f(g,i),k=j.filter(":checked");l(g,i,e),j.prop("checked",e),j.trigger("change"),b.publish(d.checkboxToggled,{root:g,toggleGroupName:i,slaves:j,checkedSlaves:k,anyChecked:e})},j=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup").split(" "),i=[],j="";h.forEach(function(a){j+=" "+a,i.push(j.trim())}),i.forEach(function(a){var c=f(e,a),g=c.filter(":checked"),h=c.length===g.length;l(e,a,h,!0),k(e,a,!g.length),b.publish(d.checkboxToggled,{root:e,toggleGroupName:a,slaves:c,checkedSlaves:g,anyChecked:!!g.length})})},k=function(a,b,c){h(a,b).prop("disabled",c)},l=function(b,c,d,e){var f=g(b,c,e);f.prop("checked",d),f.each(function(c,e){e=a(e);var f;if(f=d?e.data("toggle-deselectall"):e.data("toggle-selectall"),e.is(":checkbox")){var g=b.find('[for="'+e.attr("id")+'"]');g.length&&g.html()!==f&&g.html(f)}else e.text(f),e.data("checkall",d?0:1)})},m=function(){if(!c){c=!0;var b=a(document.body);b.on("click",'[data-action="toggle"][data-toggle="master"]',{root:b},i),b.on("change",'[data-action="toggle"][data-toggle="slave"]',{root:b},j)}};return{init:function(){m()},events:d}}); \ No newline at end of file diff --git a/lib/amd/src/checkbox-toggleall.js b/lib/amd/src/checkbox-toggleall.js index 6b5c58ac1d6..dcff937786f 100644 --- a/lib/amd/src/checkbox-toggleall.js +++ b/lib/amd/src/checkbox-toggleall.js @@ -53,7 +53,12 @@ define(['jquery', 'core/pubsub'], function($, PubSub) { var target = $(e.target); var toggleGroupName = target.data('togglegroup'); - var targetState = target.is(':checked'); + var targetState; + if (target.is(':checkbox')) { + targetState = target.is(':checked'); + } else { + targetState = target.data('checkall') === 1; + } var slaves = getAllSlaveCheckboxes(root, toggleGroupName); var checkedSlaves = slaves.filter(':checked'); @@ -115,20 +120,27 @@ define(['jquery', 'core/pubsub'], function($, PubSub) { // Set the master checkboxes value and ARIA labels.. var masters = getControlCheckboxes(root, toggleGroupName, exactMatch); 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'); - } + masters.each(function(i, masterElement) { + masterElement = $(masterElement); - if (masterLabel.html() !== targetString) { - masterLabel.html(targetString); + var targetString; + if (targetState) { + targetString = masterElement.data('toggle-deselectall'); + } else { + targetString = masterElement.data('toggle-selectall'); + } + + if (masterElement.is(':checkbox')) { + var masterLabel = root.find('[for="' + masterElement.attr('id') + '"]'); + if (masterLabel.length) { + if (masterLabel.html() !== targetString) { + masterLabel.html(targetString); + } } + } else { + masterElement.text(targetString); + // Set the checkall data attribute. + masterElement.data('checkall', targetState ? 0 : 1); } }); }; @@ -138,7 +150,7 @@ define(['jquery', 'core/pubsub'], function($, PubSub) { registered = true; var root = $(document.body); - root.on('change', '[data-action="toggle"][data-toggle="master"]', {root: root}, toggleSlavesFromMasters); + root.on('click', '[data-action="toggle"][data-toggle="master"]', {root: root}, toggleSlavesFromMasters); root.on('change', '[data-action="toggle"][data-toggle="slave"]', {root: root}, toggleMastersFromSlaves); } }; diff --git a/lib/classes/output/checkbox_toggleall.php b/lib/classes/output/checkbox_toggleall.php index bbbd3e5427c..d40520bf014 100644 --- a/lib/classes/output/checkbox_toggleall.php +++ b/lib/classes/output/checkbox_toggleall.php @@ -48,6 +48,9 @@ class checkbox_toggleall implements renderable, templatable { /** @var array $options The options for the checkbox. */ protected $options; + /** @var bool $isbutton Whether to render this as a button. Applies to master checkboxes only. */ + protected $isbutton; + /** * Constructor. * @@ -67,11 +70,13 @@ class checkbox_toggleall implements renderable, templatable { *
  • deselectall string - Master only. The language string that will be used to indicate that clicking on * the master will select all of the slave checkboxes. Defaults to "Deselect all".
  • * + * @param bool $isbutton Whether to render this as a button. Applies to master only. */ - public function __construct(string $togglegroup, bool $ismaster, $options = []) { + public function __construct(string $togglegroup, bool $ismaster, $options = [], $isbutton = false) { $this->togglegroup = $togglegroup; $this->ismaster = $ismaster; $this->options = $options; + $this->isbutton = $ismaster && $isbutton; } /** @@ -98,6 +103,8 @@ class checkbox_toggleall implements renderable, templatable { $data->deselectall = $this->options['deselectall'] ?? get_string('deselectall'); } + $data->isbutton = $this->isbutton; + return $data; } } diff --git a/lib/templates/checkbox-toggleall.mustache b/lib/templates/checkbox-toggleall.mustache index 5834dd84214..effe624850d 100644 --- a/lib/templates/checkbox-toggleall.mustache +++ b/lib/templates/checkbox-toggleall.mustache @@ -29,17 +29,31 @@ } }} {{#master}} - - {{#label}} - - {{/label}} + {{#isbutton}} + + {{/isbutton}} + {{^isbutton}} + + {{#label}} + + {{/label}} + {{/isbutton}} {{/master}} {{^master}} Date: Mon, 25 Mar 2019 10:58:00 +0800 Subject: [PATCH 5/9] MDL-65928 core: Split toggle-all elements into separate templates For cleaner templates, split checkbox toggle-all elements into three templates: * master checkbox (master control rendered as a checkbox) * master button (master control rendered as a button) * slave checkbox --- lib/classes/output/checkbox_toggleall.php | 19 ++++- lib/outputrenderers.php | 2 +- .../checkbox-toggleall-master-button.mustache | 49 ++++++++++++ .../checkbox-toggleall-master.mustache | 50 ++++++++++++ .../checkbox-toggleall-slave.mustache | 40 ++++++++++ lib/templates/checkbox-toggleall.mustache | 76 ------------------- 6 files changed, 156 insertions(+), 80 deletions(-) create mode 100644 lib/templates/checkbox-toggleall-master-button.mustache create mode 100644 lib/templates/checkbox-toggleall-master.mustache create mode 100644 lib/templates/checkbox-toggleall-slave.mustache delete mode 100644 lib/templates/checkbox-toggleall.mustache diff --git a/lib/classes/output/checkbox_toggleall.php b/lib/classes/output/checkbox_toggleall.php index d40520bf014..be81f80be91 100644 --- a/lib/classes/output/checkbox_toggleall.php +++ b/lib/classes/output/checkbox_toggleall.php @@ -88,7 +88,6 @@ class checkbox_toggleall implements renderable, templatable { public function export_for_template(renderer_base $output) { $data = (object)[ 'togglegroup' => $this->togglegroup, - 'master' => $this->ismaster, 'id' => $this->options['id'] ?? null, 'name' => $this->options['name'] ?? null, 'value' => $this->options['value'] ?? null, @@ -103,8 +102,22 @@ class checkbox_toggleall implements renderable, templatable { $data->deselectall = $this->options['deselectall'] ?? get_string('deselectall'); } - $data->isbutton = $this->isbutton; - return $data; } + + /** + * Fetches the appropriate template for the checkbox toggle all element. + * + * @return string + */ + public function get_template() { + if ($this->ismaster) { + if ($this->isbutton) { + return 'core/checkbox-toggleall-master-button'; + } else { + return 'core/checkbox-toggleall-master'; + } + } + return 'core/checkbox-toggleall-slave'; + } } diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index c75ddf1a171..3328e4cdb1d 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -4719,7 +4719,7 @@ EOD; * @return string */ public function render_checkbox_toggleall(\core\output\checkbox_toggleall $element) { - return $this->render_from_template('core/checkbox-toggleall', $element->export_for_template($this)); + return $this->render_from_template($element->get_template(), $element->export_for_template($this)); } } diff --git a/lib/templates/checkbox-toggleall-master-button.mustache b/lib/templates/checkbox-toggleall-master-button.mustache new file mode 100644 index 00000000000..8e3cc3a470d --- /dev/null +++ b/lib/templates/checkbox-toggleall-master-button.mustache @@ -0,0 +1,49 @@ +{{! + 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 . +}} +{{! + @template core/checkbox-toggleall-master-button + + Template for a master button in a toggle group. The master button toggles the checked states of the slave checkboxes. + + Example context (json): + { + "id": "select-all", + "name": "select-all", + "togglegroup": "toggle-group", + "label": "Select everything!", + "checked": true, + "classes": "p-1", + "selectall": "Select all", + "deselectall": "Deselect all" + } +}} + + +{{#js}} + require(['core/checkbox-toggleall'], function(ToggleAll) { + ToggleAll.init(); + }); +{{/js}} diff --git a/lib/templates/checkbox-toggleall-master.mustache b/lib/templates/checkbox-toggleall-master.mustache new file mode 100644 index 00000000000..4f2f367d5bc --- /dev/null +++ b/lib/templates/checkbox-toggleall-master.mustache @@ -0,0 +1,50 @@ +{{! + 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 . +}} +{{! + @template core/checkbox-toggleall-master + + Template for a master checkbox in a toggle group. The master checkbox toggles the checked states of the slave checkboxes. + + Example context (json): + { + "id": "select-all", + "name": "select-all", + "togglegroup": "toggle-group", + "label": "Select everything!", + "checked": true, + "classes": "p-1", + "selectall": "Select all", + "deselectall": "Deselect all" + } +}} + +{{#label}} + +{{/label}} + +{{#js}} + require(['core/checkbox-toggleall'], function(ToggleAll) { + ToggleAll.init(); + }); +{{/js}} diff --git a/lib/templates/checkbox-toggleall-slave.mustache b/lib/templates/checkbox-toggleall-slave.mustache new file mode 100644 index 00000000000..6511fedf7ea --- /dev/null +++ b/lib/templates/checkbox-toggleall-slave.mustache @@ -0,0 +1,40 @@ +{{! + 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 . +}} +{{! + @template core/checkbox-toggleall-slave + + Template for a slave checkbox in a toggle group. + + Example context (json): + { + "id": "select-item", + "name": "select-item", + "togglegroup": "toggle-group", + "label": "Select me!", + "checked": true, + "classes": "p-1" + } +}} + +{{#label}} + +{{/label}} diff --git a/lib/templates/checkbox-toggleall.mustache b/lib/templates/checkbox-toggleall.mustache deleted file mode 100644 index effe624850d..00000000000 --- a/lib/templates/checkbox-toggleall.mustache +++ /dev/null @@ -1,76 +0,0 @@ -{{! - 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 . -}} -{{! - @template core/checkbox-toggleall - - Template for master/slave checkboxes. The master checkbox toggles the checked states of the slave checkboxes. - - Example context (json): - { - "id": "select-all", - "name": "select-all", - "master": true, - "togglegroup": "toggle-group", - "label": "Select everything!" - } -}} -{{#master}} - {{#isbutton}} - - {{/isbutton}} - {{^isbutton}} - - {{#label}} - - {{/label}} - {{/isbutton}} -{{/master}} -{{^master}} - - {{#label}} - - {{/label}} -{{/master}} - -{{#js}} - {{#master}} - require(['core/checkbox-toggleall'], function(ToggleAll) { - ToggleAll.init(); - }); - {{/master}} -{{/js}} From c7ad9f343939ee508fba6c33b001b291f8eeb3c5 Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Thu, 21 Dec 2017 14:22:45 +1300 Subject: [PATCH 6/9] MDL-65928 mod_choice: Use core/checkbox_toggleall --- .../amd/build/select_all_choices.min.js | 1 - mod/choice/amd/src/select_all_choices.js | 33 ----------- mod/choice/renderer.php | 59 ++++++++++++------- 3 files changed, 37 insertions(+), 56 deletions(-) delete mode 100644 mod/choice/amd/build/select_all_choices.min.js delete mode 100644 mod/choice/amd/src/select_all_choices.js diff --git a/mod/choice/amd/build/select_all_choices.min.js b/mod/choice/amd/build/select_all_choices.min.js deleted file mode 100644 index bdbfb171928..00000000000 --- a/mod/choice/amd/build/select_all_choices.min.js +++ /dev/null @@ -1 +0,0 @@ -define(["jquery"],function(a){return{init:function(){a(".selectallnone a").on("click",function(b){b.preventDefault(),a("#attemptsform").find("input:checkbox").prop("checked",a(this).data("selectInfo"))})}}}); \ No newline at end of file diff --git a/mod/choice/amd/src/select_all_choices.js b/mod/choice/amd/src/select_all_choices.js deleted file mode 100644 index 5a4a4fb8c05..00000000000 --- a/mod/choice/amd/src/select_all_choices.js +++ /dev/null @@ -1,33 +0,0 @@ -// 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 . - -/** - * Ticks or unticks all checkboxes when clicking the Select all or Deselect all elements when viewing the response overview. - * - * @module mod_choice/select_all_choices - * @copyright 2017 Marcus Fabriczy - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -define(['jquery'], function($) { - return { - init: function () { - $('.selectallnone a').on('click', function(e) { - e.preventDefault(); - $('#attemptsform').find('input:checkbox').prop('checked', $(this).data('selectInfo')); - }); - } - }; -}); diff --git a/mod/choice/renderer.php b/mod/choice/renderer.php index 4e8d7e2d416..8b167d73b9b 100644 --- a/mod/choice/renderer.php +++ b/mod/choice/renderer.php @@ -133,7 +133,7 @@ class mod_choice_renderer extends plugin_renderer_base { * @return string */ public function display_publish_name_vertical($choices) { - global $PAGE; + global $PAGE, $OUTPUT; $html =''; $html .= html_writer::tag('h3',format_string(get_string("responses", "choice"))); @@ -179,22 +179,41 @@ class mod_choice_renderer extends plugin_renderer_base { foreach ($choices->options as $optionid => $options) { $celloption = clone($celldefault); $cellusernumber = clone($celldefault); - $cellusernumber->style = 'text-align: center;'; - $celltext = ''; if ($choices->showunanswered && $optionid == 0) { - $celltext = get_string('notanswered', 'choice'); + $headertitle = get_string('notanswered', 'choice'); } else if ($optionid > 0) { - $celltext = format_string($choices->options[$optionid]->text); + $headertitle = format_string($choices->options[$optionid]->text); + } + $celltext = $headertitle; + + // Render select/deselect all checkbox for this option. + if ($choices->viewresponsecapability && $choices->deleterepsonsecapability) { + + // Build the select/deselect all for this option. + $selectallid = 'selectall-option-' . $optionid; + $selectalltext = get_string('selectall', 'moodle') . ' ' . $headertitle; + $deselectalltext = get_string('deselectall', 'moodle') . ' ' . $headertitle; + $mastercheckbox = new \core\output\checkbox_toggleall($selectallid, true, [ + 'id' => $selectallid, + 'name' => $selectallid, + 'value' => 1, + 'selectall' => $selectalltext, + 'deselectall' => $deselectalltext, + 'label' => $selectalltext, + 'labelclasses' => 'accesshide', + ]); + + $celltext .= html_writer::div($OUTPUT->render($mastercheckbox)); } $numberofuser = 0; if (!empty($options->user) && count($options->user) > 0) { $numberofuser = count($options->user); } - $celloption->text = $celltext; + $celloption->text = html_writer::div($celltext, 'text-center'); $optionsnames[$optionid] = $celltext; - $cellusernumber->text = $numberofuser; + $cellusernumber->text = html_writer::div($numberofuser, 'text-center'); $columns['options'][] = $celloption; $columns['usernumber'][] = $cellusernumber; @@ -230,8 +249,6 @@ class mod_choice_renderer extends plugin_renderer_base { $checkbox = ''; if ($choices->viewresponsecapability && $choices->deleterepsonsecapability) { $checkboxid = 'attempt-user' . $user->id . '-option' . $optionid; - $checkbox .= html_writer::label($userfullname . ' ' . $optionsnames[$optionid], - $checkboxid, false, array('class' => 'accesshide')); if ($optionid > 0) { $checkboxname = 'attemptid[]'; $checkboxvalue = $user->answerid; @@ -239,8 +256,17 @@ class mod_choice_renderer extends plugin_renderer_base { $checkboxname = 'userid[]'; $checkboxvalue = $user->id; } - $checkbox .= html_writer::checkbox($checkboxname, $checkboxvalue, '', null, - array('id' => $checkboxid, 'class' => 'mr-1')); + + $togglegroup = 'selectall-option-' . $optionid; + $slavecheckbox = new \core\output\checkbox_toggleall($togglegroup, false, [ + 'id' => $checkboxid, + 'name' => $checkboxname, + 'class' => 'mr-1', + 'value' => $checkboxvalue, + 'label' => $userfullname . ' ' . $options->text, + 'labelclasses' => 'accesshide', + ]); + $checkbox = $OUTPUT->render($slavecheckbox); } $userimage = $this->output->user_picture($user, array('courseid' => $choices->courseid, 'link' => false)); $profileurl = new moodle_url('/user/view.php', array('id' => $user->id, 'course' => $choices->courseid)); @@ -262,15 +288,6 @@ class mod_choice_renderer extends plugin_renderer_base { $actiondata = ''; if ($choices->viewresponsecapability && $choices->deleterepsonsecapability) { - $selecturl = new moodle_url('#'); - - $actiondata .= html_writer::start_div('selectallnone'); - $actiondata .= html_writer::link($selecturl, get_string('selectall'), ['data-select-info' => true]) . ' / '; - - $actiondata .= html_writer::link($selecturl, get_string('deselectall'), ['data-select-info' => false]); - - $actiondata .= html_writer::end_div(); - $actionurl = new moodle_url($PAGE->url, array('sesskey'=>sesskey(), 'action'=>'delete_confirmation()')); $actionoptions = array('delete' => get_string('delete')); foreach ($choices->options as $optionid => $option) { @@ -282,8 +299,6 @@ class mod_choice_renderer extends plugin_renderer_base { array('' => get_string('chooseaction', 'choice')), 'attemptsform'); $select->set_label(get_string('withselected', 'choice')); - $PAGE->requires->js_call_amd('mod_choice/select_all_choices', 'init'); - $actiondata .= $this->output->render($select); } $html .= html_writer::tag('div', $actiondata, array('class'=>'responseaction')); From 71eadbec622537dfb439eb8eb1a22b36efa16497 Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Fri, 15 Mar 2019 10:31:10 +0800 Subject: [PATCH 7/9] MDL-65928 mod_choice: Add select/deselect all master button --- mod/choice/renderer.php | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/mod/choice/renderer.php b/mod/choice/renderer.php index 8b167d73b9b..ecd3e6a97dd 100644 --- a/mod/choice/renderer.php +++ b/mod/choice/renderer.php @@ -191,10 +191,11 @@ class mod_choice_renderer extends plugin_renderer_base { if ($choices->viewresponsecapability && $choices->deleterepsonsecapability) { // Build the select/deselect all for this option. - $selectallid = 'selectall-option-' . $optionid; + $selectallid = 'select-response-option-' . $optionid; + $togglegroup = 'responses response-option-' . $optionid; $selectalltext = get_string('selectall', 'moodle') . ' ' . $headertitle; $deselectalltext = get_string('deselectall', 'moodle') . ' ' . $headertitle; - $mastercheckbox = new \core\output\checkbox_toggleall($selectallid, true, [ + $mastercheckbox = new \core\output\checkbox_toggleall($togglegroup, true, [ 'id' => $selectallid, 'name' => $selectallid, 'value' => 1, @@ -257,7 +258,7 @@ class mod_choice_renderer extends plugin_renderer_base { $checkboxvalue = $user->id; } - $togglegroup = 'selectall-option-' . $optionid; + $togglegroup = 'responses response-option-' . $optionid; $slavecheckbox = new \core\output\checkbox_toggleall($togglegroup, false, [ 'id' => $checkboxid, 'name' => $checkboxname, @@ -288,6 +289,18 @@ class mod_choice_renderer extends plugin_renderer_base { $actiondata = ''; if ($choices->viewresponsecapability && $choices->deleterepsonsecapability) { + // Build the select/deselect all for all of options. + $selectallid = 'select-all-responses'; + $togglegroup = 'responses'; + $selectallcheckbox = new \core\output\checkbox_toggleall($togglegroup, true, [ + 'id' => $selectallid, + 'name' => $selectallid, + 'value' => 1, + 'label' => get_string('selectall'), + 'classes' => 'mr-1' + ], true); + $actiondata .= $OUTPUT->render($selectallcheckbox); + $actionurl = new moodle_url($PAGE->url, array('sesskey'=>sesskey(), 'action'=>'delete_confirmation()')); $actionoptions = array('delete' => get_string('delete')); foreach ($choices->options as $optionid => $option) { @@ -295,9 +308,16 @@ class mod_choice_renderer extends plugin_renderer_base { $actionoptions['choose_'.$optionid] = get_string('chooseoption', 'choice', $option->text); } } - $select = new single_select($actionurl, 'action', $actionoptions, null, - array('' => get_string('chooseaction', 'choice')), 'attemptsform'); + $selectattributes = [ + 'data-action' => 'toggle', + 'data-togglegroup' => 'responses', + 'data-toggle' => 'action', + ]; + $selectnothing = ['' => get_string('chooseaction', 'choice')]; + $select = new single_select($actionurl, 'action', $actionoptions, null, $selectnothing, 'attemptsform'); $select->set_label(get_string('withselected', 'choice')); + $select->disabled = true; + $select->attributes = $selectattributes; $actiondata .= $this->output->render($select); } From 05b25140d054a4812d8a0b21293e6c8fd95d843c Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Fri, 12 Jul 2019 15:42:03 +0800 Subject: [PATCH 8/9] MDL-65928 core: Use 'starts with' for non-exact toggle group matching * Use the 'starts with' selector when matching toggle groups by name in non-exact mode to avoid potential conflicts with other toggle groups. * Note down the required data attributes for JS in the template documentations. * Add a note of this output component in upgrade.txt. * Add doc blocks for the core/checkbox-toggleall attributes and methods. --- lib/amd/build/checkbox-toggleall.min.js | 2 +- lib/amd/src/checkbox-toggleall.js | 88 ++++++++++++++++++- .../checkbox-toggleall-master-button.mustache | 5 ++ .../checkbox-toggleall-master.mustache | 5 ++ .../checkbox-toggleall-slave.mustache | 5 ++ lib/upgrade.txt | 4 + 6 files changed, 105 insertions(+), 4 deletions(-) diff --git a/lib/amd/build/checkbox-toggleall.min.js b/lib/amd/build/checkbox-toggleall.min.js index 7effb6994b2..b52290f8197 100644 --- a/lib/amd/build/checkbox-toggleall.min.js +++ b/lib/amd/build/checkbox-toggleall.min.js @@ -1 +1 @@ -define(["jquery","core/pubsub"],function(a,b){var c=!1,d={checkboxToggled:"core/checkbox-toggleall:checkboxToggled"},e=function(a,b,c){return c?a.find('[data-action="toggle"][data-togglegroup="'+b+'"]'):a.find('[data-action="toggle"][data-togglegroup*="'+b+'"]')},f=function(a,b){return e(a,b).filter('[data-toggle="slave"]')},g=function(a,b,c){return e(a,b,c).filter('[data-toggle="master"]')},h=function(a,b){return e(a,b,!0).filter('[data-toggle="action"]')},i=function(c){var e,g=c.data.root,h=a(c.target),i=h.data("togglegroup");e=h.is(":checkbox")?h.is(":checked"):1===h.data("checkall");var j=f(g,i),k=j.filter(":checked");l(g,i,e),j.prop("checked",e),j.trigger("change"),b.publish(d.checkboxToggled,{root:g,toggleGroupName:i,slaves:j,checkedSlaves:k,anyChecked:e})},j=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup").split(" "),i=[],j="";h.forEach(function(a){j+=" "+a,i.push(j.trim())}),i.forEach(function(a){var c=f(e,a),g=c.filter(":checked"),h=c.length===g.length;l(e,a,h,!0),k(e,a,!g.length),b.publish(d.checkboxToggled,{root:e,toggleGroupName:a,slaves:c,checkedSlaves:g,anyChecked:!!g.length})})},k=function(a,b,c){h(a,b).prop("disabled",c)},l=function(b,c,d,e){var f=g(b,c,e);f.prop("checked",d),f.each(function(c,e){e=a(e);var f;if(f=d?e.data("toggle-deselectall"):e.data("toggle-selectall"),e.is(":checkbox")){var g=b.find('[for="'+e.attr("id")+'"]');g.length&&g.html()!==f&&g.html(f)}else e.text(f),e.data("checkall",d?0:1)})},m=function(){if(!c){c=!0;var b=a(document.body);b.on("click",'[data-action="toggle"][data-toggle="master"]',{root:b},i),b.on("change",'[data-action="toggle"][data-toggle="slave"]',{root:b},j)}};return{init:function(){m()},events:d}}); \ No newline at end of file +define(["jquery","core/pubsub"],function(a,b){var c=!1,d={checkboxToggled:"core/checkbox-toggleall:checkboxToggled"},e=function(a,b,c){return c?a.find('[data-action="toggle"][data-togglegroup="'+b+'"]'):a.find('[data-action="toggle"][data-togglegroup^="'+b+'"]')},f=function(a,b){return e(a,b,!1).filter('[data-toggle="slave"]')},g=function(a,b,c){return e(a,b,c).filter('[data-toggle="master"]')},h=function(a,b){return e(a,b,!0).filter('[data-toggle="action"]')},i=function(c){var e,g=c.data.root,h=a(c.target),i=h.data("togglegroup");e=h.is(":checkbox")?h.is(":checked"):1===h.data("checkall");var j=f(g,i),k=j.filter(":checked");l(g,i,e,!1),j.prop("checked",e),j.trigger("change"),b.publish(d.checkboxToggled,{root:g,toggleGroupName:i,slaves:j,checkedSlaves:k,anyChecked:e})},j=function(c){var e=c.data.root,g=a(c.target),h=g.data("togglegroup").split(" "),i=[],j="";h.forEach(function(a){j+=" "+a,i.push(j.trim())}),i.forEach(function(a){var c=f(e,a),g=c.filter(":checked"),h=c.length===g.length;l(e,a,h,!0),k(e,a,!g.length),b.publish(d.checkboxToggled,{root:e,toggleGroupName:a,slaves:c,checkedSlaves:g,anyChecked:!!g.length})})},k=function(a,b,c){h(a,b).prop("disabled",c)},l=function(b,c,d,e){var f=g(b,c,e);f.prop("checked",d),f.each(function(c,e){e=a(e);var f;if(f=d?e.data("toggle-deselectall"):e.data("toggle-selectall"),e.is(":checkbox")){var g=b.find('[for="'+e.attr("id")+'"]');g.length&&g.html()!==f&&g.html(f)}else e.text(f),e.data("checkall",d?0:1)})},m=function(){if(!c){c=!0;var b=a(document.body);b.on("click",'[data-action="toggle"][data-toggle="master"]',{root:b},i),b.on("change",'[data-action="toggle"][data-toggle="slave"]',{root:b},j)}};return{init:function(){m()},events:d}}); \ No newline at end of file diff --git a/lib/amd/src/checkbox-toggleall.js b/lib/amd/src/checkbox-toggleall.js index dcff937786f..345db8ffaa4 100644 --- a/lib/amd/src/checkbox-toggleall.js +++ b/lib/amd/src/checkbox-toggleall.js @@ -22,32 +22,85 @@ */ define(['jquery', 'core/pubsub'], function($, PubSub) { + /** + * Whether event listeners have already been registered. + * + * @private + * @type {boolean} + */ var registered = false; + /** + * List of custom events that this module publishes. + * + * @private + * @type {{checkboxToggled: string}} + */ var events = { checkboxToggled: 'core/checkbox-toggleall:checkboxToggled', }; + /** + * Fetches elements that are member of a given toggle group. + * + * @private + * @param {jQuery} root The root jQuery element. + * @param {string} toggleGroup The toggle group name that we're searching form. + * @param {boolean} exactMatch Whether we want an exact match we just want to match toggle groups that start with the given + * toggle group name. + * @returns {jQuery} The elements matching the given toggle group. + */ var getToggleGroupElements = function(root, toggleGroup, exactMatch) { if (exactMatch) { return root.find('[data-action="toggle"][data-togglegroup="' + toggleGroup + '"]'); } else { - return root.find('[data-action="toggle"][data-togglegroup*="' + toggleGroup + '"]'); + return root.find('[data-action="toggle"][data-togglegroup^="' + toggleGroup + '"]'); } }; + /** + * Fetches the slave checkboxes for a given toggle group. + * + * @private + * @param {jQuery} root The root jQuery element. + * @param {string} toggleGroup The toggle group name. + * @returns {jQuery} The slave checkboxes belonging to the toggle group. + */ var getAllSlaveCheckboxes = function(root, toggleGroup) { - return getToggleGroupElements(root, toggleGroup).filter('[data-toggle="slave"]'); + return getToggleGroupElements(root, toggleGroup, false).filter('[data-toggle="slave"]'); }; + /** + * Fetches the master elements (checkboxes or buttons) that control the slave checkboxes in a given toggle group. + * + * @private + * @param {jQuery} root The root jQuery element. + * @param {string} toggleGroup The toggle group name. + * @param {boolean} exactMatch + * @returns {jQuery} The control elements belonging to the toggle group. + */ var getControlCheckboxes = function(root, toggleGroup, exactMatch) { return getToggleGroupElements(root, toggleGroup, exactMatch).filter('[data-toggle="master"]'); }; + /** + * Fetches the action elements that perform actions on the selected checkboxes in a given toggle group. + * + * @private + * @param {jQuery} root The root jQuery element. + * @param {string} toggleGroup The toggle group name. + * @returns {jQuery} The action elements belonging to the toggle group. + */ var getActionElements = function(root, toggleGroup) { return getToggleGroupElements(root, toggleGroup, true).filter('[data-toggle="action"]'); }; + /** + * Toggles the slave checkboxes in a given toggle group when a master element in that toggle group is toggled. + * + * @private + * @param {Object} e The event object. + */ var toggleSlavesFromMasters = function(e) { var root = e.data.root; var target = $(e.target); @@ -63,7 +116,7 @@ define(['jquery', 'core/pubsub'], function($, PubSub) { var slaves = getAllSlaveCheckboxes(root, toggleGroupName); var checkedSlaves = slaves.filter(':checked'); - setMasterStates(root, toggleGroupName, targetState); + setMasterStates(root, toggleGroupName, targetState, false); // Set the slave checkboxes from the masters. slaves.prop('checked', targetState); @@ -79,6 +132,13 @@ define(['jquery', 'core/pubsub'], function($, PubSub) { }); }; + /** + * Toggles the master checkboxes in a given toggle group when all or none of the slave checkboxes in the same toggle group + * have been selected. + * + * @private + * @param {Object} e The event object. + */ var toggleMastersFromSlaves = function(e) { var root = e.data.root; var target = $(e.target); @@ -112,10 +172,27 @@ define(['jquery', 'core/pubsub'], function($, PubSub) { }); }; + /** + * Enables or disables the action elements. + * + * @private + * @param {jQuery} root The root jQuery element. + * @param {string} toggleGroupName The toggle group name of the action element(s). + * @param {boolean} disableActionElements Whether to disable or to enable the action elements. + */ var setActionElementStates = function(root, toggleGroupName, disableActionElements) { getActionElements(root, toggleGroupName).prop('disabled', disableActionElements); }; + /** + * Selects or deselects the master elements. + * + * @private + * @param {jQuery} root The root jQuery element. + * @param {string} toggleGroupName The toggle group name of the master element(s). + * @param {boolean} targetState Whether to select (true) or deselect (false). + * @param {boolean} exactMatch Whether to do an exact match for the toggle group name or not. + */ var setMasterStates = function(root, toggleGroupName, targetState, exactMatch) { // Set the master checkboxes value and ARIA labels.. var masters = getControlCheckboxes(root, toggleGroupName, exactMatch); @@ -145,6 +222,11 @@ define(['jquery', 'core/pubsub'], function($, PubSub) { }); }; + /** + * Registers the event listeners. + * + * @private + */ var registerListeners = function() { if (!registered) { registered = true; diff --git a/lib/templates/checkbox-toggleall-master-button.mustache b/lib/templates/checkbox-toggleall-master-button.mustache index 8e3cc3a470d..4648b4303c4 100644 --- a/lib/templates/checkbox-toggleall-master-button.mustache +++ b/lib/templates/checkbox-toggleall-master-button.mustache @@ -19,6 +19,11 @@ Template for a master button in a toggle group. The master button toggles the checked states of the slave checkboxes. + Data attributes required for JS: + * data-action + * data-toggle + * data-togglegroup + Example context (json): { "id": "select-all", diff --git a/lib/templates/checkbox-toggleall-master.mustache b/lib/templates/checkbox-toggleall-master.mustache index 4f2f367d5bc..1a514a4ddb5 100644 --- a/lib/templates/checkbox-toggleall-master.mustache +++ b/lib/templates/checkbox-toggleall-master.mustache @@ -19,6 +19,11 @@ Template for a master checkbox in a toggle group. The master checkbox toggles the checked states of the slave checkboxes. + Data attributes required for JS: + * data-action + * data-toggle + * data-togglegroup + Example context (json): { "id": "select-all", diff --git a/lib/templates/checkbox-toggleall-slave.mustache b/lib/templates/checkbox-toggleall-slave.mustache index 6511fedf7ea..ac013add67e 100644 --- a/lib/templates/checkbox-toggleall-slave.mustache +++ b/lib/templates/checkbox-toggleall-slave.mustache @@ -19,6 +19,11 @@ Template for a slave checkbox in a toggle group. + Data attributes required for JS: + * data-action + * data-toggle + * data-togglegroup + Example context (json): { "id": "select-item", diff --git a/lib/upgrade.txt b/lib/upgrade.txt index 3c5e180c8d1..a6e58650a77 100644 --- a/lib/upgrade.txt +++ b/lib/upgrade.txt @@ -14,6 +14,10 @@ information provided here is intended especially for developers. * allow_switch() * Remove duplicate font-awesome SCSS, Please see /theme/boost/scss/fontawesome for usage (MDL-65936) * Remove lib/pear/Crypt/CHAP.php (MDL-65747) +* New output component available: \core\output\checkbox_toggleall + - This allows developers to easily output groups of checkboxes that can be toggled by master controls in the form of a checkbox or + a button. Action elements which perform actions on the selected checkboxes can also be enabled/disabled depending on whether + at least a single checkbox item is selected or not. === 3.7 === From 622d83d7bcc2a74846ca4e02190e15c8f8706116 Mon Sep 17 00:00:00 2001 From: Jun Pataleta Date: Fri, 12 Jul 2019 15:43:40 +0800 Subject: [PATCH 9/9] MDL-65928 mod_choice: Use proper string for 'select/deselect all' labels * Because string concatenation is bad! --- mod/choice/lang/en/choice.php | 2 ++ mod/choice/renderer.php | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/mod/choice/lang/en/choice.php b/mod/choice/lang/en/choice.php index f9ec368a556..7aa6252052b 100644 --- a/mod/choice/lang/en/choice.php +++ b/mod/choice/lang/en/choice.php @@ -70,6 +70,7 @@ $string['choice:view'] = 'View choice activity'; $string['chooseaction'] = 'Choose an action ...'; $string['chooseoption'] = 'Choose: {$a}'; $string['description'] = 'Description'; +$string['deselectalloption'] = 'Deselect all "{$a}"'; $string['includeinactive'] = 'Include responses from inactive/suspended users'; $string['indicator:cognitivedepth'] = 'Choice cognitive'; $string['indicator:cognitivedepth_help'] = 'This indicator is based on the cognitive depth reached by the student in a Choice activity.'; @@ -134,6 +135,7 @@ $string['responsesto'] = 'Responses to {$a}'; $string['results'] = 'Results'; $string['savemychoice'] = 'Save my choice'; $string['search:activity'] = 'Choice - activity information'; +$string['selectalloption'] = 'Select all "{$a}"'; $string['showpreview'] = 'Show preview'; $string['showpreview_help'] = 'Allow students to preview the available options before the choice is opened for submission.'; $string['showunanswered'] = 'Show column for unanswered'; diff --git a/mod/choice/renderer.php b/mod/choice/renderer.php index ecd3e6a97dd..2811996f4a8 100644 --- a/mod/choice/renderer.php +++ b/mod/choice/renderer.php @@ -193,8 +193,8 @@ class mod_choice_renderer extends plugin_renderer_base { // Build the select/deselect all for this option. $selectallid = 'select-response-option-' . $optionid; $togglegroup = 'responses response-option-' . $optionid; - $selectalltext = get_string('selectall', 'moodle') . ' ' . $headertitle; - $deselectalltext = get_string('deselectall', 'moodle') . ' ' . $headertitle; + $selectalltext = get_string('selectalloption', 'choice', $headertitle); + $deselectalltext = get_string('deselectalloption', 'choice', $headertitle); $mastercheckbox = new \core\output\checkbox_toggleall($togglegroup, true, [ 'id' => $selectallid, 'name' => $selectallid,