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: Created Dec. 16, 2015, 1:31 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
« no previous file with comments | « options.html ('k') | skin/options.css » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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.addItems = function() 34 Collection.prototype.addItems = function()
34 { 35 {
35 var length = Array.prototype.push.apply(this.items, arguments); 36 var length = Array.prototype.push.apply(this.items, arguments);
36 if (length == 0) 37 if (length == 0)
37 return; 38 return;
38 39
39 this.items.sort(function(a, b) 40 this.items.sort(function(a, b)
40 { 41 {
41 var aValue = (a.title || a.text || a.url).toLowerCase(); 42 var aValue = (a.title || a.text || a.url).toLowerCase();
42 var bValue = (b.title || b.text || b.url).toLowerCase(); 43 var bValue = (b.title || b.text || b.url).toLowerCase();
43 return aValue.localeCompare(bValue); 44 return aValue.localeCompare(bValue);
44 }); 45 });
45 46
46 for (var j = 0; j < this.details.length; j++) 47 for (var j = 0; j < this.details.length; j++)
47 { 48 {
48 var table = E(this.details[j].id); 49 var table = E(this.details[j].id);
49 var template = table.querySelector("template"); 50 var template = table.querySelector("template");
50 for (var i = 0; i < arguments.length; i++) 51 for (var i = 0; i < arguments.length; i++)
51 { 52 {
52 var item = arguments[i]; 53 var item = arguments[i];
53 var text = item.title || item.url || item.text;
54 var listItem = document.createElement("li"); 54 var listItem = document.createElement("li");
55 listItem.appendChild(document.importNode(template.content, true)); 55 listItem.appendChild(document.importNode(template.content, true));
56 listItem.setAttribute("data-access", item.url || item.text); 56 listItem.setAttribute("data-access", item.url || item.text);
57 listItem.querySelector(".display").textContent = text;
58 if (text)
59 listItem.setAttribute("data-search", text.toLowerCase());
60 57
58 var labelId = "label-" + (++maxLabelId);
59 var label = listItem.querySelector(".display");
60 label.setAttribute("id", labelId);
61 var control = listItem.querySelector(".control"); 61 var control = listItem.querySelector(".control");
62 if (control) 62 if (control)
63 { 63 {
64 // We use aria-labelledby to avoid triggering the control when
65 // interacting with the label
66 control.setAttribute("aria-labelledby", labelId);
64 control.addEventListener("click", this.details[j].onClick, false); 67 control.addEventListener("click", this.details[j].onClick, false);
65 control.checked = item.disabled == false;
66 } 68 }
67 69
68 if (table.hasChildNodes()) 70 if (table.hasChildNodes())
69 table.insertBefore(listItem, table.childNodes[this.items.indexOf(item) ]); 71 table.insertBefore(listItem, table.childNodes[this.items.indexOf(item) ]);
70 else 72 else
71 table.appendChild(listItem); 73 table.appendChild(listItem);
74 this.updateItem(item);
72 } 75 }
73 } 76 }
74 return length; 77 return length;
75 }; 78 };
76 79
77 Collection.prototype.removeItem = function(item) 80 Collection.prototype.removeItem = function(item)
78 { 81 {
79 var index = this.items.indexOf(item); 82 var index = this.items.indexOf(item);
80 if (index == -1) 83 if (index == -1)
81 return; 84 return;
82 85
83 this.items.splice(index, 1); 86 this.items.splice(index, 1);
84 var access = (item.url || item.text).replace(/'/g, "\\'"); 87 var access = (item.url || item.text).replace(/'/g, "\\'");
85 for (var i = 0; i < this.details.length; i++) 88 for (var i = 0; i < this.details.length; i++)
86 { 89 {
87 var table = E(this.details[i].id); 90 var table = E(this.details[i].id);
88 var element = table.querySelector("[data-access='" + access + "']"); 91 var element = table.querySelector("[data-access='" + access + "']");
92
93 // Element gets removed so make sure to handle focus appropriately
94 var control = element.querySelector(".control");
95 if (control && control == document.activeElement)
96 {
97 if (!focusNextElement(element.parentElement, control))
98 {
99 // Fall back to next focusable element within same tab
100 var tab = element.parentElement;
101 while (true)
102 {
103 if (tab.classList.contains("tab-content"))
104 break;
105
106 tab = tab.parentElement;
107 if (!tab)
108 {
109 tab = document;
110 break;
111 }
112 }
113 focusNextElement(tab, control);
114 }
115 }
116
89 element.parentElement.removeChild(element); 117 element.parentElement.removeChild(element);
90 } 118 }
91 }; 119 };
92 120
121 Collection.prototype.updateItem = function(item)
122 {
123 var access = (item.url || item.text).replace(/'/g, "\\'");
124 for (var i = 0; i < this.details.length; i++)
125 {
126 var table = E(this.details[i].id);
127 var element = table.querySelector("[data-access='" + access + "']");
128 if (!element)
129 continue;
130
131 var text = item.title || item.url || item.text;
132 element.querySelector(".display").textContent = text;
133 if (text)
134 element.setAttribute("data-search", text.toLowerCase());
135 var control = element.querySelector(".control[role='checkbox']");
136 if (control)
137 control.setAttribute("aria-checked", item.disabled == false);
138 }
139 };
140
93 Collection.prototype.clearAll = function() 141 Collection.prototype.clearAll = function()
94 { 142 {
95 for (var i = 0; i < this.details.length; i++) 143 for (var i = 0; i < this.details.length; i++)
96 { 144 {
97 var table = E(this.details[i].id); 145 var table = E(this.details[i].id);
98 var template = table.querySelector("template"); 146 var template = table.querySelector("template");
99 table.innerHTML = ""; 147 table.innerHTML = "";
100 table.appendChild(template); 148 table.appendChild(template);
101 } 149 }
102 this.items.length = 0; 150 this.items.length = 0;
103 }; 151 };
104 152
153 function focusNextElement(container, currentElement)
154 {
155 var focusables = container.querySelectorAll("a, button, .control");
156 focusables = Array.prototype.slice.call(focusables);
157 var index = focusables.indexOf(currentElement);
158 if (index + 1 < focusables.length)
159 index += 1;
160 else if (index < focusables.length)
161 index -= 1;
162
163 var nextElement = focusables[index];
164 if (!nextElement)
165 return false;
166
167 nextElement.focus();
168 return true;
169 }
170
105 function onToggleSubscriptionClick(e) 171 function onToggleSubscriptionClick(e)
106 { 172 {
107 e.preventDefault(); 173 e.preventDefault();
108 var subscriptionUrl = e.target.parentNode.getAttribute("data-access"); 174 var checkbox = e.target;
109 if (!e.target.checked) 175 var subscriptionUrl = checkbox.parentElement.getAttribute("data-access");
176 if (checkbox.getAttribute("aria-checked") == "true")
110 { 177 {
111 ext.backgroundPage.sendMessage( 178 ext.backgroundPage.sendMessage(
112 { 179 {
113 type: "subscriptions.remove", 180 type: "subscriptions.remove",
114 url: subscriptionUrl 181 url: subscriptionUrl
115 }); 182 });
116 } 183 }
117 else 184 else
118 addEnableSubscription(subscriptionUrl); 185 addEnableSubscription(subscriptionUrl);
119 } 186 }
120 187
121 function onAddLanguageSubscriptionClick(e) 188 function onAddLanguageSubscriptionClick(e)
122 { 189 {
123 e.preventDefault(); 190 e.preventDefault();
124 var url = this.parentNode.getAttribute("data-access"); 191 var url = this.parentNode.getAttribute("data-access");
125 addEnableSubscription(url); 192 addEnableSubscription(url);
(...skipping 22 matching lines...) Expand all
148 id: "blocking-languages-table", 215 id: "blocking-languages-table",
149 onClick: onToggleSubscriptionClick 216 onClick: onToggleSubscriptionClick
150 }, 217 },
151 { 218 {
152 id: "blocking-languages-dialog-table" 219 id: "blocking-languages-dialog-table"
153 } 220 }
154 ]); 221 ]);
155 collections.allLangs = new Collection( 222 collections.allLangs = new Collection(
156 [ 223 [
157 { 224 {
158 id: "all-lang-table", 225 id: "all-lang-table",
159 onClick: onAddLanguageSubscriptionClick 226 onClick: onAddLanguageSubscriptionClick
160 } 227 }
161 ]); 228 ]);
162 collections.acceptableAds = new Collection( 229 collections.acceptableAds = new Collection(
163 [ 230 [
164 { 231 {
165 id: "acceptableads-table", 232 id: "acceptableads-table",
166 onClick: onToggleSubscriptionClick 233 onClick: onToggleSubscriptionClick
167 } 234 }
168 ]); 235 ]);
169 collections.custom = new Collection( 236 collections.custom = new Collection(
170 [ 237 [
171 { 238 {
172 id: "custom-list-table", 239 id: "custom-list-table",
173 onClick: onToggleSubscriptionClick 240 onClick: onToggleSubscriptionClick
174 } 241 }
175 ]); 242 ]);
176 collections.whitelist = new Collection( 243 collections.whitelist = new Collection(
177 [ 244 [
178 { 245 {
179 id: "whitelisting-table", 246 id: "whitelisting-table",
180 onClick: onRemoveFilterClick 247 onClick: onRemoveFilterClick
181 } 248 }
182 ]); 249 ]);
183 collections.customFilters = new Collection( 250 collections.customFilters = new Collection(
184 [ 251 [
185 { 252 {
186 id: "custom-filters-table" 253 id: "custom-filters-table"
187 } 254 }
188 ]); 255 ]);
189 256
190 function updateSubscription(subscription) 257 function updateSubscription(subscription)
191 { 258 {
192 var subscriptionUrl = subscription.url; 259 var subscriptionUrl = subscription.url;
193 var knownSubscription = subscriptionsMap[subscriptionUrl]; 260 var knownSubscription = subscriptionsMap[subscriptionUrl];
194 if (knownSubscription) 261 if (knownSubscription)
195 knownSubscription.disabled = subscription.disabled; 262 knownSubscription.disabled = subscription.disabled;
196 else 263 else
197 { 264 {
198 getAcceptableAdsURL(function(acceptableAdsUrl) 265 getAcceptableAdsURL(function(acceptableAdsUrl)
199 { 266 {
200 function onObjectChanged() 267 function onObjectChanged()
201 { 268 {
202 var access = (subscriptionUrl || subscription.text).replace(/'/g, "\\' "); 269 for (var i in collections)
203 var elements = document.querySelectorAll("[data-access='" + access + " ']"); 270 collections[i].updateItem(subscription);
204 for (var i = 0; i < elements.length; i++) 271
272 var recommendation = recommendationsMap[subscriptionUrl];
273 if (recommendation && recommendation.type == "ads")
205 { 274 {
206 var element = elements[i]; 275 if (subscription.disabled == false)
207 var control = element.querySelector(".control");
208 if (control.localName == "input")
209 control.checked = subscription.disabled == false;
210 if (subscriptionUrl in recommendationsMap)
211 { 276 {
212 var recommendation = recommendationsMap[subscriptionUrl]; 277 collections.allLangs.removeItem(subscription);
213 if (recommendation.type == "ads") 278 collections.langs.addItems(subscription);
214 { 279 }
215 if (subscription.disabled == false) 280 else
216 { 281 {
217 collections.allLangs.removeItem(subscription); 282 collections.allLangs.addItems(subscription);
218 collections.langs.addItems(subscription); 283 collections.langs.removeItem(subscription);
219 }
220 else
221 {
222 collections.allLangs.addItems(subscription);
223 collections.langs.removeItem(subscription);
224 }
225 }
226 } 284 }
227 } 285 }
228 } 286 }
229 287
230 if (!Object.observe) 288 if (!Object.observe)
231 { 289 {
232 // Currently only "disabled" property of subscription used for observa tion 290 // Currently only "disabled" property of subscription used for observa tion
233 // but with Advanced tab implementation we should also add more proper ties. 291 // but with Advanced tab implementation we should also add more proper ties.
234 ["disabled"].forEach(function(property) 292 ["disabled"].forEach(function(property)
235 { 293 {
(...skipping 554 matching lines...) Expand 10 before | Expand all | Expand 10 after
790 filter: ["added", "loaded", "removed"] 848 filter: ["added", "loaded", "removed"]
791 }); 849 });
792 ext.backgroundPage.sendMessage( 850 ext.backgroundPage.sendMessage(
793 { 851 {
794 type: "subscriptions.listen", 852 type: "subscriptions.listen",
795 filter: ["added", "disabled", "homepage", "removed", "title"] 853 filter: ["added", "disabled", "homepage", "removed", "title"]
796 }); 854 });
797 855
798 window.addEventListener("DOMContentLoaded", onDOMLoaded, false); 856 window.addEventListener("DOMContentLoaded", onDOMLoaded, false);
799 })(); 857 })();
OLDNEW
« 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