Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Unified Diff: options.js

Issue 29332808: Issue 2408 - Improved accessibility of checkboxes in options page (Closed)
Patch Set: Created Dec. 16, 2015, 1:31 p.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « options.html ('k') | skin/options.css » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: options.js
===================================================================
--- a/options.js
+++ b/options.js
@@ -23,6 +23,7 @@
var recommendationsMap = Object.create(null);
var filtersMap = Object.create(null);
var collections = Object.create(null);
+ var maxLabelId = 0;
function Collection(details)
{
@@ -50,25 +51,27 @@
for (var i = 0; i < arguments.length; i++)
{
var item = arguments[i];
- var text = item.title || item.url || item.text;
var listItem = document.createElement("li");
listItem.appendChild(document.importNode(template.content, true));
listItem.setAttribute("data-access", item.url || item.text);
- listItem.querySelector(".display").textContent = text;
- if (text)
- listItem.setAttribute("data-search", text.toLowerCase());
+ var labelId = "label-" + (++maxLabelId);
+ var label = listItem.querySelector(".display");
+ label.setAttribute("id", labelId);
var control = listItem.querySelector(".control");
if (control)
{
+ // We use aria-labelledby to avoid triggering the control when
+ // interacting with the label
+ control.setAttribute("aria-labelledby", labelId);
control.addEventListener("click", this.details[j].onClick, false);
- control.checked = item.disabled == false;
}
if (table.hasChildNodes())
table.insertBefore(listItem, table.childNodes[this.items.indexOf(item)]);
else
table.appendChild(listItem);
+ this.updateItem(item);
}
}
return length;
@@ -86,10 +89,55 @@
{
var table = E(this.details[i].id);
var element = table.querySelector("[data-access='" + access + "']");
+
+ // Element gets removed so make sure to handle focus appropriately
+ var control = element.querySelector(".control");
+ if (control && control == document.activeElement)
+ {
+ if (!focusNextElement(element.parentElement, control))
+ {
+ // Fall back to next focusable element within same tab
+ var tab = element.parentElement;
+ while (true)
+ {
+ if (tab.classList.contains("tab-content"))
+ break;
+
+ tab = tab.parentElement;
+ if (!tab)
+ {
+ tab = document;
+ break;
+ }
+ }
+ focusNextElement(tab, control);
+ }
+ }
+
element.parentElement.removeChild(element);
}
};
+ Collection.prototype.updateItem = function(item)
+ {
+ var access = (item.url || item.text).replace(/'/g, "\\'");
+ for (var i = 0; i < this.details.length; i++)
+ {
+ var table = E(this.details[i].id);
+ var element = table.querySelector("[data-access='" + access + "']");
+ if (!element)
+ continue;
+
+ var text = item.title || item.url || item.text;
+ element.querySelector(".display").textContent = text;
+ if (text)
+ element.setAttribute("data-search", text.toLowerCase());
+ var control = element.querySelector(".control[role='checkbox']");
+ if (control)
+ control.setAttribute("aria-checked", item.disabled == false);
+ }
+ };
+
Collection.prototype.clearAll = function()
{
for (var i = 0; i < this.details.length; i++)
@@ -102,17 +150,36 @@
this.items.length = 0;
};
+ function focusNextElement(container, currentElement)
+ {
+ var focusables = container.querySelectorAll("a, button, .control");
+ focusables = Array.prototype.slice.call(focusables);
+ var index = focusables.indexOf(currentElement);
+ if (index + 1 < focusables.length)
+ index += 1;
+ else if (index < focusables.length)
+ index -= 1;
+
+ var nextElement = focusables[index];
+ if (!nextElement)
+ return false;
+
+ nextElement.focus();
+ return true;
+ }
+
function onToggleSubscriptionClick(e)
{
e.preventDefault();
- var subscriptionUrl = e.target.parentNode.getAttribute("data-access");
- if (!e.target.checked)
+ var checkbox = e.target;
+ var subscriptionUrl = checkbox.parentElement.getAttribute("data-access");
+ if (checkbox.getAttribute("aria-checked") == "true")
{
- ext.backgroundPage.sendMessage(
- {
- type: "subscriptions.remove",
- url: subscriptionUrl
- });
+ ext.backgroundPage.sendMessage(
+ {
+ type: "subscriptions.remove",
+ url: subscriptionUrl
+ });
}
else
addEnableSubscription(subscriptionUrl);
@@ -155,28 +222,28 @@
collections.allLangs = new Collection(
[
{
- id: "all-lang-table",
+ id: "all-lang-table",
onClick: onAddLanguageSubscriptionClick
}
]);
collections.acceptableAds = new Collection(
[
{
- id: "acceptableads-table",
+ id: "acceptableads-table",
onClick: onToggleSubscriptionClick
}
]);
collections.custom = new Collection(
[
{
- id: "custom-list-table",
+ id: "custom-list-table",
onClick: onToggleSubscriptionClick
}
]);
collections.whitelist = new Collection(
[
{
- id: "whitelisting-table",
+ id: "whitelisting-table",
onClick: onRemoveFilterClick
}
]);
@@ -199,30 +266,21 @@
{
function onObjectChanged()
{
- var access = (subscriptionUrl || subscription.text).replace(/'/g, "\\'");
- var elements = document.querySelectorAll("[data-access='" + access + "']");
- for (var i = 0; i < elements.length; i++)
+ for (var i in collections)
+ collections[i].updateItem(subscription);
+
+ var recommendation = recommendationsMap[subscriptionUrl];
+ if (recommendation && recommendation.type == "ads")
{
- var element = elements[i];
- var control = element.querySelector(".control");
- if (control.localName == "input")
- control.checked = subscription.disabled == false;
- if (subscriptionUrl in recommendationsMap)
+ if (subscription.disabled == false)
{
- var recommendation = recommendationsMap[subscriptionUrl];
- if (recommendation.type == "ads")
- {
- if (subscription.disabled == false)
- {
- collections.allLangs.removeItem(subscription);
- collections.langs.addItems(subscription);
- }
- else
- {
- collections.allLangs.addItems(subscription);
- collections.langs.removeItem(subscription);
- }
- }
+ collections.allLangs.removeItem(subscription);
+ collections.langs.addItems(subscription);
+ }
+ else
+ {
+ collections.allLangs.addItems(subscription);
+ collections.langs.removeItem(subscription);
}
}
}
« no previous file with comments | « options.html ('k') | skin/options.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld