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

Side by Side Diff: options.js

Issue 29332808: Issue 2408 - Improved accessibility of checkboxes in options page (Closed)
Patch Set: Rebased to 59920e6112a6 Created Dec. 17, 2015, 6:36 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
OLDNEW
1 /* 1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>, 2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2015 Eyeo GmbH 3 * Copyright (C) 2006-2015 Eyeo GmbH
4 * 4 *
5 * Adblock Plus is free software: you can redistribute it and/or modify 5 * Adblock Plus is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 3 as 6 * it under the terms of the GNU General Public License version 3 as
7 * published by the Free Software Foundation. 7 * published by the Free Software Foundation.
8 * 8 *
9 * Adblock Plus is distributed in the hope that it will be useful, 9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. 12 * GNU General Public License for more details.
13 * 13 *
14 * You should have received a copy of the GNU General Public License 14 * You should have received a copy of the GNU General Public License
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
16 */ 16 */
17 17
18 "use strict"; 18 "use strict";
19 19
20 (function() 20 (function()
21 { 21 {
22 var subscriptionsMap = Object.create(null); 22 var subscriptionsMap = Object.create(null);
23 var recommendationsMap = Object.create(null); 23 var recommendationsMap = Object.create(null);
24 var filtersMap = Object.create(null); 24 var filtersMap = Object.create(null);
25 var collections = Object.create(null); 25 var collections = Object.create(null);
26 var maxLabelId = 0;
26 27
27 function Collection(details) 28 function Collection(details)
28 { 29 {
29 this.details = details; 30 this.details = details;
30 this.items = []; 31 this.items = [];
31 } 32 }
32 33
33 Collection.prototype._setEmpty = function(table, text) 34 Collection.prototype._setEmpty = function(table, text)
34 { 35 {
35 var placeholder = table.querySelector(".empty-placeholder"); 36 var placeholder = table.querySelector(".empty-placeholder");
(...skipping 21 matching lines...) Expand all
57 return aValue.localeCompare(bValue); 58 return aValue.localeCompare(bValue);
58 }); 59 });
59 60
60 for (var j = 0; j < this.details.length; j++) 61 for (var j = 0; j < this.details.length; j++)
61 { 62 {
62 var table = E(this.details[j].id); 63 var table = E(this.details[j].id);
63 var template = table.querySelector("template"); 64 var template = table.querySelector("template");
64 for (var i = 0; i < arguments.length; i++) 65 for (var i = 0; i < arguments.length; i++)
65 { 66 {
66 var item = arguments[i]; 67 var item = arguments[i];
67 var text = item.title || item.url || item.text;
68 var listItem = document.createElement("li"); 68 var listItem = document.createElement("li");
69 listItem.appendChild(document.importNode(template.content, true)); 69 listItem.appendChild(document.importNode(template.content, true));
70 listItem.setAttribute("data-access", item.url || item.text); 70 listItem.setAttribute("data-access", item.url || item.text);
71 listItem.querySelector(".display").textContent = text;
72 if (text)
73 listItem.setAttribute("data-search", text.toLowerCase());
74 71
72 var labelId = "label-" + (++maxLabelId);
73 var label = listItem.querySelector(".display");
saroyanm 2016/01/19 11:19:50 Nit: we can have two lines merged: listItem.query
Thomas Greiner 2016/01/19 15:15:04 Done.
74 label.setAttribute("id", labelId);
75 var control = listItem.querySelector(".control"); 75 var control = listItem.querySelector(".control");
76 if (control) 76 if (control)
77 { 77 {
78 // We use aria-labelledby to avoid triggering the control when
79 // interacting with the label
80 control.setAttribute("aria-labelledby", labelId);
78 control.addEventListener("click", this.details[j].onClick, false); 81 control.addEventListener("click", this.details[j].onClick, false);
79 control.checked = item.disabled == false;
80 } 82 }
81 83
82 this._setEmpty(table, null); 84 this._setEmpty(table, null);
83 if (table.hasChildNodes()) 85 if (table.hasChildNodes())
84 table.insertBefore(listItem, table.childNodes[this.items.indexOf(item) ]); 86 table.insertBefore(listItem, table.childNodes[this.items.indexOf(item) ]);
85 else 87 else
86 table.appendChild(listItem); 88 table.appendChild(listItem);
89 this.updateItem(item);
87 } 90 }
88 } 91 }
89 return length; 92 return length;
90 }; 93 };
91 94
92 Collection.prototype.removeItem = function(item) 95 Collection.prototype.removeItem = function(item)
93 { 96 {
94 var index = this.items.indexOf(item); 97 var index = this.items.indexOf(item);
95 if (index == -1) 98 if (index == -1)
96 return; 99 return;
97 100
98 this.items.splice(index, 1); 101 this.items.splice(index, 1);
99 for (var i = 0; i < this.details.length; i++) 102 for (var i = 0; i < this.details.length; i++)
100 { 103 {
101 var table = E(this.details[i].id); 104 var table = E(this.details[i].id);
102 var element = table.childNodes[index]; 105 var element = table.childNodes[index];
106
107 // Element gets removed so make sure to handle focus appropriately
108 var control = element.querySelector(".control");
109 if (control && control == document.activeElement)
110 {
111 if (!focusNextElement(element.parentElement, control))
112 {
113 // Fall back to next focusable element within same tab
114 var tab = element.parentElement;
115 while (true)
saroyanm 2016/01/19 11:19:49 What about ? while (tab.tagName == "BODY") ? In t
Thomas Greiner 2016/01/19 15:15:04 Done. Checking for the element type should be the
saroyanm 2016/01/25 14:45:43 you can also check for the element itself "documen
116 {
117 if (tab.classList.contains("tab-content"))
118 break;
119
120 tab = tab.parentElement;
121 if (!tab)
122 {
123 tab = document;
124 break;
125 }
126 }
127 focusNextElement(tab, control);
128 }
129 }
130
103 element.parentElement.removeChild(element); 131 element.parentElement.removeChild(element);
104 if (this.items.length == 0) 132 if (this.items.length == 0)
105 this._setEmpty(table, this.details[i].emptyText); 133 this._setEmpty(table, this.details[i].emptyText);
106 } 134 }
107 }; 135 };
108 136
137 Collection.prototype.updateItem = function(item)
saroyanm 2016/01/19 11:19:50 I think it make sense to have second optional argu
Thomas Greiner 2016/01/19 15:15:04 But an item can be associated with multiple list e
138 {
139 var access = (item.url || item.text).replace(/'/g, "\\'");
140 for (var i = 0; i < this.details.length; i++)
141 {
142 var table = E(this.details[i].id);
143 var element = table.querySelector("[data-access='" + access + "']");
144 if (!element)
145 continue;
146
147 var text = item.title || item.url || item.text;
148 element.querySelector(".display").textContent = text;
149 if (text)
150 element.setAttribute("data-search", text.toLowerCase());
151 var control = element.querySelector(".control[role='checkbox']");
152 if (control)
153 control.setAttribute("aria-checked", item.disabled == false);
154 }
155 };
156
109 Collection.prototype.clearAll = function() 157 Collection.prototype.clearAll = function()
110 { 158 {
111 this.items = []; 159 this.items = [];
112 for (var i = 0; i < this.details.length; i++) 160 for (var i = 0; i < this.details.length; i++)
113 { 161 {
114 var table = E(this.details[i].id); 162 var table = E(this.details[i].id);
115 var template = table.querySelector("template"); 163 var template = table.querySelector("template");
116 table.innerHTML = ""; 164 table.innerHTML = "";
117 table.appendChild(template); 165 table.appendChild(template);
118 this._setEmpty(table, this.details[i].emptyText); 166 this._setEmpty(table, this.details[i].emptyText);
119 } 167 }
120 }; 168 };
121 169
170 function focusNextElement(container, currentElement)
171 {
172 var focusables = container.querySelectorAll("a, button, .control");
saroyanm 2016/01/19 11:19:50 What about have a focus for input elements as well
Thomas Greiner 2016/01/19 15:15:04 Done.
173 focusables = Array.prototype.slice.call(focusables);
174 var index = focusables.indexOf(currentElement);
175 if (index + 1 < focusables.length)
saroyanm 2016/01/19 11:19:49 I think we can write this one line, something like
Thomas Greiner 2016/01/19 15:15:04 Done.
176 index += 1;
177 else if (index < focusables.length)
178 index -= 1;
179
180 var nextElement = focusables[index];
181 if (!nextElement)
182 return false;
183
184 nextElement.focus();
185 return true;
186 }
187
122 function onToggleSubscriptionClick(e) 188 function onToggleSubscriptionClick(e)
123 { 189 {
124 e.preventDefault(); 190 e.preventDefault();
125 var subscriptionUrl = e.target.parentNode.getAttribute("data-access"); 191 var checkbox = e.target;
126 if (!e.target.checked) 192 var subscriptionUrl = checkbox.parentElement.getAttribute("data-access");
193 if (checkbox.getAttribute("aria-checked") == "true")
127 { 194 {
128 ext.backgroundPage.sendMessage({ 195 ext.backgroundPage.sendMessage({
129 type: "subscriptions.remove", 196 type: "subscriptions.remove",
130 url: subscriptionUrl 197 url: subscriptionUrl
131 }); 198 });
132 } 199 }
133 else 200 else
134 addEnableSubscription(subscriptionUrl); 201 addEnableSubscription(subscriptionUrl);
135 } 202 }
136 203
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
213 var subscriptionUrl = subscription.url; 280 var subscriptionUrl = subscription.url;
214 var knownSubscription = subscriptionsMap[subscriptionUrl]; 281 var knownSubscription = subscriptionsMap[subscriptionUrl];
215 if (knownSubscription) 282 if (knownSubscription)
216 knownSubscription.disabled = subscription.disabled; 283 knownSubscription.disabled = subscription.disabled;
217 else 284 else
218 { 285 {
219 getAcceptableAdsURL(function(acceptableAdsUrl) 286 getAcceptableAdsURL(function(acceptableAdsUrl)
220 { 287 {
221 function onObjectChanged() 288 function onObjectChanged()
222 { 289 {
223 var access = (subscriptionUrl || subscription.text).replace(/'/g, "\\' "); 290 for (var i in collections)
224 var elements = document.querySelectorAll("[data-access='" + access + " ']"); 291 collections[i].updateItem(subscription);
225 for (var i = 0; i < elements.length; i++) 292
293 var recommendation = recommendationsMap[subscriptionUrl];
294 if (recommendation && recommendation.type == "ads")
226 { 295 {
227 var element = elements[i]; 296 if (subscription.disabled == false)
228 var control = element.querySelector(".control");
229 if (control.localName == "input")
230 control.checked = subscription.disabled == false;
231 if (subscriptionUrl in recommendationsMap)
232 { 297 {
233 var recommendation = recommendationsMap[subscriptionUrl]; 298 collections.allLangs.removeItem(subscription);
234 if (recommendation.type == "ads") 299 collections.langs.addItems(subscription);
235 { 300 }
236 if (subscription.disabled == false) 301 else
237 { 302 {
238 collections.allLangs.removeItem(subscription); 303 collections.allLangs.addItems(subscription);
239 collections.langs.addItems(subscription); 304 collections.langs.removeItem(subscription);
240 }
241 else
242 {
243 collections.allLangs.addItems(subscription);
244 collections.langs.removeItem(subscription);
245 }
246 }
247 } 305 }
248 } 306 }
249 } 307 }
250 308
251 if (!Object.observe) 309 if (!Object.observe)
252 { 310 {
253 // Currently only "disabled" property of subscription used for observa tion 311 // Currently only "disabled" property of subscription used for observa tion
254 // but with Advanced tab implementation we should also add more proper ties. 312 // but with Advanced tab implementation we should also add more proper ties.
255 ["disabled"].forEach(function(property) 313 ["disabled"].forEach(function(property)
256 { 314 {
(...skipping 560 matching lines...) Expand 10 before | Expand all | Expand 10 after
817 filter: ["added", "loaded", "removed"] 875 filter: ["added", "loaded", "removed"]
818 }); 876 });
819 ext.backgroundPage.sendMessage( 877 ext.backgroundPage.sendMessage(
820 { 878 {
821 type: "subscriptions.listen", 879 type: "subscriptions.listen",
822 filter: ["added", "disabled", "homepage", "removed", "title"] 880 filter: ["added", "disabled", "homepage", "removed", "title"]
823 }); 881 });
824 882
825 window.addEventListener("DOMContentLoaded", onDOMLoaded, false); 883 window.addEventListener("DOMContentLoaded", onDOMLoaded, false);
826 })(); 884 })();
OLDNEW

Powered by Google App Engine
This is Rietveld