From e3690a392dedc33a3b9e95e343aa3984edb87b80 Mon Sep 17 00:00:00 2001 From: Dongsheng Cai Date: Tue, 25 May 2021 20:38:51 +1000 Subject: [PATCH] MDL-70846 accessibility: update tree attributes to pass a11y check - Move aria-* atrributes from

to

  • - Move "role" attribute from

    to

  • - Update behat tests Based on reference implementation from: - https://www.w3.org/TR/wai-aria-practices-1.1/examples/treeview/treeview-2/treeview-2a.html - https://www.w3.org/WAI/GL/wiki/Using_ARIA_trees --- .../amd/build/ajax_response_renderer.min.js | 2 +- .../build/ajax_response_renderer.min.js.map | 2 +- .../amd/src/ajax_response_renderer.js | 20 ++++++++-------- blocks/navigation/renderer.php | 18 +++++++------- blocks/navigation/styles.css | 2 +- blocks/settings/renderer.php | 18 +++++++------- blocks/settings/styles.css | 2 +- lib/amd/build/tree.min.js | 2 +- lib/amd/build/tree.min.js.map | 2 +- lib/amd/src/tree.js | 5 ++-- lib/tests/behat/behat_navigation.php | 24 +++++++++---------- theme/boost/scss/moodle/blocks.scss | 12 +++++----- theme/boost/style/moodle.css | 12 +++++----- theme/classic/style/moodle.css | 12 +++++----- 14 files changed, 67 insertions(+), 66 deletions(-) diff --git a/blocks/navigation/amd/build/ajax_response_renderer.min.js b/blocks/navigation/amd/build/ajax_response_renderer.min.js index 7be3a49539b..a3d3c377c11 100644 --- a/blocks/navigation/amd/build/ajax_response_renderer.min.js +++ b/blocks/navigation/amd/build/ajax_response_renderer.min.js @@ -1,2 +1,2 @@ -function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("block_navigation/ajax_response_renderer",["jquery","core/templates","core/notification","core/url","core/aria"],function(a,b,c,d,e){var g={ACTIVITY:40,RESOURCE:50};function f(h,i){var j=a("");j.attr("role","group");e.hide(j);a.each(i,function(e,h){if("object"!==_typeof(h)){return}var i=a("
  • "),k=a("

    "),l=h.id||h.key+"_tree_item",m=null,n=h.expandable||h.haschildren?!0:!1;k.addClass("tree_item");k.attr("id",l);k.attr("role","treeitem");k.attr("tabindex","-1");if(h.requiresajaxloading){k.attr("data-requires-ajax",!0);k.attr("data-node-id",h.id);k.attr("data-node-key",h.key);k.attr("data-node-type",h.type)}if(n){i.addClass("collapsed contains_branch");k.attr("aria-expanded",!1);k.addClass("branch")}var o=null;if(h.link){var p=a("");o=p;p.append(""+h.name+"");if(h.hidden){p.addClass("dimmed")}k.append(p)}else{var q=a("");o=q;q.append(""+h.name+"");if(h.hidden){q.addClass("dimmed")}k.append(q)}if(h.icon&&(!n||h.type===g.ACTIVITY||h.type===g.RESOURCE)){i.addClass("item_with_icon");k.addClass("hasicon");if(h.type===g.ACTIVITY||h.type===g.RESOURCE){m=a("");m.attr("alt",h.icon.alt);m.attr("title",h.icon.title);m.attr("src",d.imageUrl(h.icon.pix,h.icon.component));a.each(h.icon.classes,function(a,b){m.addClass(b)});o.prepend(m)}else{if("moodle"==h.icon.component){h.icon.component="core"}b.renderPix(h.icon.pix,h.icon.component,h.icon.title).then(function(a){o.prepend(a)}).catch(c.exception)}}i.append(k);j.append(i);if(h.children&&h.children.length){f(k,h.children)}else if(n&&!h.requiresajaxloading){i.removeClass("contains_branch");k.addClass("emptybranch")}});h.parent().append(j);var k=h.attr("id")+"_group";j.attr("id",k);h.attr("aria-owns",k);h.attr("role","treeitem")}return{render:function render(a,b){if(b.children&&b.children.length){f(a,b.children);var c=a.children("[role='treeitem']").first(),d=a.find("#"+c.attr("aria-owns"));c.attr("aria-expanded",!0);e.unhide(d)}else{if(a.parent().hasClass("contains_branch")){a.parent().removeClass("contains_branch");a.addClass("emptybranch")}}}}}); +function _typeof(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){_typeof=function(a){return typeof a}}else{_typeof=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return _typeof(a)}define ("block_navigation/ajax_response_renderer",["jquery","core/templates","core/notification","core/url","core/aria"],function(a,b,c,d,e){var g={ACTIVITY:40,RESOURCE:50};function f(h,i){var j=a("");j.attr("role","group");e.hide(j);a.each(i,function(e,h){if("object"!==_typeof(h)){return}var i=a("
  • "),k=a("

    "),l=h.id||h.key+"_tree_item",m=null,n=h.expandable||h.haschildren?!0:!1;i.attr("role","treeitem");k.addClass("tree_item");k.attr("id",l);k.attr("tabindex","-1");if(h.requiresajaxloading){i.attr("data-requires-ajax",!0);i.attr("data-node-id",h.id);i.attr("data-node-key",h.key);i.attr("data-node-type",h.type)}if(n){i.addClass("collapsed contains_branch");i.attr("aria-expanded",!1);k.addClass("branch")}var o=null;if(h.link){var p=a("");o=p;p.append(""+h.name+"");if(h.hidden){p.addClass("dimmed")}k.append(p)}else{var q=a("");o=q;q.append(""+h.name+"");if(h.hidden){q.addClass("dimmed")}k.append(q)}if(h.icon&&(!n||h.type===g.ACTIVITY||h.type===g.RESOURCE)){i.addClass("item_with_icon");k.addClass("hasicon");if(h.type===g.ACTIVITY||h.type===g.RESOURCE){m=a("");m.attr("alt",h.icon.alt);m.attr("title",h.icon.title);m.attr("src",d.imageUrl(h.icon.pix,h.icon.component));a.each(h.icon.classes,function(a,b){m.addClass(b)});o.prepend(m)}else{if("moodle"==h.icon.component){h.icon.component="core"}b.renderPix(h.icon.pix,h.icon.component,h.icon.title).then(function(a){o.prepend(a)}).catch(c.exception)}}i.append(k);j.append(i);if(h.children&&h.children.length){f(i,h.children)}else if(n&&!h.requiresajaxloading){i.removeClass("contains_branch");k.addClass("emptybranch")}});h.append(j);var k=h.attr("id")+"_group";j.attr("id",k);h.attr("aria-owns",k);h.attr("role","treeitem")}return{render:function render(a,b){if(b.children&&b.children.length){f(a,b.children);var c=a.children("[role='treeitem']").first(),d=a.find("#"+c.attr("aria-owns"));c.attr("aria-expanded",!0);e.unhide(d)}else{if(a.hasClass("contains_branch")){a.removeClass("contains_branch");a.addClass("emptybranch")}}}}}); //# sourceMappingURL=ajax_response_renderer.min.js.map diff --git a/blocks/navigation/amd/build/ajax_response_renderer.min.js.map b/blocks/navigation/amd/build/ajax_response_renderer.min.js.map index c729061b495..709c880d4a0 100644 --- a/blocks/navigation/amd/build/ajax_response_renderer.min.js.map +++ b/blocks/navigation/amd/build/ajax_response_renderer.min.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/ajax_response_renderer.js"],"names":["define","$","Templates","Notification","Url","Aria","NODETYPE","ACTIVITY","RESOURCE","buildDOM","rootElement","nodes","ul","attr","hide","each","index","node","li","p","id","key","icon","isBranch","expandable","haschildren","addClass","requiresajaxloading","type","eleToAddIcon","link","title","append","name","hidden","span","alt","imageUrl","pix","component","classes","className","prepend","renderPix","then","html","catch","exception","children","length","removeClass","parent","render","element","item","first","group","find","unhide","hasClass"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Parse the response from the navblock ajax page and render the correct DOM\n * structure for the tree from it.\n *\n * @module block_navigation/ajax_response_renderer\n * @package core\n * @copyright 2015 John Okely \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'jquery',\n 'core/templates',\n 'core/notification',\n 'core/url',\n 'core/aria',\n], function(\n $,\n Templates,\n Notification,\n Url,\n Aria\n) {\n\n // Mappings for the different types of nodes coming from the navigation.\n // Copied from lib/navigationlib.php navigation_node constants.\n var NODETYPE = {\n // @type int Activity (course module) = 40.\n ACTIVITY: 40,\n // @type int Resource (course module = 50.\n RESOURCE: 50,\n };\n\n /**\n * Build DOM.\n *\n * @method buildDOM\n * @param {Object} rootElement the root element of DOM.\n * @param {object} nodes jquery object representing the nodes to be build.\n */\n function buildDOM(rootElement, nodes) {\n var ul = $('
      ');\n ul.attr('role', 'group');\n Aria.hide(ul);\n\n $.each(nodes, function(index, node) {\n if (typeof node !== 'object') {\n return;\n }\n\n var li = $('
    • ');\n var p = $('

      ');\n var id = node.id || node.key + '_tree_item';\n var icon = null;\n var isBranch = (node.expandable || node.haschildren) ? true : false;\n\n p.addClass('tree_item');\n p.attr('id', id);\n p.attr('role', 'treeitem');\n // Negative tab index to allow it to receive focus.\n p.attr('tabindex', '-1');\n\n if (node.requiresajaxloading) {\n p.attr('data-requires-ajax', true);\n p.attr('data-node-id', node.id);\n p.attr('data-node-key', node.key);\n p.attr('data-node-type', node.type);\n }\n\n if (isBranch) {\n li.addClass('collapsed contains_branch');\n p.attr('aria-expanded', false);\n p.addClass('branch');\n }\n\n var eleToAddIcon = null;\n if (node.link) {\n var link = $('');\n\n eleToAddIcon = link;\n link.append('' + node.name + '');\n\n if (node.hidden) {\n link.addClass('dimmed');\n }\n\n p.append(link);\n } else {\n var span = $('');\n\n eleToAddIcon = span;\n span.append('' + node.name + '');\n\n if (node.hidden) {\n span.addClass('dimmed');\n }\n\n p.append(span);\n }\n\n if (node.icon && (!isBranch || node.type === NODETYPE.ACTIVITY || node.type === NODETYPE.RESOURCE)) {\n li.addClass('item_with_icon');\n p.addClass('hasicon');\n\n if (node.type === NODETYPE.ACTIVITY || node.type === NODETYPE.RESOURCE) {\n icon = $('');\n icon.attr('alt', node.icon.alt);\n icon.attr('title', node.icon.title);\n icon.attr('src', Url.imageUrl(node.icon.pix, node.icon.component));\n $.each(node.icon.classes, function(index, className) {\n icon.addClass(className);\n });\n eleToAddIcon.prepend(icon);\n } else {\n if (node.icon.component == 'moodle') {\n node.icon.component = 'core';\n }\n Templates.renderPix(node.icon.pix, node.icon.component, node.icon.title).then(function(html) {\n // Prepend.\n eleToAddIcon.prepend(html);\n return;\n }).catch(Notification.exception);\n }\n }\n\n li.append(p);\n ul.append(li);\n\n if (node.children && node.children.length) {\n buildDOM(p, node.children);\n } else if (isBranch && !node.requiresajaxloading) {\n li.removeClass('contains_branch');\n p.addClass('emptybranch');\n }\n });\n\n rootElement.parent().append(ul);\n var id = rootElement.attr('id') + '_group';\n ul.attr('id', id);\n rootElement.attr('aria-owns', id);\n rootElement.attr('role', 'treeitem');\n }\n\n return {\n render: function(element, nodes) {\n // The first element of the response is the existing node so we start with processing the children.\n if (nodes.children && nodes.children.length) {\n buildDOM(element, nodes.children);\n\n var item = element.children(\"[role='treeitem']\").first();\n var group = element.find('#' + item.attr('aria-owns'));\n\n item.attr('aria-expanded', true);\n Aria.unhide(group);\n } else {\n if (element.parent().hasClass('contains_branch')) {\n element.parent().removeClass('contains_branch');\n element.addClass('emptybranch');\n }\n }\n }\n };\n});\n"],"file":"ajax_response_renderer.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/ajax_response_renderer.js"],"names":["define","$","Templates","Notification","Url","Aria","NODETYPE","ACTIVITY","RESOURCE","buildDOM","rootElement","nodes","ul","attr","hide","each","index","node","li","p","id","key","icon","isBranch","expandable","haschildren","addClass","requiresajaxloading","type","eleToAddIcon","link","title","append","name","hidden","span","alt","imageUrl","pix","component","classes","className","prepend","renderPix","then","html","catch","exception","children","length","removeClass","render","element","item","first","group","find","unhide","hasClass"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Parse the response from the navblock ajax page and render the correct DOM\n * structure for the tree from it.\n *\n * @module block_navigation/ajax_response_renderer\n * @package core\n * @copyright 2015 John Okely \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine([\n 'jquery',\n 'core/templates',\n 'core/notification',\n 'core/url',\n 'core/aria',\n], function(\n $,\n Templates,\n Notification,\n Url,\n Aria\n) {\n\n // Mappings for the different types of nodes coming from the navigation.\n // Copied from lib/navigationlib.php navigation_node constants.\n var NODETYPE = {\n // @type int Activity (course module) = 40.\n ACTIVITY: 40,\n // @type int Resource (course module = 50.\n RESOURCE: 50,\n };\n\n /**\n * Build DOM.\n *\n * @method buildDOM\n * @param {Object} rootElement the root element of DOM.\n * @param {object} nodes jquery object representing the nodes to be build.\n */\n function buildDOM(rootElement, nodes) {\n var ul = $('
        ');\n ul.attr('role', 'group');\n Aria.hide(ul);\n\n $.each(nodes, function(index, node) {\n if (typeof node !== 'object') {\n return;\n }\n\n var li = $('
      • ');\n var p = $('

        ');\n var id = node.id || node.key + '_tree_item';\n var icon = null;\n var isBranch = (node.expandable || node.haschildren) ? true : false;\n\n li.attr('role', 'treeitem');\n p.addClass('tree_item');\n p.attr('id', id);\n // Negative tab index to allow it to receive focus.\n p.attr('tabindex', '-1');\n\n if (node.requiresajaxloading) {\n li.attr('data-requires-ajax', true);\n li.attr('data-node-id', node.id);\n li.attr('data-node-key', node.key);\n li.attr('data-node-type', node.type);\n }\n\n if (isBranch) {\n li.addClass('collapsed contains_branch');\n li.attr('aria-expanded', false);\n p.addClass('branch');\n }\n\n var eleToAddIcon = null;\n if (node.link) {\n var link = $('');\n\n eleToAddIcon = link;\n link.append('' + node.name + '');\n\n if (node.hidden) {\n link.addClass('dimmed');\n }\n\n p.append(link);\n } else {\n var span = $('');\n\n eleToAddIcon = span;\n span.append('' + node.name + '');\n\n if (node.hidden) {\n span.addClass('dimmed');\n }\n\n p.append(span);\n }\n\n if (node.icon && (!isBranch || node.type === NODETYPE.ACTIVITY || node.type === NODETYPE.RESOURCE)) {\n li.addClass('item_with_icon');\n p.addClass('hasicon');\n\n if (node.type === NODETYPE.ACTIVITY || node.type === NODETYPE.RESOURCE) {\n icon = $('');\n icon.attr('alt', node.icon.alt);\n icon.attr('title', node.icon.title);\n icon.attr('src', Url.imageUrl(node.icon.pix, node.icon.component));\n $.each(node.icon.classes, function(index, className) {\n icon.addClass(className);\n });\n eleToAddIcon.prepend(icon);\n } else {\n if (node.icon.component == 'moodle') {\n node.icon.component = 'core';\n }\n Templates.renderPix(node.icon.pix, node.icon.component, node.icon.title).then(function(html) {\n // Prepend.\n eleToAddIcon.prepend(html);\n return;\n }).catch(Notification.exception);\n }\n }\n\n li.append(p);\n ul.append(li);\n\n if (node.children && node.children.length) {\n buildDOM(li, node.children);\n } else if (isBranch && !node.requiresajaxloading) {\n li.removeClass('contains_branch');\n p.addClass('emptybranch');\n }\n });\n\n rootElement.append(ul);\n var id = rootElement.attr('id') + '_group';\n ul.attr('id', id);\n rootElement.attr('aria-owns', id);\n rootElement.attr('role', 'treeitem');\n }\n\n return {\n render: function(element, nodes) {\n // The first element of the response is the existing node so we start with processing the children.\n if (nodes.children && nodes.children.length) {\n buildDOM(element, nodes.children);\n\n var item = element.children(\"[role='treeitem']\").first();\n var group = element.find('#' + item.attr('aria-owns'));\n\n item.attr('aria-expanded', true);\n Aria.unhide(group);\n } else {\n if (element.hasClass('contains_branch')) {\n element.removeClass('contains_branch');\n element.addClass('emptybranch');\n }\n }\n }\n };\n});\n"],"file":"ajax_response_renderer.min.js"} \ No newline at end of file diff --git a/blocks/navigation/amd/src/ajax_response_renderer.js b/blocks/navigation/amd/src/ajax_response_renderer.js index e92ed463bee..8b6fc4672e9 100644 --- a/blocks/navigation/amd/src/ajax_response_renderer.js +++ b/blocks/navigation/amd/src/ajax_response_renderer.js @@ -68,22 +68,22 @@ define([ var icon = null; var isBranch = (node.expandable || node.haschildren) ? true : false; + li.attr('role', 'treeitem'); p.addClass('tree_item'); p.attr('id', id); - p.attr('role', 'treeitem'); // Negative tab index to allow it to receive focus. p.attr('tabindex', '-1'); if (node.requiresajaxloading) { - p.attr('data-requires-ajax', true); - p.attr('data-node-id', node.id); - p.attr('data-node-key', node.key); - p.attr('data-node-type', node.type); + li.attr('data-requires-ajax', true); + li.attr('data-node-id', node.id); + li.attr('data-node-key', node.key); + li.attr('data-node-type', node.type); } if (isBranch) { li.addClass('collapsed contains_branch'); - p.attr('aria-expanded', false); + li.attr('aria-expanded', false); p.addClass('branch'); } @@ -141,14 +141,14 @@ define([ ul.append(li); if (node.children && node.children.length) { - buildDOM(p, node.children); + buildDOM(li, node.children); } else if (isBranch && !node.requiresajaxloading) { li.removeClass('contains_branch'); p.addClass('emptybranch'); } }); - rootElement.parent().append(ul); + rootElement.append(ul); var id = rootElement.attr('id') + '_group'; ul.attr('id', id); rootElement.attr('aria-owns', id); @@ -167,8 +167,8 @@ define([ item.attr('aria-expanded', true); Aria.unhide(group); } else { - if (element.parent().hasClass('contains_branch')) { - element.parent().removeClass('contains_branch'); + if (element.hasClass('contains_branch')) { + element.removeClass('contains_branch'); element.addClass('emptybranch'); } } diff --git a/blocks/navigation/renderer.php b/blocks/navigation/renderer.php index c64e55d4d84..4583a12722a 100644 --- a/blocks/navigation/renderer.php +++ b/blocks/navigation/renderer.php @@ -73,6 +73,7 @@ class block_navigation_renderer extends plugin_renderer_base { $lis = array(); // Set the number to be static for unique id's. static $number = 0; + $htmlidprefix = html_writer::random_id(); foreach ($items as $item) { $number++; if (!$item->display && !$item->contains_active_node()) { @@ -90,8 +91,8 @@ class block_navigation_renderer extends plugin_renderer_base { $content = $item->get_content(); $title = $item->get_title(); $ulattr = ['id' => $id . '_group', 'role' => 'group']; - $liattr = ['class' => [$item->get_css_type(), 'depth_'.$depth]]; - $pattr = ['class' => ['tree_item'], 'role' => 'treeitem']; + $liattr = ['class' => [$item->get_css_type(), 'depth_'.$depth], 'role' => 'treeitem']; + $pattr = ['class' => ['tree_item']]; $pattr += !empty($item->id) ? ['id' => $item->id] : []; $isbranch = $isexpandable && ($item->children->count() > 0 || ($item->has_children() && (isloggedin() || $item->type <= navigation_node::TYPE_CATEGORY))); $hasicon = ((!$isbranch || $item->type == navigation_node::TYPE_ACTIVITY || $item->type == navigation_node::TYPE_RESOURCE) && $item->icon instanceof renderable); @@ -112,7 +113,7 @@ class block_navigation_renderer extends plugin_renderer_base { continue; } - $nodetextid = 'label_' . $depth . '_' . $number; + $nodetextid = $htmlidprefix . '_label_' . $depth . '_' . $number; $attributes = array('tabindex' => '-1', 'id' => $nodetextid); if ($title !== '') { $attributes['title'] = $title; @@ -135,11 +136,12 @@ class block_navigation_renderer extends plugin_renderer_base { } if ($isbranch) { + $ariaexpanded = $item->has_children() && (!$item->forceopen || $item->collapse); $pattr['class'][] = 'branch'; $liattr['class'][] = 'contains_branch'; - $pattr += ['aria-expanded' => ($item->has_children() && (!$item->forceopen || $item->collapse)) ? "false" : "true"]; + $liattr += ['aria-expanded' => $ariaexpanded ? "false" : "true"]; if ($item->requiresajaxloading) { - $pattr += [ + $liattr += [ 'data-requires-ajax' => 'true', 'data-loaded' => 'false', 'data-node-id' => $item->id, @@ -147,7 +149,7 @@ class block_navigation_renderer extends plugin_renderer_base { 'data-node-type' => $item->type ]; } else { - $pattr += ['aria-owns' => $id . '_group']; + $liattr += ['aria-owns' => $id . '_group']; } } @@ -161,8 +163,8 @@ class block_navigation_renderer extends plugin_renderer_base { $liattr['class'] = join(' ', $liattr['class']); $pattr['class'] = join(' ', $pattr['class']); - $pattr += $depth == 1 ? ['data-collapsible' => 'false'] : []; - if (isset($pattr['aria-expanded']) && $pattr['aria-expanded'] === 'false') { + $liattr += $depth == 1 ? ['data-collapsible' => 'false'] : []; + if (isset($liattr['aria-expanded']) && $liattr['aria-expanded'] === 'false') { $ulattr += ['aria-hidden' => 'true']; } diff --git a/blocks/navigation/styles.css b/blocks/navigation/styles.css index 2369c1af0d6..0411381acca 100644 --- a/blocks/navigation/styles.css +++ b/blocks/navigation/styles.css @@ -57,7 +57,7 @@ background-image: url('[[pix:t/collapsed_empty]]'); } -.block_navigation .block_tree [aria-expanded="false"].loading { +.block_navigation .block_tree [aria-expanded="false"] p.loading { background-image: url('[[pix:i/loading_small]]'); } diff --git a/blocks/settings/renderer.php b/blocks/settings/renderer.php index 3e928483a8d..1b73ad9b551 100644 --- a/blocks/settings/renderer.php +++ b/blocks/settings/renderer.php @@ -77,8 +77,8 @@ class block_settings_renderer extends plugin_renderer_base { $content = $this->output->render($item); $id = $item->id ? $item->id : html_writer::random_id(); $ulattr = ['id' => $id . '_group', 'role' => 'group']; - $liattr = ['class' => [$item->get_css_type(), 'depth_'.$depth], 'tabindex' => '-1']; - $pattr = ['class' => ['tree_item'], 'role' => 'treeitem']; + $liattr = ['class' => [$item->get_css_type(), 'depth_'.$depth], 'tabindex' => '-1', 'role' => 'treeitem']; + $pattr = ['class' => ['tree_item']]; $pattr += !empty($item->id) ? ['id' => $item->id] : []; $hasicon = (!$isbranch && $item->icon instanceof renderable); @@ -86,15 +86,15 @@ class block_settings_renderer extends plugin_renderer_base { $liattr['class'][] = 'contains_branch'; if (!$item->forceopen || (!$item->forceopen && $item->collapse) || ($item->children->count() == 0 && $item->nodetype == navigation_node::NODETYPE_BRANCH)) { - $pattr += ['aria-expanded' => 'false']; + $liattr += ['aria-expanded' => 'false']; } else { - $pattr += ['aria-expanded' => 'true']; + $liattr += ['aria-expanded' => 'true']; } if ($item->requiresajaxloading) { - $pattr['data-requires-ajax'] = 'true'; - $pattr['data-loaded'] = 'false'; + $liattr['data-requires-ajax'] = 'true'; + $liattr['data-loaded'] = 'false'; } else { - $pattr += ['aria-owns' => $id . '_group']; + $liattr += ['aria-owns' => $id . '_group']; } } else if ($hasicon) { $liattr['class'][] = 'item_with_icon'; @@ -106,7 +106,6 @@ class block_settings_renderer extends plugin_renderer_base { if (!empty($item->classes) && count($item->classes) > 0) { $pattr['class'] = array_merge($pattr['class'], $item->classes); } - $nodetextid = 'label_' . $depth . '_' . $number; // class attribute on the div item which only contains the item content $pattr['class'][] = 'tree_item'; @@ -119,7 +118,7 @@ class block_settings_renderer extends plugin_renderer_base { $liattr['class'] = join(' ', $liattr['class']); $pattr['class'] = join(' ', $pattr['class']); - if (isset($pattr['aria-expanded']) && $pattr['aria-expanded'] === 'false') { + if (isset($liattr['aria-expanded']) && $liattr['aria-expanded'] === 'false') { $ulattr += ['aria-hidden' => 'true']; } @@ -127,7 +126,6 @@ class block_settings_renderer extends plugin_renderer_base { if (!empty($item->preceedwithhr) && $item->preceedwithhr===true) { $content = html_writer::empty_tag('hr') . $content; } - $liattr['aria-labelledby'] = $nodetextid; $content = html_writer::tag('li', $content, $liattr); $lis[] = $content; } diff --git a/blocks/settings/styles.css b/blocks/settings/styles.css index 63c032dd13d..3c9fa588b0a 100644 --- a/blocks/settings/styles.css +++ b/blocks/settings/styles.css @@ -46,7 +46,7 @@ background-image: url('[[pix:t/collapsed_empty]]'); } -.block_settings .block_tree [aria-expanded="false"].loading { +.block_settings .block_tree [aria-expanded="false"] p.loading { background-image: url('[[pix:i/loading_small]]'); } /*rtl:raw: diff --git a/lib/amd/build/tree.min.js b/lib/amd/build/tree.min.js index 1d9c1c860cb..8b47188e688 100644 --- a/lib/amd/build/tree.min.js +++ b/lib/amd/build/tree.min.js @@ -1,2 +1,2 @@ -define ("core/tree",["jquery"],function(a){var b={ITEM:"[role=treeitem]",GROUP:"[role=treeitem]:has([role=group]), [role=treeitem][aria-owns], [role=treeitem][data-requires-ajax=true]",CLOSED_GROUP:"[role=treeitem]:has([role=group])[aria-expanded=false], [role=treeitem][aria-owns][aria-expanded=false], [role=treeitem][data-requires-ajax=true][aria-expanded=false]",FIRST_ITEM:"[role=treeitem]:first",VISIBLE_ITEM:"[role=treeitem]:visible",UNLOADED_AJAX_ITEM:"[role=treeitem][data-requires-ajax=true][data-loaded=false][aria-expanded=true]"},c=function(c,d){this.treeRoot=a(c);this.treeRoot.data("activeItem",null);this.selectCallback=d;this.keys={tab:9,enter:13,space:32,pageup:33,pagedown:34,end:35,home:36,left:37,up:38,right:39,down:40,asterisk:106};this.initialiseNodes(this.treeRoot);this.setActiveItem(this.treeRoot.find(b.FIRST_ITEM));this.refreshVisibleItemsCache();this.bindEventHandlers()};c.prototype.registerEnterCallback=function(a){this.enterCallback=a};c.prototype.refreshVisibleItemsCache=function(){this.treeRoot.data("visibleItems",this.treeRoot.find(b.VISIBLE_ITEM))};c.prototype.getVisibleItems=function(){return this.treeRoot.data("visibleItems")};c.prototype.setActiveItem=function(a){var b=this.treeRoot.data("activeItem");if(a===b){return}if(b){b.attr("tabindex","-1");b.attr("aria-selected","false")}a.attr("tabindex","0");a.attr("aria-selected","true");this.treeRoot.data("activeItem",a);if("function"==typeof this.selectCallback){this.selectCallback(a)}};c.prototype.isGroupItem=function(a){return a.is(b.GROUP)};c.prototype.getGroupFromItem=function(a){var b=this.treeRoot.find("#"+a.attr("aria-owns")),c=a.children("[role=group]");if(b.length>c.length){return b}else{return c}};c.prototype.isGroupCollapsed=function(a){return"false"===a.attr("aria-expanded")};c.prototype.isGroupCollapsible=function(a){return"false"!==a.attr("data-collapsible")};c.prototype.initialiseNodes=function(c){this.removeAllFromTabOrder(c);this.setAriaSelectedFalseOnItems(c);var d=this;c.find(b.UNLOADED_AJAX_ITEM).each(function(){var b=a(this);d.collapseGroup(b);d.expandGroup(b)})};c.prototype.removeAllFromTabOrder=function(b){b.find("*").attr("tabindex","-1");this.getGroupFromItem(a(b)).find("*").attr("tabindex","-1")};c.prototype.setAriaSelectedFalseOnItems=function(a){a.find(b.ITEM).attr("aria-selected","false")};c.prototype.expandAllGroups=function(){var c=this;this.treeRoot.find(b.CLOSED_GROUP).each(function(){var b=a(this);c.expandGroup(a(this)).done(function(){c.expandAllChildGroups(b)})})};c.prototype.expandAllChildGroups=function(c){var d=this;this.getGroupFromItem(c).find(b.CLOSED_GROUP).each(function(){var b=a(this);d.expandGroup(a(this)).done(function(){d.expandAllChildGroups(b)})})};c.prototype.expandGroup=function(b){var c=a.Deferred();if("false"!==b.attr("data-expandable")&&this.isGroupCollapsed(b)){if("true"===b.attr("data-requires-ajax")&&"true"!==b.attr("data-loaded")){b.attr("data-loaded",!1);var d=b.closest("[data-ajax-loader]").attr("data-ajax-loader"),e=this;b.addClass("loading");require([d],function(a){a.load(b).done(function(){b.attr("data-loaded",!0);e.initialiseNodes(b);e.finishExpandingGroup(b);b.removeClass("loading");c.resolve()})})}else{this.finishExpandingGroup(b);c.resolve()}}else{c.resolve()}return c};c.prototype.finishExpandingGroup=function(a){var b=this.getGroupFromItem(a);b.removeAttr("aria-hidden");a.attr("aria-expanded","true");this.refreshVisibleItemsCache()};c.prototype.collapseGroup=function(a){if(!this.isGroupCollapsible(a)||this.isGroupCollapsed(a)){return}var b=this.getGroupFromItem(a);b.attr("aria-hidden","true");a.attr("aria-expanded","false");this.refreshVisibleItemsCache()};c.prototype.toggleGroup=function(a){if("true"===a.attr("aria-expanded")){this.collapseGroup(a)}else{this.expandGroup(a)}};c.prototype.handleKeyDown=function(c){var d=a(c.target),e=this.getVisibleItems().index(d);if(c.altKey||c.ctrlKey||c.metaKey||c.shiftKey&&c.keyCode!=this.keys.tab){return}switch(c.keyCode){case this.keys.home:{this.getVisibleItems().first().focus();c.preventDefault();return}case this.keys.end:{this.getVisibleItems().last().focus();c.preventDefault();return}case this.keys.enter:{var f=d.children("a").length?d.children("a"):d.children().not(b.GROUP).find("a");if(f.length){if(f.first().data("overrides-tree-activation-key-handler")){f.first().triggerHandler(c)}else if("function"==typeof this.enterCallback){this.enterCallback(d)}else{window.location.href=f.first().attr("href")}}else if(this.isGroupItem(d)){this.toggleGroup(d,!0)}c.preventDefault();return}case this.keys.space:{if(this.isGroupItem(d)){this.toggleGroup(d,!0)}else if(d.children("a").length){var g=d.children("a").first();if(g.data("overrides-tree-activation-key-handler")){g.triggerHandler(c)}}c.preventDefault();return}case this.keys.left:{var h=function(b){b.getVisibleItems().filter(function(){return b.getGroupFromItem(a(this)).has(d).length}).focus()};if(this.isGroupItem(d)){if(this.isGroupCollapsed(d)){h(this)}else{this.collapseGroup(d)}}else{h(this)}c.preventDefault();return}case this.keys.right:{if(this.isGroupItem(d)){if(this.isGroupCollapsed(d)){this.expandGroup(d)}else{this.getGroupFromItem(d).find(b.ITEM).first().focus()}}c.preventDefault();return}case this.keys.up:{if(0c.length){return b}else{return c}};c.prototype.isGroupCollapsed=function(a){return"false"===a.attr("aria-expanded")};c.prototype.isGroupCollapsible=function(a){return"false"!==a.attr("data-collapsible")};c.prototype.initialiseNodes=function(c){this.removeAllFromTabOrder(c);this.setAriaSelectedFalseOnItems(c);var d=this;c.find(b.UNLOADED_AJAX_ITEM).each(function(){var b=a(this);d.collapseGroup(b);d.expandGroup(b)})};c.prototype.removeAllFromTabOrder=function(b){b.find("*").attr("tabindex","-1");this.getGroupFromItem(a(b)).find("*").attr("tabindex","-1")};c.prototype.setAriaSelectedFalseOnItems=function(a){a.find(b.ITEM).attr("aria-selected","false")};c.prototype.expandAllGroups=function(){var c=this;this.treeRoot.find(b.CLOSED_GROUP).each(function(){var b=a(this);c.expandGroup(a(this)).done(function(){c.expandAllChildGroups(b)})})};c.prototype.expandAllChildGroups=function(c){var d=this;this.getGroupFromItem(c).find(b.CLOSED_GROUP).each(function(){var b=a(this);d.expandGroup(a(this)).done(function(){d.expandAllChildGroups(b)})})};c.prototype.expandGroup=function(b){var c=a.Deferred();if("false"!==b.attr("data-expandable")&&this.isGroupCollapsed(b)){if("true"===b.attr("data-requires-ajax")&&"true"!==b.attr("data-loaded")){b.attr("data-loaded",!1);var d=b.closest("[data-ajax-loader]").attr("data-ajax-loader"),e=this,f=b.find("p");f.addClass("loading");require([d],function(a){a.load(b).done(function(){b.attr("data-loaded",!0);e.initialiseNodes(b);e.finishExpandingGroup(b);f.removeClass("loading");c.resolve()})})}else{this.finishExpandingGroup(b);c.resolve()}}else{c.resolve()}return c};c.prototype.finishExpandingGroup=function(a){var b=this.getGroupFromItem(a);b.removeAttr("aria-hidden");a.attr("aria-expanded","true");this.refreshVisibleItemsCache()};c.prototype.collapseGroup=function(a){if(!this.isGroupCollapsible(a)||this.isGroupCollapsed(a)){return}var b=this.getGroupFromItem(a);b.attr("aria-hidden","true");a.attr("aria-expanded","false");this.refreshVisibleItemsCache()};c.prototype.toggleGroup=function(a){if("true"===a.attr("aria-expanded")){this.collapseGroup(a)}else{this.expandGroup(a)}};c.prototype.handleKeyDown=function(c){var d=a(c.target),e=this.getVisibleItems().index(d);if(c.altKey||c.ctrlKey||c.metaKey||c.shiftKey&&c.keyCode!=this.keys.tab){return}switch(c.keyCode){case this.keys.home:{this.getVisibleItems().first().focus();c.preventDefault();return}case this.keys.end:{this.getVisibleItems().last().focus();c.preventDefault();return}case this.keys.enter:{var f=d.children("a").length?d.children("a"):d.children().not(b.GROUP).find("a");if(f.length){if(f.first().data("overrides-tree-activation-key-handler")){f.first().triggerHandler(c)}else if("function"==typeof this.enterCallback){this.enterCallback(d)}else{window.location.href=f.first().attr("href")}}else if(this.isGroupItem(d)){this.toggleGroup(d,!0)}c.preventDefault();return}case this.keys.space:{if(this.isGroupItem(d)){this.toggleGroup(d,!0)}else if(d.children("a").length){var g=d.children("a").first();if(g.data("overrides-tree-activation-key-handler")){g.triggerHandler(c)}}c.preventDefault();return}case this.keys.left:{var h=function(b){b.getVisibleItems().filter(function(){return b.getGroupFromItem(a(this)).has(d).length}).focus()};if(this.isGroupItem(d)){if(this.isGroupCollapsed(d)){h(this)}else{this.collapseGroup(d)}}else{h(this)}c.preventDefault();return}case this.keys.right:{if(this.isGroupItem(d)){if(this.isGroupCollapsed(d)){this.expandGroup(d)}else{this.getGroupFromItem(d).find(b.ITEM).first().focus()}}c.preventDefault();return}case this.keys.up:{if(0.\n\n/**\n * Implement an accessible aria tree widget, from a nested unordered list.\n * Based on http://oaa-accessibility.org/example/41/.\n *\n * @module tool_lp/tree\n * @package core\n * @copyright 2015 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery'], function($) {\n // Private variables and functions.\n var SELECTORS = {\n ITEM: '[role=treeitem]',\n GROUP: '[role=treeitem]:has([role=group]), [role=treeitem][aria-owns], [role=treeitem][data-requires-ajax=true]',\n CLOSED_GROUP: '[role=treeitem]:has([role=group])[aria-expanded=false], [role=treeitem][aria-owns][aria-expanded=false], ' +\n '[role=treeitem][data-requires-ajax=true][aria-expanded=false]',\n FIRST_ITEM: '[role=treeitem]:first',\n VISIBLE_ITEM: '[role=treeitem]:visible',\n UNLOADED_AJAX_ITEM: '[role=treeitem][data-requires-ajax=true][data-loaded=false][aria-expanded=true]'\n };\n\n /**\n * Constructor.\n *\n * @param {String} selector\n * @param {function} selectCallback Called when the active node is changed.\n */\n var Tree = function(selector, selectCallback) {\n this.treeRoot = $(selector);\n\n this.treeRoot.data('activeItem', null);\n this.selectCallback = selectCallback;\n this.keys = {\n tab: 9,\n enter: 13,\n space: 32,\n pageup: 33,\n pagedown: 34,\n end: 35,\n home: 36,\n left: 37,\n up: 38,\n right: 39,\n down: 40,\n asterisk: 106\n };\n\n // Apply the standard default initialisation for all nodes, starting with the tree root.\n this.initialiseNodes(this.treeRoot);\n // Make the first item the active item for the tree so that it is added to the tab order.\n this.setActiveItem(this.treeRoot.find(SELECTORS.FIRST_ITEM));\n // Create the cache of the visible items.\n this.refreshVisibleItemsCache();\n // Create the event handlers for the tree.\n this.bindEventHandlers();\n };\n\n Tree.prototype.registerEnterCallback = function(callback) {\n this.enterCallback = callback;\n };\n\n /**\n * Find all visible tree items and save a cache of them on the tree object.\n *\n * @method refreshVisibleItemsCache\n */\n Tree.prototype.refreshVisibleItemsCache = function() {\n this.treeRoot.data('visibleItems', this.treeRoot.find(SELECTORS.VISIBLE_ITEM));\n };\n\n /**\n * Get all visible tree items.\n *\n * @method getVisibleItems\n * @return {Object} visible items\n */\n Tree.prototype.getVisibleItems = function() {\n return this.treeRoot.data('visibleItems');\n };\n\n /**\n * Mark the given item as active within the tree and fire the callback for when the active item is set.\n *\n * @method setActiveItem\n * @param {object} item jquery object representing an item on the tree.\n */\n Tree.prototype.setActiveItem = function(item) {\n var currentActive = this.treeRoot.data('activeItem');\n if (item === currentActive) {\n return;\n }\n\n // Remove previous active from tab order.\n if (currentActive) {\n currentActive.attr('tabindex', '-1');\n currentActive.attr('aria-selected', 'false');\n }\n item.attr('tabindex', '0');\n item.attr('aria-selected', 'true');\n\n // Set the new active item.\n this.treeRoot.data('activeItem', item);\n\n if (typeof this.selectCallback === 'function') {\n this.selectCallback(item);\n }\n };\n\n /**\n * Determines if the given item is a group item (contains child tree items) in the tree.\n *\n * @method isGroupItem\n * @param {object} item jquery object representing an item on the tree.\n * @returns {bool}\n */\n Tree.prototype.isGroupItem = function(item) {\n return item.is(SELECTORS.GROUP);\n };\n\n /**\n * Determines if the given item is a group item (contains child tree items) in the tree.\n *\n * @method isGroupItem\n * @param {object} item jquery object representing an item on the tree.\n * @returns {bool}\n */\n Tree.prototype.getGroupFromItem = function(item) {\n var ariaowns = this.treeRoot.find('#' + item.attr('aria-owns'));\n var plain = item.children('[role=group]');\n if (ariaowns.length > plain.length) {\n return ariaowns;\n } else {\n return plain;\n }\n };\n\n /**\n * Determines if the given group item (contains child tree items) is collapsed.\n *\n * @method isGroupCollapsed\n * @param {object} item jquery object representing a group item on the tree.\n * @returns {bool}\n */\n Tree.prototype.isGroupCollapsed = function(item) {\n return item.attr('aria-expanded') === 'false';\n };\n\n /**\n * Determines if the given group item (contains child tree items) can be collapsed.\n *\n * @method isGroupCollapsible\n * @param {object} item jquery object representing a group item on the tree.\n * @returns {bool}\n */\n Tree.prototype.isGroupCollapsible = function(item) {\n return item.attr('data-collapsible') !== 'false';\n };\n\n /**\n * Performs the tree initialisation for all child items from the given node,\n * such as removing everything from the tab order and setting aria selected\n * on items.\n *\n * @method initialiseNodes\n * @param {object} node jquery object representing a node.\n */\n Tree.prototype.initialiseNodes = function(node) {\n this.removeAllFromTabOrder(node);\n this.setAriaSelectedFalseOnItems(node);\n\n // Get all ajax nodes that have been rendered as expanded but haven't loaded the child items yet.\n var thisTree = this;\n node.find(SELECTORS.UNLOADED_AJAX_ITEM).each(function() {\n var unloadedNode = $(this);\n // Collapse and then expand to trigger the ajax loading.\n thisTree.collapseGroup(unloadedNode);\n thisTree.expandGroup(unloadedNode);\n });\n };\n\n /**\n * Removes all child DOM elements of the given node from the tab order.\n *\n * @method removeAllFromTabOrder\n * @param {object} node jquery object representing a node.\n */\n Tree.prototype.removeAllFromTabOrder = function(node) {\n node.find('*').attr('tabindex', '-1');\n this.getGroupFromItem($(node)).find('*').attr('tabindex', '-1');\n };\n\n /**\n * Find all child tree items from the given node and set the aria selected attribute to false.\n *\n * @method setAriaSelectedFalseOnItems\n * @param {object} node jquery object representing a node.\n */\n Tree.prototype.setAriaSelectedFalseOnItems = function(node) {\n node.find(SELECTORS.ITEM).attr('aria-selected', 'false');\n };\n\n /**\n * Expand all group nodes within the tree.\n *\n * @method expandAllGroups\n */\n Tree.prototype.expandAllGroups = function() {\n var thisTree = this;\n\n this.treeRoot.find(SELECTORS.CLOSED_GROUP).each(function() {\n var groupNode = $(this);\n\n thisTree.expandGroup($(this)).done(function() {\n thisTree.expandAllChildGroups(groupNode);\n });\n });\n };\n\n /**\n * Find all child group nodes from the given node and expand them.\n *\n * @method expandAllChildGroups\n * @param {Object} item is the jquery id of the group.\n */\n Tree.prototype.expandAllChildGroups = function(item) {\n var thisTree = this;\n\n this.getGroupFromItem(item).find(SELECTORS.CLOSED_GROUP).each(function() {\n var groupNode = $(this);\n\n thisTree.expandGroup($(this)).done(function() {\n thisTree.expandAllChildGroups(groupNode);\n });\n });\n };\n\n /**\n * Expand a collapsed group.\n *\n * Handles expanding nodes that are ajax loaded (marked with a data-requires-ajax attribute).\n *\n * @method expandGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n * @return {Object} a promise that is resolved when the group has been expanded.\n */\n Tree.prototype.expandGroup = function(item) {\n var promise = $.Deferred();\n // Ignore nodes that are explicitly maked as not expandable or are already expanded.\n if (item.attr('data-expandable') !== 'false' && this.isGroupCollapsed(item)) {\n // If this node requires ajax load and we haven't already loaded it.\n if (item.attr('data-requires-ajax') === 'true' && item.attr('data-loaded') !== 'true') {\n item.attr('data-loaded', false);\n // Get the closes ajax loading module specificed in the tree.\n var moduleName = item.closest('[data-ajax-loader]').attr('data-ajax-loader');\n var thisTree = this;\n // Flag this node as loading.\n item.addClass('loading');\n // Require the ajax module (must be AMD) and try to load the items.\n require([moduleName], function(loader) {\n // All ajax module must implement a \"load\" method.\n loader.load(item).done(function() {\n item.attr('data-loaded', true);\n\n // Set defaults on the newly constructed part of the tree.\n thisTree.initialiseNodes(item);\n thisTree.finishExpandingGroup(item);\n // Make sure no child elements of the item we just loaded are tabbable.\n item.removeClass('loading');\n promise.resolve();\n });\n });\n } else {\n this.finishExpandingGroup(item);\n promise.resolve();\n }\n } else {\n promise.resolve();\n }\n return promise;\n };\n\n /**\n * Perform the necessary DOM changes to display a group item.\n *\n * @method finishExpandingGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n */\n Tree.prototype.finishExpandingGroup = function(item) {\n // Expand the group.\n var group = this.getGroupFromItem(item);\n group.removeAttr('aria-hidden');\n item.attr('aria-expanded', 'true');\n\n // Update the list of visible items.\n this.refreshVisibleItemsCache();\n };\n\n /**\n * Collapse an expanded group.\n *\n * @method collapseGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n */\n Tree.prototype.collapseGroup = function(item) {\n // If the item is not collapsible or already collapsed then do nothing.\n if (!this.isGroupCollapsible(item) || this.isGroupCollapsed(item)) {\n return;\n }\n\n // Collapse the group.\n var group = this.getGroupFromItem(item);\n group.attr('aria-hidden', 'true');\n item.attr('aria-expanded', 'false');\n\n // Update the list of visible items.\n this.refreshVisibleItemsCache();\n };\n\n /**\n * Expand or collapse a group.\n *\n * @method toggleGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n */\n Tree.prototype.toggleGroup = function(item) {\n if (item.attr('aria-expanded') === 'true') {\n this.collapseGroup(item);\n } else {\n this.expandGroup(item);\n }\n };\n\n /**\n * Handle a key down event - ie navigate the tree.\n *\n * @method handleKeyDown\n * @param {Event} e The event.\n */\n // This function should be simplified. In the meantime..\n // eslint-disable-next-line complexity\n Tree.prototype.handleKeyDown = function(e) {\n var item = $(e.target);\n var currentIndex = this.getVisibleItems().index(item);\n\n if ((e.altKey || e.ctrlKey || e.metaKey) || (e.shiftKey && e.keyCode != this.keys.tab)) {\n // Do nothing.\n return;\n }\n\n switch (e.keyCode) {\n case this.keys.home: {\n // Jump to first item in tree.\n this.getVisibleItems().first().focus();\n\n e.preventDefault();\n return;\n }\n case this.keys.end: {\n // Jump to last visible item.\n this.getVisibleItems().last().focus();\n\n e.preventDefault();\n return;\n }\n case this.keys.enter: {\n var links = item.children('a').length ? item.children('a') : item.children().not(SELECTORS.GROUP).find('a');\n if (links.length) {\n if (links.first().data('overrides-tree-activation-key-handler')) {\n // If the link overrides handling of activation keys, let it do so.\n links.first().triggerHandler(e);\n } else if (typeof this.enterCallback === 'function') {\n // Use callback if there is one.\n this.enterCallback(item);\n } else {\n window.location.href = links.first().attr('href');\n }\n } else if (this.isGroupItem(item)) {\n this.toggleGroup(item, true);\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.space: {\n if (this.isGroupItem(item)) {\n this.toggleGroup(item, true);\n } else if (item.children('a').length) {\n var firstLink = item.children('a').first();\n\n if (firstLink.data('overrides-tree-activation-key-handler')) {\n firstLink.triggerHandler(e);\n }\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.left: {\n var focusParent = function(tree) {\n // Get the immediate visible parent group item that contains this element.\n tree.getVisibleItems().filter(function() {\n return tree.getGroupFromItem($(this)).has(item).length;\n }).focus();\n };\n\n // If this is a goup item then collapse it and focus the parent group\n // in accordance with the aria spec.\n if (this.isGroupItem(item)) {\n if (this.isGroupCollapsed(item)) {\n focusParent(this);\n } else {\n this.collapseGroup(item);\n }\n } else {\n focusParent(this);\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.right: {\n // If this is a group item then expand it and focus the first child item\n // in accordance with the aria spec.\n if (this.isGroupItem(item)) {\n if (this.isGroupCollapsed(item)) {\n this.expandGroup(item);\n } else {\n // Move to the first item in the child group.\n this.getGroupFromItem(item).find(SELECTORS.ITEM).first().focus();\n }\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.up: {\n\n if (currentIndex > 0) {\n var prev = this.getVisibleItems().eq(currentIndex - 1);\n\n prev.focus();\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.down: {\n\n if (currentIndex < this.getVisibleItems().length - 1) {\n var next = this.getVisibleItems().eq(currentIndex + 1);\n\n next.focus();\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.asterisk: {\n // Expand all groups.\n this.expandAllGroups();\n e.preventDefault();\n return;\n }\n }\n };\n\n /**\n * Handle a click (select).\n *\n * @method handleClick\n * @param {Event} e The event.\n */\n Tree.prototype.handleClick = function(e) {\n if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {\n // Do nothing.\n return;\n }\n\n // Get the closest tree item from the event target.\n var item = $(e.target).closest('[role=\"treeitem\"]');\n if (!item.is(e.currentTarget)) {\n return;\n }\n\n // Update the active item.\n item.focus();\n\n // If the item is a group node.\n if (this.isGroupItem(item)) {\n this.toggleGroup(item);\n }\n };\n\n /**\n * Handle a focus event.\n *\n * @method handleFocus\n * @param {Event} e The event.\n */\n Tree.prototype.handleFocus = function(e) {\n this.setActiveItem($(e.target));\n };\n\n /**\n * Bind the event listeners we require.\n *\n * @method bindEventHandlers\n */\n Tree.prototype.bindEventHandlers = function() {\n // Bind event handlers to the tree items. Use event delegates to allow\n // for dynamically loaded parts of the tree.\n this.treeRoot.on({\n click: this.handleClick.bind(this),\n keydown: this.handleKeyDown.bind(this),\n focus: this.handleFocus.bind(this),\n }, SELECTORS.ITEM);\n };\n\n return /** @alias module:tool_lp/tree */ Tree;\n});\n"],"file":"tree.min.js"} \ No newline at end of file +{"version":3,"sources":["../src/tree.js"],"names":["define","$","SELECTORS","ITEM","GROUP","CLOSED_GROUP","FIRST_ITEM","VISIBLE_ITEM","UNLOADED_AJAX_ITEM","Tree","selector","selectCallback","treeRoot","data","keys","tab","enter","space","pageup","pagedown","end","home","left","up","right","down","asterisk","initialiseNodes","setActiveItem","find","refreshVisibleItemsCache","bindEventHandlers","prototype","registerEnterCallback","callback","enterCallback","getVisibleItems","item","currentActive","attr","isGroupItem","is","getGroupFromItem","ariaowns","plain","children","length","isGroupCollapsed","isGroupCollapsible","node","removeAllFromTabOrder","setAriaSelectedFalseOnItems","thisTree","each","unloadedNode","collapseGroup","expandGroup","expandAllGroups","groupNode","done","expandAllChildGroups","promise","Deferred","moduleName","closest","p","addClass","require","loader","load","finishExpandingGroup","removeClass","resolve","group","removeAttr","toggleGroup","handleKeyDown","e","target","currentIndex","index","altKey","ctrlKey","metaKey","shiftKey","keyCode","first","focus","preventDefault","last","links","not","triggerHandler","window","location","href","firstLink","focusParent","tree","filter","has","prev","eq","next","handleClick","currentTarget","handleFocus","on","click","bind","keydown"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Implement an accessible aria tree widget, from a nested unordered list.\n * Based on http://oaa-accessibility.org/example/41/.\n *\n * @module tool_lp/tree\n * @package core\n * @copyright 2015 Damyon Wiese \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\ndefine(['jquery'], function($) {\n // Private variables and functions.\n var SELECTORS = {\n ITEM: '[role=treeitem]',\n GROUP: '[role=treeitem]:has([role=group]), [role=treeitem][aria-owns], [role=treeitem][data-requires-ajax=true]',\n CLOSED_GROUP: '[role=treeitem]:has([role=group])[aria-expanded=false], [role=treeitem][aria-owns][aria-expanded=false], ' +\n '[role=treeitem][data-requires-ajax=true][aria-expanded=false]',\n FIRST_ITEM: '[role=treeitem]:first',\n VISIBLE_ITEM: '[role=treeitem]:visible',\n UNLOADED_AJAX_ITEM: '[role=treeitem][data-requires-ajax=true][data-loaded=false][aria-expanded=true]'\n };\n\n /**\n * Constructor.\n *\n * @param {String} selector\n * @param {function} selectCallback Called when the active node is changed.\n */\n var Tree = function(selector, selectCallback) {\n this.treeRoot = $(selector);\n\n this.treeRoot.data('activeItem', null);\n this.selectCallback = selectCallback;\n this.keys = {\n tab: 9,\n enter: 13,\n space: 32,\n pageup: 33,\n pagedown: 34,\n end: 35,\n home: 36,\n left: 37,\n up: 38,\n right: 39,\n down: 40,\n asterisk: 106\n };\n\n // Apply the standard default initialisation for all nodes, starting with the tree root.\n this.initialiseNodes(this.treeRoot);\n // Make the first item the active item for the tree so that it is added to the tab order.\n this.setActiveItem(this.treeRoot.find(SELECTORS.FIRST_ITEM));\n // Create the cache of the visible items.\n this.refreshVisibleItemsCache();\n // Create the event handlers for the tree.\n this.bindEventHandlers();\n };\n\n Tree.prototype.registerEnterCallback = function(callback) {\n this.enterCallback = callback;\n };\n\n /**\n * Find all visible tree items and save a cache of them on the tree object.\n *\n * @method refreshVisibleItemsCache\n */\n Tree.prototype.refreshVisibleItemsCache = function() {\n this.treeRoot.data('visibleItems', this.treeRoot.find(SELECTORS.VISIBLE_ITEM));\n };\n\n /**\n * Get all visible tree items.\n *\n * @method getVisibleItems\n * @return {Object} visible items\n */\n Tree.prototype.getVisibleItems = function() {\n return this.treeRoot.data('visibleItems');\n };\n\n /**\n * Mark the given item as active within the tree and fire the callback for when the active item is set.\n *\n * @method setActiveItem\n * @param {object} item jquery object representing an item on the tree.\n */\n Tree.prototype.setActiveItem = function(item) {\n var currentActive = this.treeRoot.data('activeItem');\n if (item === currentActive) {\n return;\n }\n\n // Remove previous active from tab order.\n if (currentActive) {\n currentActive.attr('tabindex', '-1');\n currentActive.attr('aria-selected', 'false');\n }\n item.attr('tabindex', '0');\n item.attr('aria-selected', 'true');\n\n // Set the new active item.\n this.treeRoot.data('activeItem', item);\n\n if (typeof this.selectCallback === 'function') {\n this.selectCallback(item);\n }\n };\n\n /**\n * Determines if the given item is a group item (contains child tree items) in the tree.\n *\n * @method isGroupItem\n * @param {object} item jquery object representing an item on the tree.\n * @returns {bool}\n */\n Tree.prototype.isGroupItem = function(item) {\n return item.is(SELECTORS.GROUP);\n };\n\n /**\n * Determines if the given item is a group item (contains child tree items) in the tree.\n *\n * @method isGroupItem\n * @param {object} item jquery object representing an item on the tree.\n * @returns {bool}\n */\n Tree.prototype.getGroupFromItem = function(item) {\n var ariaowns = this.treeRoot.find('#' + item.attr('aria-owns'));\n var plain = item.children('[role=group]');\n if (ariaowns.length > plain.length) {\n return ariaowns;\n } else {\n return plain;\n }\n };\n\n /**\n * Determines if the given group item (contains child tree items) is collapsed.\n *\n * @method isGroupCollapsed\n * @param {object} item jquery object representing a group item on the tree.\n * @returns {bool}\n */\n Tree.prototype.isGroupCollapsed = function(item) {\n return item.attr('aria-expanded') === 'false';\n };\n\n /**\n * Determines if the given group item (contains child tree items) can be collapsed.\n *\n * @method isGroupCollapsible\n * @param {object} item jquery object representing a group item on the tree.\n * @returns {bool}\n */\n Tree.prototype.isGroupCollapsible = function(item) {\n return item.attr('data-collapsible') !== 'false';\n };\n\n /**\n * Performs the tree initialisation for all child items from the given node,\n * such as removing everything from the tab order and setting aria selected\n * on items.\n *\n * @method initialiseNodes\n * @param {object} node jquery object representing a node.\n */\n Tree.prototype.initialiseNodes = function(node) {\n this.removeAllFromTabOrder(node);\n this.setAriaSelectedFalseOnItems(node);\n\n // Get all ajax nodes that have been rendered as expanded but haven't loaded the child items yet.\n var thisTree = this;\n node.find(SELECTORS.UNLOADED_AJAX_ITEM).each(function() {\n var unloadedNode = $(this);\n // Collapse and then expand to trigger the ajax loading.\n thisTree.collapseGroup(unloadedNode);\n thisTree.expandGroup(unloadedNode);\n });\n };\n\n /**\n * Removes all child DOM elements of the given node from the tab order.\n *\n * @method removeAllFromTabOrder\n * @param {object} node jquery object representing a node.\n */\n Tree.prototype.removeAllFromTabOrder = function(node) {\n node.find('*').attr('tabindex', '-1');\n this.getGroupFromItem($(node)).find('*').attr('tabindex', '-1');\n };\n\n /**\n * Find all child tree items from the given node and set the aria selected attribute to false.\n *\n * @method setAriaSelectedFalseOnItems\n * @param {object} node jquery object representing a node.\n */\n Tree.prototype.setAriaSelectedFalseOnItems = function(node) {\n node.find(SELECTORS.ITEM).attr('aria-selected', 'false');\n };\n\n /**\n * Expand all group nodes within the tree.\n *\n * @method expandAllGroups\n */\n Tree.prototype.expandAllGroups = function() {\n var thisTree = this;\n\n this.treeRoot.find(SELECTORS.CLOSED_GROUP).each(function() {\n var groupNode = $(this);\n\n thisTree.expandGroup($(this)).done(function() {\n thisTree.expandAllChildGroups(groupNode);\n });\n });\n };\n\n /**\n * Find all child group nodes from the given node and expand them.\n *\n * @method expandAllChildGroups\n * @param {Object} item is the jquery id of the group.\n */\n Tree.prototype.expandAllChildGroups = function(item) {\n var thisTree = this;\n\n this.getGroupFromItem(item).find(SELECTORS.CLOSED_GROUP).each(function() {\n var groupNode = $(this);\n\n thisTree.expandGroup($(this)).done(function() {\n thisTree.expandAllChildGroups(groupNode);\n });\n });\n };\n\n /**\n * Expand a collapsed group.\n *\n * Handles expanding nodes that are ajax loaded (marked with a data-requires-ajax attribute).\n *\n * @method expandGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n * @return {Object} a promise that is resolved when the group has been expanded.\n */\n Tree.prototype.expandGroup = function(item) {\n var promise = $.Deferred();\n // Ignore nodes that are explicitly maked as not expandable or are already expanded.\n if (item.attr('data-expandable') !== 'false' && this.isGroupCollapsed(item)) {\n // If this node requires ajax load and we haven't already loaded it.\n if (item.attr('data-requires-ajax') === 'true' && item.attr('data-loaded') !== 'true') {\n item.attr('data-loaded', false);\n // Get the closes ajax loading module specificed in the tree.\n var moduleName = item.closest('[data-ajax-loader]').attr('data-ajax-loader');\n var thisTree = this;\n // Flag this node as loading.\n const p = item.find('p');\n p.addClass('loading');\n // Require the ajax module (must be AMD) and try to load the items.\n require([moduleName], function(loader) {\n // All ajax module must implement a \"load\" method.\n loader.load(item).done(function() {\n item.attr('data-loaded', true);\n\n // Set defaults on the newly constructed part of the tree.\n thisTree.initialiseNodes(item);\n thisTree.finishExpandingGroup(item);\n // Make sure no child elements of the item we just loaded are tabbable.\n p.removeClass('loading');\n promise.resolve();\n });\n });\n } else {\n this.finishExpandingGroup(item);\n promise.resolve();\n }\n } else {\n promise.resolve();\n }\n return promise;\n };\n\n /**\n * Perform the necessary DOM changes to display a group item.\n *\n * @method finishExpandingGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n */\n Tree.prototype.finishExpandingGroup = function(item) {\n // Expand the group.\n var group = this.getGroupFromItem(item);\n group.removeAttr('aria-hidden');\n item.attr('aria-expanded', 'true');\n\n // Update the list of visible items.\n this.refreshVisibleItemsCache();\n };\n\n /**\n * Collapse an expanded group.\n *\n * @method collapseGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n */\n Tree.prototype.collapseGroup = function(item) {\n // If the item is not collapsible or already collapsed then do nothing.\n if (!this.isGroupCollapsible(item) || this.isGroupCollapsed(item)) {\n return;\n }\n\n // Collapse the group.\n var group = this.getGroupFromItem(item);\n group.attr('aria-hidden', 'true');\n item.attr('aria-expanded', 'false');\n\n // Update the list of visible items.\n this.refreshVisibleItemsCache();\n };\n\n /**\n * Expand or collapse a group.\n *\n * @method toggleGroup\n * @param {Object} item is the jquery id of the parent item of the group.\n */\n Tree.prototype.toggleGroup = function(item) {\n if (item.attr('aria-expanded') === 'true') {\n this.collapseGroup(item);\n } else {\n this.expandGroup(item);\n }\n };\n\n /**\n * Handle a key down event - ie navigate the tree.\n *\n * @method handleKeyDown\n * @param {Event} e The event.\n */\n // This function should be simplified. In the meantime..\n // eslint-disable-next-line complexity\n Tree.prototype.handleKeyDown = function(e) {\n var item = $(e.target);\n var currentIndex = this.getVisibleItems().index(item);\n\n if ((e.altKey || e.ctrlKey || e.metaKey) || (e.shiftKey && e.keyCode != this.keys.tab)) {\n // Do nothing.\n return;\n }\n\n switch (e.keyCode) {\n case this.keys.home: {\n // Jump to first item in tree.\n this.getVisibleItems().first().focus();\n\n e.preventDefault();\n return;\n }\n case this.keys.end: {\n // Jump to last visible item.\n this.getVisibleItems().last().focus();\n\n e.preventDefault();\n return;\n }\n case this.keys.enter: {\n var links = item.children('a').length ? item.children('a') : item.children().not(SELECTORS.GROUP).find('a');\n if (links.length) {\n if (links.first().data('overrides-tree-activation-key-handler')) {\n // If the link overrides handling of activation keys, let it do so.\n links.first().triggerHandler(e);\n } else if (typeof this.enterCallback === 'function') {\n // Use callback if there is one.\n this.enterCallback(item);\n } else {\n window.location.href = links.first().attr('href');\n }\n } else if (this.isGroupItem(item)) {\n this.toggleGroup(item, true);\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.space: {\n if (this.isGroupItem(item)) {\n this.toggleGroup(item, true);\n } else if (item.children('a').length) {\n var firstLink = item.children('a').first();\n\n if (firstLink.data('overrides-tree-activation-key-handler')) {\n firstLink.triggerHandler(e);\n }\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.left: {\n var focusParent = function(tree) {\n // Get the immediate visible parent group item that contains this element.\n tree.getVisibleItems().filter(function() {\n return tree.getGroupFromItem($(this)).has(item).length;\n }).focus();\n };\n\n // If this is a goup item then collapse it and focus the parent group\n // in accordance with the aria spec.\n if (this.isGroupItem(item)) {\n if (this.isGroupCollapsed(item)) {\n focusParent(this);\n } else {\n this.collapseGroup(item);\n }\n } else {\n focusParent(this);\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.right: {\n // If this is a group item then expand it and focus the first child item\n // in accordance with the aria spec.\n if (this.isGroupItem(item)) {\n if (this.isGroupCollapsed(item)) {\n this.expandGroup(item);\n } else {\n // Move to the first item in the child group.\n this.getGroupFromItem(item).find(SELECTORS.ITEM).first().focus();\n }\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.up: {\n\n if (currentIndex > 0) {\n var prev = this.getVisibleItems().eq(currentIndex - 1);\n\n prev.focus();\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.down: {\n\n if (currentIndex < this.getVisibleItems().length - 1) {\n var next = this.getVisibleItems().eq(currentIndex + 1);\n\n next.focus();\n }\n\n e.preventDefault();\n return;\n }\n case this.keys.asterisk: {\n // Expand all groups.\n this.expandAllGroups();\n e.preventDefault();\n return;\n }\n }\n };\n\n /**\n * Handle a click (select).\n *\n * @method handleClick\n * @param {Event} e The event.\n */\n Tree.prototype.handleClick = function(e) {\n if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) {\n // Do nothing.\n return;\n }\n\n // Get the closest tree item from the event target.\n var item = $(e.target).closest('[role=\"treeitem\"]');\n if (!item.is(e.currentTarget)) {\n return;\n }\n\n // Update the active item.\n item.focus();\n\n // If the item is a group node.\n if (this.isGroupItem(item)) {\n this.toggleGroup(item);\n }\n };\n\n /**\n * Handle a focus event.\n *\n * @method handleFocus\n * @param {Event} e The event.\n */\n Tree.prototype.handleFocus = function(e) {\n this.setActiveItem($(e.target));\n };\n\n /**\n * Bind the event listeners we require.\n *\n * @method bindEventHandlers\n */\n Tree.prototype.bindEventHandlers = function() {\n // Bind event handlers to the tree items. Use event delegates to allow\n // for dynamically loaded parts of the tree.\n this.treeRoot.on({\n click: this.handleClick.bind(this),\n keydown: this.handleKeyDown.bind(this),\n focus: this.handleFocus.bind(this),\n }, SELECTORS.ITEM);\n };\n\n return /** @alias module:tool_lp/tree */ Tree;\n});\n"],"file":"tree.min.js"} \ No newline at end of file diff --git a/lib/amd/src/tree.js b/lib/amd/src/tree.js index 167e7bb91ad..481ae190266 100644 --- a/lib/amd/src/tree.js +++ b/lib/amd/src/tree.js @@ -269,7 +269,8 @@ define(['jquery'], function($) { var moduleName = item.closest('[data-ajax-loader]').attr('data-ajax-loader'); var thisTree = this; // Flag this node as loading. - item.addClass('loading'); + const p = item.find('p'); + p.addClass('loading'); // Require the ajax module (must be AMD) and try to load the items. require([moduleName], function(loader) { // All ajax module must implement a "load" method. @@ -280,7 +281,7 @@ define(['jquery'], function($) { thisTree.initialiseNodes(item); thisTree.finishExpandingGroup(item); // Make sure no child elements of the item we just loaded are tabbable. - item.removeClass('loading'); + p.removeClass('loading'); promise.resolve(); }); }); diff --git a/lib/tests/behat/behat_navigation.php b/lib/tests/behat/behat_navigation.php index 7acc898f3dc..85e628fb5c8 100644 --- a/lib/tests/behat/behat_navigation.php +++ b/lib/tests/behat/behat_navigation.php @@ -64,8 +64,8 @@ class behat_navigation extends behat_base { $nodetextliteral = behat_context_helper::escape($text); $hasblocktree = "[contains(concat(' ', normalize-space(@class), ' '), ' block_tree ')]"; $hasbranch = "[contains(concat(' ', normalize-space(@class), ' '), ' branch ')]"; - $hascollapsed = "p[@aria-expanded='false']"; - $notcollapsed = "p[@aria-expanded='true']"; + $hascollapsed = "li[@aria-expanded='false']/p"; + $notcollapsed = "li[@aria-expanded='true']/p"; $match = "[normalize-space(.)={$nodetextliteral}]"; // Avoid problems with quotes. @@ -75,18 +75,18 @@ class behat_navigation extends behat_base { } else if ($collapsed === false) { $iscollapsed = $notcollapsed; } else { - $iscollapsed = 'p'; + $iscollapsed = 'li/p'; } // First check root nodes, it can be a span or link. - $xpath = "//ul{$hasblocktree}/li/{$hascollapsed}{$isbranch}/span{$match}|"; - $xpath .= "//ul{$hasblocktree}/li/{$hascollapsed}{$isbranch}/a{$match}|"; + $xpath = "//ul{$hasblocktree}/{$hascollapsed}{$isbranch}/span{$match}|"; + $xpath .= "//ul{$hasblocktree}/{$hascollapsed}{$isbranch}/a{$match}|"; // Next search for the node containing the text within a link. - $xpath .= "//ul{$hasblocktree}//ul/li/{$iscollapsed}{$isbranch}/a{$match}|"; + $xpath .= "//ul{$hasblocktree}//ul/{$iscollapsed}{$isbranch}/a{$match}|"; // Finally search for the node containing the text within a span. - $xpath .= "//ul{$hasblocktree}//ul/li/{$iscollapsed}{$isbranch}/span{$match}"; + $xpath .= "//ul{$hasblocktree}//ul/{$iscollapsed}{$isbranch}/span{$match}"; $node = $this->find('xpath', $xpath, $exception); $this->ensure_node_is_visible($node); @@ -263,16 +263,16 @@ class behat_navigation extends behat_base { // The p node contains the aria jazz. $pnodexpath = "/p[contains(concat(' ', normalize-space(@class), ' '), ' tree_item ')]"; $pnode = $node->find('xpath', $pnodexpath); + $linode = $pnode->getParent(); // Keep expanding all sub-parents if js enabled. - if ($pnode && $this->running_javascript() && $pnode->hasAttribute('aria-expanded') && - ($pnode->getAttribute('aria-expanded') == "false")) { - + if ($pnode && $this->running_javascript() && $linode->hasAttribute('aria-expanded') && + ($linode->getAttribute('aria-expanded') == "false")) { $this->js_trigger_click($pnode); // Wait for node to load, if not loaded before. - if ($pnode->hasAttribute('data-loaded') && $pnode->getAttribute('data-loaded') == "false") { - $jscondition = '(document.evaluate("' . $pnode->getXpath() . '", document, null, '. + if ($linode->hasAttribute('data-loaded') && $linode->getAttribute('data-loaded') == "false") { + $jscondition = '(document.evaluate("' . $linode->getXpath() . '", document, null, '. 'XPathResult.ANY_TYPE, null).iterateNext().getAttribute(\'data-loaded\') == "true")'; $this->getSession()->wait(behat_base::get_extended_timeout() * 1000, $jscondition); diff --git a/theme/boost/scss/moodle/blocks.scss b/theme/boost/scss/moodle/blocks.scss index 95ac0b7fbe1..6ca04b53564 100644 --- a/theme/boost/scss/moodle/blocks.scss +++ b/theme/boost/scss/moodle/blocks.scss @@ -322,8 +322,8 @@ body.drawer-open-left #region-main.has-blocks { .block_navigation .block_tree [aria-expanded="false"] { background-image: none; } -.block_settings .block_tree [aria-expanded="true"]:before, -.block_navigation .block_tree [aria-expanded="true"]:before { +.block_settings .block_tree [aria-expanded="true"] > p:before, +.block_navigation .block_tree [aria-expanded="true"] > p:before { content: $fa-var-angle-down; margin-right: 0; @include fa-icon(); @@ -331,8 +331,8 @@ body.drawer-open-left #region-main.has-blocks { width: 16px; } -.block_settings .block_tree [aria-expanded="false"]:before, -.block_navigation .block_tree [aria-expanded="false"]:before { +.block_settings .block_tree [aria-expanded="false"] > p:before, +.block_navigation .block_tree [aria-expanded="false"] > p:before { content: $fa-var-angle-right; margin-right: 0; @include fa-icon(); @@ -340,8 +340,8 @@ body.drawer-open-left #region-main.has-blocks { width: 16px; } .dir-rtl { - .block_settings .block_tree [aria-expanded="false"]:before, - .block_navigation .block_tree [aria-expanded="false"]:before { + .block_settings .block_tree [aria-expanded="false"] > p:before, + .block_navigation .block_tree [aria-expanded="false"] > p:before { content: $fa-var-angle-left; } } diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 0539a8e57bc..87cf85b08da 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -12744,8 +12744,8 @@ input[disabled] { .block_navigation .block_tree [aria-expanded="false"] { background-image: none; } -.block_settings .block_tree [aria-expanded="true"]:before, -.block_navigation .block_tree [aria-expanded="true"]:before { +.block_settings .block_tree [aria-expanded="true"] > p:before, +.block_navigation .block_tree [aria-expanded="true"] > p:before { content: ""; margin-right: 0; display: inline-block; @@ -12757,8 +12757,8 @@ input[disabled] { font-size: 16px; width: 16px; } -.block_settings .block_tree [aria-expanded="false"]:before, -.block_navigation .block_tree [aria-expanded="false"]:before { +.block_settings .block_tree [aria-expanded="false"] > p:before, +.block_navigation .block_tree [aria-expanded="false"] > p:before { content: ""; margin-right: 0; display: inline-block; @@ -12770,8 +12770,8 @@ input[disabled] { font-size: 16px; width: 16px; } -.dir-rtl .block_settings .block_tree [aria-expanded="false"]:before, -.dir-rtl .block_navigation .block_tree [aria-expanded="false"]:before { +.dir-rtl .block_settings .block_tree [aria-expanded="false"] > p:before, +.dir-rtl .block_navigation .block_tree [aria-expanded="false"] > p:before { content: ""; } .block_navigation .block_tree p.hasicon, diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index cdbdf87f5ec..4dcd0dc1f70 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -12966,8 +12966,8 @@ input[disabled] { .block_navigation .block_tree [aria-expanded="false"] { background-image: none; } -.block_settings .block_tree [aria-expanded="true"]:before, -.block_navigation .block_tree [aria-expanded="true"]:before { +.block_settings .block_tree [aria-expanded="true"] > p:before, +.block_navigation .block_tree [aria-expanded="true"] > p:before { content: ""; margin-right: 0; display: inline-block; @@ -12979,8 +12979,8 @@ input[disabled] { font-size: 16px; width: 16px; } -.block_settings .block_tree [aria-expanded="false"]:before, -.block_navigation .block_tree [aria-expanded="false"]:before { +.block_settings .block_tree [aria-expanded="false"] > p:before, +.block_navigation .block_tree [aria-expanded="false"] > p:before { content: ""; margin-right: 0; display: inline-block; @@ -12992,8 +12992,8 @@ input[disabled] { font-size: 16px; width: 16px; } -.dir-rtl .block_settings .block_tree [aria-expanded="false"]:before, -.dir-rtl .block_navigation .block_tree [aria-expanded="false"]:before { +.dir-rtl .block_settings .block_tree [aria-expanded="false"] > p:before, +.dir-rtl .block_navigation .block_tree [aria-expanded="false"] > p:before { content: ""; } .block_navigation .block_tree p.hasicon,