OLD | NEW |
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 cr.define('options', function() { | 5 cr.define('options', function() { |
6 const OptionsPage = options.OptionsPage; | 6 const OptionsPage = options.OptionsPage; |
7 | 7 |
8 /** | 8 /** |
9 * Encapsulated handling of the search page. | 9 * Encapsulated handling of the search page. |
10 * @constructor | 10 * @constructor |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
42 self.tab.appendChild(searchField); | 42 self.tab.appendChild(searchField); |
43 | 43 |
44 // Handle search events. (No need to throttle, WebKit's search field | 44 // Handle search events. (No need to throttle, WebKit's search field |
45 // will do that automatically.) | 45 // will do that automatically.) |
46 searchField.onsearch = function(e) { | 46 searchField.onsearch = function(e) { |
47 self.setSearchText_(this.value); | 47 self.setSearchText_(this.value); |
48 }; | 48 }; |
49 }, | 49 }, |
50 | 50 |
51 /** | 51 /** |
| 52 * @inheritDoc |
| 53 */ |
| 54 get sticky() { |
| 55 return true; |
| 56 }, |
| 57 |
| 58 /** |
52 * Called after this page has shown. | 59 * Called after this page has shown. |
53 */ | 60 */ |
54 didShowPage: function() { | 61 didShowPage: function() { |
55 // This method is called by the Options page after all pages have | 62 // This method is called by the Options page after all pages have |
56 // had their visibilty attribute set. At this point we can perform the | 63 // had their visibilty attribute set. At this point we can perform the |
57 // search specific DOM manipulation. | 64 // search specific DOM manipulation. |
58 this.setSearchActive_(true); | 65 this.setSearchActive_(true); |
59 }, | 66 }, |
60 | 67 |
61 /** | 68 /** |
(...skipping 22 matching lines...) Expand all Loading... |
84 this.searchActive_ = active; | 91 this.searchActive_ = active; |
85 if (active) { | 92 if (active) { |
86 // Reset the search criteria, effectively hiding all the sections. | 93 // Reset the search criteria, effectively hiding all the sections. |
87 this.setSearchText_(''); | 94 this.setSearchText_(''); |
88 } else { | 95 } else { |
89 // Just wipe out any active search text since it's no longer relevant. | 96 // Just wipe out any active search text since it's no longer relevant. |
90 $('search-field').value = ''; | 97 $('search-field').value = ''; |
91 } | 98 } |
92 } | 99 } |
93 | 100 |
94 var page, length, childDiv; | 101 var page, key, length, childDiv, i; |
95 var pagesToSearch = this.getSearchablePages_(); | 102 var pagesToSearch = this.getSearchablePages_(); |
96 for (var key in pagesToSearch) { | 103 for (key in pagesToSearch) { |
97 var page = pagesToSearch[key]; | 104 page = pagesToSearch[key]; |
98 | 105 |
99 if (!active) { | 106 if (!active) |
100 page.visible = false; | 107 page.visible = false; |
101 this.unhighlightMatches_(page.tab); | |
102 this.unhighlightMatches_(page.pageDiv); | |
103 } | |
104 | 108 |
105 // Update the visible state of all top-level elements that are not | 109 // Update the visible state of all top-level elements that are not |
106 // sections (ie titles, button strips). We do this before changing | 110 // sections (ie titles, button strips). We do this before changing |
107 // the page visibility to avoid excessive re-draw. | 111 // the page visibility to avoid excessive re-draw. |
108 length = page.pageDiv.childNodes.length; | 112 length = page.pageDiv.childNodes.length; |
109 for (var i = 0; i < length; i++) { | 113 for (i = 0; i < length; i++) { |
110 childDiv = page.pageDiv.childNodes[i]; | 114 childDiv = page.pageDiv.childNodes[i]; |
111 if (childDiv.nodeType == document.ELEMENT_NODE) { | 115 if (childDiv.nodeType == document.ELEMENT_NODE) { |
112 if (active) { | 116 if (active) { |
113 if (childDiv.nodeName.toLowerCase() != 'section') | 117 if (childDiv.nodeName.toLowerCase() != 'section') |
114 childDiv.classList.add('search-hidden'); | 118 childDiv.classList.add('search-hidden'); |
115 } else { | 119 } else { |
116 childDiv.classList.remove('search-hidden'); | 120 childDiv.classList.remove('search-hidden'); |
117 } | 121 } |
118 } | 122 } |
119 } | 123 } |
120 | 124 |
121 // Toggle the visibility state of the page. | |
122 if (active) { | 125 if (active) { |
123 // When search is active, remove the 'hidden' tag. This tag may have | 126 // When search is active, remove the 'hidden' tag. This tag may have |
124 // been added by the OptionsPage. | 127 // been added by the OptionsPage. |
125 page.pageDiv.classList.remove('hidden'); | 128 page.pageDiv.classList.remove('hidden'); |
126 } | 129 } |
127 } | 130 } |
| 131 |
| 132 // After hiding all page content, remove any highlighted matches. |
| 133 if (!active) |
| 134 this.unhighlightMatches_(); |
128 }, | 135 }, |
129 | 136 |
130 /** | 137 /** |
131 * Set the current search criteria. | 138 * Set the current search criteria. |
132 * @param {string} text Search text. | 139 * @param {string} text Search text. |
133 * @private | 140 * @private |
134 */ | 141 */ |
135 setSearchText_: function(text) { | 142 setSearchText_: function(text) { |
136 var foundMatches = false; | 143 var foundMatches = false; |
137 | 144 |
| 145 // Remove any highlighted matches. |
| 146 this.unhighlightMatches_(); |
| 147 |
138 // Generate search text by applying lowercase and escaping any characters | 148 // Generate search text by applying lowercase and escaping any characters |
139 // that would be problematic for regular expressions. | 149 // that would be problematic for regular expressions. |
140 var searchText = | 150 var searchText = |
141 text.toLowerCase().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); | 151 text.toLowerCase().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); |
142 | 152 |
143 // Generate a regular expression and replace string for hilighting | 153 // Generate a regular expression and replace string for hilighting |
144 // search terms. | 154 // search terms. |
145 var regEx = new RegExp('(\\b' + searchText + ')', 'ig'); | 155 var regEx = new RegExp('(\\b' + searchText + ')', 'ig'); |
146 var replaceString = '<span class="search-highlighted">$1</span>'; | 156 var replaceString = '<span class="search-highlighted">$1</span>'; |
147 | 157 |
148 // Initialize all sections. If the search string matches a title page, | 158 // Initialize all sections. If the search string matches a title page, |
149 // show sections for that page. | 159 // show sections for that page. |
| 160 var page, key, pageMatch, childDiv, i; |
150 var pagesToSearch = this.getSearchablePages_(); | 161 var pagesToSearch = this.getSearchablePages_(); |
151 for (var key in pagesToSearch) { | 162 for (key in pagesToSearch) { |
152 var page = pagesToSearch[key]; | 163 page = pagesToSearch[key]; |
153 this.unhighlightMatches_(page.tab); | 164 pageMatch = false; |
154 this.unhighlightMatches_(page.pageDiv); | |
155 var pageMatch = false; | |
156 if (searchText.length) { | 165 if (searchText.length) { |
157 pageMatch = this.performReplace_(regEx, replaceString, page.tab); | 166 pageMatch = this.performReplace_(regEx, replaceString, page.tab); |
158 } | 167 } |
159 if (pageMatch) | 168 if (pageMatch) |
160 foundMatches = true; | 169 foundMatches = true; |
161 for (var i = 0; i < page.pageDiv.childNodes.length; i++) { | 170 for (i = 0; i < page.pageDiv.childNodes.length; i++) { |
162 var childDiv = page.pageDiv.childNodes[i]; | 171 childDiv = page.pageDiv.childNodes[i]; |
163 if (childDiv.nodeType == document.ELEMENT_NODE && | 172 if (childDiv.nodeType == document.ELEMENT_NODE && |
164 childDiv.nodeName.toLowerCase() == 'section') { | 173 childDiv.nodeName.toLowerCase() == 'section') { |
165 if (pageMatch) { | 174 if (pageMatch) { |
166 childDiv.classList.remove('search-hidden'); | 175 childDiv.classList.remove('search-hidden'); |
167 } else { | 176 } else { |
168 childDiv.classList.add('search-hidden'); | 177 childDiv.classList.add('search-hidden'); |
169 } | 178 } |
170 } | 179 } |
171 } | 180 } |
172 } | 181 } |
173 | 182 |
174 // Search all sections for anchored string matches. | |
175 if (searchText.length) { | 183 if (searchText.length) { |
176 for (var key in pagesToSearch) { | 184 // Search all sub-pages, generating an array of top-level sections that |
177 var page = pagesToSearch[key]; | 185 // we need to make visible. |
178 for (var i = 0; i < page.pageDiv.childNodes.length; i++) { | 186 var subPagesToSearch = this.getSearchableSubPages_(); |
179 var childDiv = page.pageDiv.childNodes[i]; | 187 var control, node; |
| 188 for (key in subPagesToSearch) { |
| 189 page = subPagesToSearch[key]; |
| 190 if (this.performReplace_(regEx, replaceString, page.pageDiv)) { |
| 191 section = page.associatedSection; |
| 192 if (section) |
| 193 section.classList.remove('search-hidden'); |
| 194 controls = page.associatedControls; |
| 195 if (controls) { |
| 196 // TODO(csilv): highlight each control. |
| 197 } |
| 198 |
| 199 foundMatches = true; |
| 200 } |
| 201 } |
| 202 |
| 203 // Search all top-level sections for anchored string matches. |
| 204 for (key in pagesToSearch) { |
| 205 page = pagesToSearch[key]; |
| 206 for (i = 0; i < page.pageDiv.childNodes.length; i++) { |
| 207 childDiv = page.pageDiv.childNodes[i]; |
180 if (childDiv.nodeType == document.ELEMENT_NODE && | 208 if (childDiv.nodeType == document.ELEMENT_NODE && |
181 childDiv.nodeName.toLowerCase() == 'section' && | 209 childDiv.nodeName.toLowerCase() == 'section' && |
182 this.performReplace_(regEx, replaceString, childDiv)) { | 210 this.performReplace_(regEx, replaceString, childDiv)) { |
183 childDiv.classList.remove('search-hidden'); | 211 childDiv.classList.remove('search-hidden'); |
184 foundMatches = true; | 212 foundMatches = true; |
185 } | 213 } |
186 } | 214 } |
187 } | 215 } |
188 } | 216 } |
189 | 217 |
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
245 tmp.parentNode.removeChild(tmp); | 273 tmp.parentNode.removeChild(tmp); |
246 } else { | 274 } else { |
247 node = walker.nextNode(); | 275 node = walker.nextNode(); |
248 } | 276 } |
249 } | 277 } |
250 | 278 |
251 return found; | 279 return found; |
252 }, | 280 }, |
253 | 281 |
254 /** | 282 /** |
255 * Removes all search highlight tags from a container element. | 283 * Removes all search highlight tags from the document. |
256 * @param {Element} element An HTML container element. | |
257 * @private | 284 * @private |
258 */ | 285 */ |
259 unhighlightMatches_: function(element) { | 286 unhighlightMatches_: function() { |
260 // Find all search highlight elements. | 287 // Find all search highlight elements. |
261 var elements = element.querySelectorAll('.search-highlighted'); | 288 var elements = document.querySelectorAll('.search-highlighted'); |
262 | 289 |
263 // For each element, remove the highlighting. | 290 // For each element, remove the highlighting. |
264 var node, parent, i, length = elements.length; | 291 var node, parent, i, length = elements.length; |
265 for (i = 0; i < length; i++) { | 292 for (i = 0; i < length; i++) { |
266 node = elements[i]; | 293 node = elements[i]; |
267 parent = node.parentNode; | 294 parent = node.parentNode; |
268 | 295 |
269 // Replace the highlight element with the first child (the text node). | 296 // Replace the highlight element with the first child (the text node). |
270 parent.replaceChild(node.firstChild, node); | 297 parent.replaceChild(node.firstChild, node); |
271 | 298 |
272 // Normalize the parent so that multiple text nodes will be combined. | 299 // Normalize the parent so that multiple text nodes will be combined. |
273 parent.normalize(); | 300 parent.normalize(); |
274 } | 301 } |
275 }, | 302 }, |
276 | 303 |
277 /** | 304 /** |
278 * Builds a list of pages to search. Omits the search page. | 305 * Builds a list of top-level pages to search. Omits the search page and |
| 306 * all sub-pages. |
279 * @returns {Array} An array of pages to search. | 307 * @returns {Array} An array of pages to search. |
280 * @private | 308 * @private |
281 */ | 309 */ |
282 getSearchablePages_: function() { | 310 getSearchablePages_: function() { |
283 var pages = []; | 311 var name, page, pages = []; |
284 for (var name in OptionsPage.registeredPages) { | 312 for (name in OptionsPage.registeredPages) { |
285 if (name != this.name) | 313 if (name != this.name) { |
286 pages.push(OptionsPage.registeredPages[name]); | 314 page = OptionsPage.registeredPages[name]; |
| 315 if (!page.parentPage) |
| 316 pages.push(page); |
| 317 } |
| 318 } |
| 319 return pages; |
| 320 }, |
| 321 |
| 322 /** |
| 323 * Builds a list of sub-pages (and overlay pages) to search. Ignore pages |
| 324 * that have no associated controls. |
| 325 * @returns {Array} An array of pages to search. |
| 326 * @private |
| 327 */ |
| 328 getSearchableSubPages_: function() { |
| 329 var name, pageInfo, page, pages = []; |
| 330 for (name in OptionsPage.registeredPages) { |
| 331 page = OptionsPage.registeredPages[name]; |
| 332 if (page.parentPage && page.associatedSection) |
| 333 pages.push(page); |
| 334 } |
| 335 for (name in OptionsPage.registeredOverlayPages) { |
| 336 page = OptionsPage.registeredOverlayPages[name]; |
| 337 if (page.associatedSection && page.pageDiv != undefined) |
| 338 pages.push(page); |
287 } | 339 } |
288 return pages; | 340 return pages; |
289 } | 341 } |
290 }; | 342 }; |
291 | 343 |
292 // Export | 344 // Export |
293 return { | 345 return { |
294 SearchPage: SearchPage | 346 SearchPage: SearchPage |
295 }; | 347 }; |
296 | 348 |
297 }); | 349 }); |
OLD | NEW |