Skip to content

Commit ebfc0e6

Browse files
committed
ENH Add aria attrs to site tree
1 parent 33d9091 commit ebfc0e6

File tree

7 files changed

+125
-7
lines changed

7 files changed

+125
-7
lines changed

client/dist/js/bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client/src/legacy/CMSMain.Tree.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,14 @@ $.entwine('ss.tree', function($) {
185185
}
186186
});
187187

188+
// Add role="tree" to the root level <ul> for accessibility
189+
// This needs to be done with JS as jstree will override any attributes set server-side
190+
$('.cms-tree > ul').entwine({
191+
onmatch: function() {
192+
this.attr('role', 'tree');
193+
}
194+
});
195+
188196
// Scroll tree down to context of the current page, if it isn't
189197
// already visible
190198
$('.cms-tree a.jstree-clicked').entwine({

code/Controllers/CMSMain.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,8 @@ protected function getTreeNodeCustomisations()
599599
'hasCurrentPage' => $hasCurrentPage,
600600
'listViewLink' => $this->LinkListViewChildren($node->ID),
601601
'rootTitle' => $this->getCMSTreeTitle(),
602+
'toggleSiteTreeLabel' => _t(CMSMain::class . '.TOGGLE_SITE_TREE', 'Toggle site tree'),
603+
'toggleChildPagesLabel' => _t(CMSMain::class . '.TOGGLE_CHILD_PAGES', 'Toggle child pages'),
602604
'extraClass' => $this->getTreeNodeClasses($node),
603605
'Title' => _t(
604606
CMSMain::class . '.RECORD_TYPE_TITLE',
@@ -608,6 +610,13 @@ protected function getTreeNodeCustomisations()
608610
'title' => $node->Title,
609611
]
610612
),
613+
'ToggleTitle' => _t(
614+
CMSMain::class . '.TOGGLE_CHILD_PAGES',
615+
'Toggle child pages of {title}',
616+
[
617+
'title' => $node->Title,
618+
]
619+
),
611620
'TreeTitle' => DBHTMLText::create()->setValue($this->getRecordTreeMarkup($node)),
612621
];
613622
};

lang/en.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ en:
8989
SEARCHRESULTS: 'Search results'
9090
SHOW_AS_LIST: 'show as list'
9191
SITE_PAGE_NAVIGATION: 'Site Page Navigation'
92+
TOGGLE_CHILD_PAGES: 'Toggle child pages'
93+
TOGGLE_SITE_TREE: 'Toggle site tree'
9294
TOO_MANY_PAGES: 'Too many pages'
9395
TOO_MANY_RECORDS: 'Too many records'
9496
TREE_NO_TITLE: '(no title)'

templates/SilverStripe/CMS/Controllers/Includes/CMSMain_SubTree.ss

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
<% if not $node.IsInDB %><%-- Only render root node if it's the true root --%>
2-
<ul><li id="record-0" data-id="0" class="Root nodelete"><span class="jstree-icon jstree-icon--arrow"><span class="font-icon-right-dir" aria-hidden="true"></span>&nbsp;</span>
3-
<strong tabindex="-1">$rootTitle</strong>
2+
<ul role="tree"><li id="record-0" data-id="0" class="Root nodelete" role="none"><span class="jstree-icon jstree-icon--arrow"<% if $count %> role="button" aria-label="{$toggleSiteTreeLabel.ATT}" aria-expanded="<% if $opened %>true<% else %>false<% end_if %>"<% else %> aria-hidden="true"<% end_if %>><span class="font-icon-right-dir" aria-hidden="true"></span>&nbsp;</span>
3+
<strong tabindex="-1" role="treeitem" aria-level="1"<% if $count %> aria-expanded="<% if $opened %>true<% else %>false<% end_if %>"<% end_if %>>$rootTitle</strong>
44
<% end_if %>
55
<% if $limited %>
6-
<ul><li class="readonly">
6+
<ul id="subtree-<% if $node.IsInDB %>$node.ID<% else %>0<% end_if %>" role="group"><li class="readonly" role="none">
77
<span class="item">
88
<%t SilverStripe\\CMS\\Controllers\\CMSMain.TOO_MANY_RECORDS 'Too many records' %>
99
(<a href="{$listViewLink.ATT}" class="subtree-list-link" data-id="$node.ID" data-pjax-target="Content"><%t SilverStripe\\CMS\\Controllers\\CMSMain.SHOW_AS_LIST 'show as list' %></a>)
1010
</span>
1111
</li></ul>
1212
<% else_if $children %>
13-
<ul>
13+
<ul id="subtree-<% if $node.IsInDB %>$node.ID<% else %>0<% end_if %>" role="group">
1414
<% loop $children %><% include SilverStripe\\CMS\\Controllers\\CMSMain_TreeNode Controller=$Top.Controller %><% end_loop %>
1515
</ul>
1616
<% end_if %>

