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 Jan. 19, 2016, 3:10 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') | skin/options.css » ('J')
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._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 listItem.querySelector(".display").setAttribute("id", labelId);
75 var control = listItem.querySelector(".control"); 74 var control = listItem.querySelector(".control");
76 if (control) 75 if (control)
77 { 76 {
77 // We use aria-labelledby to avoid triggering the control when
78 // interacting with the label
79 control.setAttribute("aria-labelledby", labelId);
78 control.addEventListener("click", this.details[j].onClick, false); 80 control.addEventListener("click", this.details[j].onClick, false);
79 control.checked = item.disabled == false;
80 } 81 }
81 82
82 this._setEmpty(table, null); 83 this._setEmpty(table, null);
83 if (table.hasChildNodes()) 84 if (table.hasChildNodes())
84 table.insertBefore(listItem, table.childNodes[this.items.indexOf(item) ]); 85 table.insertBefore(listItem, table.childNodes[this.items.indexOf(item) ]);
85 else 86 else
86 table.appendChild(listItem); 87 table.appendChild(listItem);
88 this.updateItem(item);
87 } 89 }
88 } 90 }
89 return length; 91 return length;
90 }; 92 };
91 93
92 Collection.prototype.removeItem = function(item) 94 Collection.prototype.removeItem = function(item)
93 { 95 {
94 var index = this.items.indexOf(item); 96 var index = this.items.indexOf(item);
95 if (index == -1) 97 if (index == -1)
96 return; 98 return;
97 99
98 this.items.splice(index, 1); 100 this.items.splice(index, 1);
99 for (var i = 0; i < this.details.length; i++) 101 for (var i = 0; i < this.details.length; i++)
100 { 102 {
101 var table = E(this.details[i].id); 103 var table = E(this.details[i].id);
102 var element = table.childNodes[index]; 104 var element = table.childNodes[index];
105
106 // Element gets removed so make sure to handle focus appropriately
107 var control = element.querySelector(".control");
108 if (control && control == document.activeElement)
109 {
110 if (!focusNextElement(element.parentElement, control))
111 {
112 // Fall back to next focusable element within same tab or dialog
113 var tab = element.parentElement;
saroyanm 2016/01/25 14:45:43 Detail: This is not only tab already, so maybe mak
Thomas Greiner 2016/01/25 18:04:21 Done.
114 while (tab)
saroyanm 2016/01/25 14:45:43 What if the dialog where the collection is impleme
Thomas Greiner 2016/01/25 18:04:21 The main challenge with focus management is that y
saroyanm 2016/01/26 18:40:12 Fare enough.
115 {
116 if (tab.classList.contains("tab-content")
117 || tab.classList.contains("dialog-content"))
saroyanm 2016/01/25 14:45:43 I think we need to make it obvious whether it's so
Thomas Greiner 2016/01/25 18:04:21 We could put this into a separate function that's
118 break;
119
120 tab = tab.parentElement;
121 }
122 focusNextElement(tab || document, control);
123 }
124 }
125
103 element.parentElement.removeChild(element); 126 element.parentElement.removeChild(element);
104 if (this.items.length == 0) 127 if (this.items.length == 0)
105 this._setEmpty(table, this.details[i].emptyText); 128 this._setEmpty(table, this.details[i].emptyText);
106 } 129 }
107 }; 130 };
108 131
132 Collection.prototype.updateItem = function(item)
133 {
134 var access = (item.url || item.text).replace(/'/g, "\\'");
135 for (var i = 0; i < this.details.length; i++)
136 {
137 var table = E(this.details[i].id);
138 var element = table.querySelector("[data-access='" + access + "']");
139 if (!element)
140 continue;
141
142 var text = item.title || item.url || item.text;
143 element.querySelector(".display").textContent = text;
144 if (text)
145 element.setAttribute("data-search", text.toLowerCase());
146 var control = element.querySelector(".control[role='checkbox']");
147 if (control)
148 control.setAttribute("aria-checked", item.disabled == false);
149 }
150 };
151
109 Collection.prototype.clearAll = function() 152 Collection.prototype.clearAll = function()
110 { 153 {
111 this.items = []; 154 this.items = [];
112 for (var i = 0; i < this.details.length; i++) 155 for (var i = 0; i < this.details.length; i++)
113 { 156 {
114 var table = E(this.details[i].id); 157 var table = E(this.details[i].id);
115 var template = table.querySelector("template"); 158 var template = table.querySelector("template");
116 table.innerHTML = ""; 159 table.innerHTML = "";
117 table.appendChild(template); 160 table.appendChild(template);
118 this._setEmpty(table, this.details[i].emptyText); 161 this._setEmpty(table, this.details[i].emptyText);
119 } 162 }
120 }; 163 };
121 164
165 function focusNextElement(container, currentElement)
166 {
167 var focusables = container.querySelectorAll("a, button, input, .control");
168 focusables = Array.prototype.slice.call(focusables);
169 var index = focusables.indexOf(currentElement);
170 index += (index == focusables.length - 1) ? -1 : 1;
171
172 var nextElement = focusables[index];
173 if (!nextElement)
174 return false;
175
176 nextElement.focus();
177 return true;
178 }
179
122 function onToggleSubscriptionClick(e) 180 function onToggleSubscriptionClick(e)
123 { 181 {
124 e.preventDefault(); 182 e.preventDefault();
125 var subscriptionUrl = e.target.parentNode.getAttribute("data-access"); 183 var checkbox = e.target;
126 if (!e.target.checked) 184 var subscriptionUrl = checkbox.parentElement.getAttribute("data-access");
185 if (checkbox.getAttribute("aria-checked") == "true")
127 { 186 {
128 ext.backgroundPage.sendMessage({ 187 ext.backgroundPage.sendMessage({
129 type: "subscriptions.remove", 188 type: "subscriptions.remove",
130 url: subscriptionUrl 189 url: subscriptionUrl
131 }); 190 });
132 } 191 }
133 else 192 else
134 addEnableSubscription(subscriptionUrl); 193 addEnableSubscription(subscriptionUrl);
135 } 194 }
136 195
(...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after
213 var subscriptionUrl = subscription.url; 272 var subscriptionUrl = subscription.url;
214 var knownSubscription = subscriptionsMap[subscriptionUrl]; 273 var knownSubscription = subscriptionsMap[subscriptionUrl];
215 if (knownSubscription) 274 if (knownSubscription)
216 knownSubscription.disabled = subscription.disabled; 275 knownSubscription.disabled = subscription.disabled;
217 else 276 else
218 { 277 {
219 getAcceptableAdsURL(function(acceptableAdsUrl) 278 getAcceptableAdsURL(function(acceptableAdsUrl)
220 { 279 {
221 function onObjectChanged() 280 function onObjectChanged()
222 { 281 {
223 var access = (subscriptionUrl || subscription.text).replace(/'/g, "\\' "); 282 for (var i in collections)
224 var elements = document.querySelectorAll("[data-access='" + access + " ']"); 283 collections[i].updateItem(subscription);
225 for (var i = 0; i < elements.length; i++) 284
285 var recommendation = recommendationsMap[subscriptionUrl];
286 if (recommendation && recommendation.type == "ads")
226 { 287 {
227 var element = elements[i]; 288 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 { 289 {
233 var recommendation = recommendationsMap[subscriptionUrl]; 290 collections.allLangs.removeItem(subscription);
234 if (recommendation.type == "ads") 291 collections.langs.addItems(subscription);
235 { 292 }
236 if (subscription.disabled == false) 293 else
237 { 294 {
238 collections.allLangs.removeItem(subscription); 295 collections.allLangs.addItems(subscription);
239 collections.langs.addItems(subscription); 296 collections.langs.removeItem(subscription);
240 }
241 else
242 {
243 collections.allLangs.addItems(subscription);
244 collections.langs.removeItem(subscription);
245 }
246 }
247 } 297 }
248 } 298 }
249 } 299 }
250 300
251 if (!Object.observe) 301 if (!Object.observe)
252 { 302 {
253 // Currently only "disabled" property of subscription used for observa tion 303 // Currently only "disabled" property of subscription used for observa tion
254 // but with Advanced tab implementation we should also add more proper ties. 304 // but with Advanced tab implementation we should also add more proper ties.
255 ["disabled"].forEach(function(property) 305 ["disabled"].forEach(function(property)
256 { 306 {
(...skipping 560 matching lines...) Expand 10 before | Expand all | Expand 10 after
817 filter: ["added", "loaded", "removed"] 867 filter: ["added", "loaded", "removed"]
818 }); 868 });
819 ext.backgroundPage.sendMessage( 869 ext.backgroundPage.sendMessage(
820 { 870 {
821 type: "subscriptions.listen", 871 type: "subscriptions.listen",
822 filter: ["added", "disabled", "homepage", "removed", "title"] 872 filter: ["added", "disabled", "homepage", "removed", "title"]
823 }); 873 });
824 874
825 window.addEventListener("DOMContentLoaded", onDOMLoaded, false); 875 window.addEventListener("DOMContentLoaded", onDOMLoaded, false);
826 })(); 876 })();
OLDNEW
« no previous file with comments | « options.html ('k') | skin/options.css » ('j') | skin/options.css » ('J')

Powered by Google App Engine
This is Rietveld