| 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); |
| } |
| } |
| } |