templates/SilverStripe/CMS/Controllers/Includes/CMSMain_TreeNode.ss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
<li id="record-{$node.ID}" data-id="{$node.ID}" data-recordtype="{$node.ClassName}" class="$markingClasses $extraClass"><span class="jstree-icon jstree-icon--arrow"><span class="font-icon-right-dir" aria-hidden="true"></span>&nbsp;</span>
1+
<li id="record-{$node.ID}" data-id="{$node.ID}" data-recordtype="{$node.ClassName}" class="$markingClasses $extraClass" role="none"><span class="jstree-icon jstree-icon--arrow"<% if $count %> role="button" aria-label="{$toggleChildPagesLabel.ATT}" aria-expanded="<% if $opened %>true<% else %>false<% end_if %>"<% else %> aria-hidden="true"<% end_if %>><span class="font-icon-right-dir" aria-hidden="true"></span>&nbsp;</span>
22
<%-- IMPORTANT: There MUST NOT be any whitespace between the <a> element and the <ins> element below or it will break things in the JS --%>
3-
<a href="{$Controller.LinkRecordEdit($node.ID).ATT}" title="{$Title.ATT}"<% if $isCurrentPage %> tabindex="0" aria-current="page"<% else_if not $hasCurrentPage && $isFirstPage %> tabindex="0"<% else %> tabindex="-1"<% end_if %>><ins class="jstree-icon jstree-icon--drag-handle"><span class="font-icon-drag-handle" aria-hidden="true"></span>&nbsp;</ins>
3+
<a href="{$Controller.LinkRecordEdit($node.ID).ATT}" title="{$Title.ATT}" role="treeitem" aria-level="{$level}"<% if $count %> aria-expanded="<% if $opened %>true<% else %>false<% end_if %>"<% end_if %><% if $isCurrentPage %> tabindex="0" aria-current="page"<% else_if not $hasCurrentPage && $isFirstPage %> tabindex="0"<% else %> tabindex="-1"<% end_if %>><ins class="jstree-icon jstree-icon--drag-handle"><span class="font-icon-drag-handle" aria-hidden="true"></span>&nbsp;</ins>
44
<span class="text">{$TreeTitle}</span>
55
</a>
66
$SubTree

tests/php/Controllers/CMSMainTest.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,78 @@ public function testTreeAsULPrepopulateOptions(bool $siteTreeConfig, string $exp
904904
$this->assertSame($expected, $actual);
905905
}
906906

907+
public function testTreeMarkupProvidesAriaForCollapsedAndLeafNodes(): void
908+
{
909+
$this->logInWithPermission('ADMIN');
910+
911+
$page1ID = $this->idFromFixture(SiteTree::class, 'page1');
912+
$page3ID = $this->idFromFixture(SiteTree::class, 'page3');
913+
$parser = $this->getTreeMarkupParser();
914+
915+
$this->assertCount(1, $parser->getByXpath('//ul[@role="tree"]'));
916+
$this->assertCount(
917+
1,
918+
$parser->getByXpath(
919+
'//li[@id="record-0"]/strong[@role="treeitem" and @aria-level="1" and @aria-expanded="false"]'
920+
)
921+
);
922+
$this->assertCount(
923+
1,
924+
$parser->getByXpath(
925+
'//li[@id="record-0"]/span[contains(@class, "jstree-icon--arrow") and @role="button" and @aria-label="Toggle site tree" and @aria-expanded="false"]'
926+
)
927+
);
928+
$this->assertCount(
929+
1,
930+
$parser->getByXpath('//li[@id="record-' . $page1ID . '"]/a[@role="treeitem" and @aria-level="2"]')
931+
);
932+
$this->assertNull(
933+
$this->getTreeMarkupAttribute($parser, '//li[@id="record-' . $page1ID . '"]/a[@role="treeitem"]', 'aria-expanded')
934+
);
935+
$this->assertCount(
936+
0,
937+
$parser->getByXpath(
938+
'//li[@id="record-' . $page1ID . '"]/span[contains(@class, "jstree-icon--arrow") and @role="button"]'
939+
)
940+
);
941+
$this->assertCount(
942+
1,
943+
$parser->getByXpath(
944+
'//li[@id="record-' . $page3ID . '"]/a[@role="treeitem" and @aria-level="2" and @aria-expanded="false"]'
945+
)
946+
);
947+
$this->assertCount(
948+
1,
949+
$parser->getByXpath(
950+
'//li[@id="record-' . $page3ID . '"]/span[contains(@class, "jstree-icon--arrow") and @role="button" and @aria-label="Toggle child pages" and @aria-expanded="false"]'
951+
)
952+
);
953+
}
954+
955+
public function testTreeMarkupProvidesAriaForExpandedAncestors(): void
956+
{
957+
$this->logInWithPermission('ADMIN');
958+
959+
$page3ID = $this->idFromFixture(SiteTree::class, 'page3');
960+
$page31ID = $this->idFromFixture(SiteTree::class, 'page31');
961+
$parser = $this->getTreeMarkupParser($this->idFromFixture(SiteTree::class, 'page311'));
962+
963+
$this->assertCount(
964+
1,
965+
$parser->getByXpath(
966+
'//li[@id="record-' . $page3ID . '"]/a[@role="treeitem" and @aria-level="2" and @aria-expanded="true"]'
967+
)
968+
);
969+
$this->assertCount(
970+
1,
971+
$parser->getByXpath(
972+
'//li[@id="record-' . $page31ID . '"]/a[@role="treeitem" and @aria-level="3" and @aria-expanded="true"]'
973+
)
974+
);
975+
$this->assertCount(1, $parser->getByXpath('//ul[@id="subtree-' . $page3ID . '" and @role="group"]'));
976+
$this->assertCount(1, $parser->getByXpath('//ul[@id="subtree-' . $page31ID . '" and @role="group"]'));
977+
}
978+
907979
public static function provideGetRecordTreeMarkup(): array
908980
{
909981
return [
@@ -944,6 +1016,33 @@ public function testGetRecordTreeMarkup(string $title, string $expected): void
9441016
$this->assertSame($expected, $actual);
9451017
}
9461018

1019+
protected function getTreeMarkupParser(?int $currentRecordID = null): CSSContentParser
1020+
{
1021+
$cmsMain = CMSMain::create();
1022+
$cmsMain->setRequest(new HTTPRequest('GET', 'admin/pages'));
1023+
$cmsMain->setCurrentRecordID($currentRecordID);
1024+
1025+
return new CSSContentParser($cmsMain->TreeAsUL());
1026+
}
1027+
1028+
protected function getTreeMarkupAttribute(CSSContentParser $parser, string $selector, string $attribute)
1029+
{
1030+
$nodes = str_starts_with($selector, '//')
1031+
? $parser->getByXpath($selector)
1032+
: $parser->getBySelector($selector);
1033+
if (empty($nodes)) {
1034+
return null;
1035+
}
1036+
1037+
foreach ($nodes[0]->attributes() as $key => $value) {
1038+
if ((string)$key === $attribute) {
1039+
return (string)$value;
1040+
}
1041+
}
1042+
1043+
return null;
1044+
}
1045+
9471046
public function testGetArchiveWarningMessage(): void
9481047
{
9491048
$controller = new CMSMain();

0 commit comments

Comments
 (0)