Chromium Code Reviews| 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 a search bubble. | |
| 10 * @constructor | |
| 11 */ | |
| 12 function SearchBubble(text) { | |
| 13 var el = cr.doc.createElement('div'); | |
| 14 SearchBubble.decorate(el); | |
| 15 el.textContent = text; | |
| 16 return el; | |
| 17 } | |
| 18 | |
| 19 SearchBubble.decorate = function(el) { | |
| 20 el.__proto__ = SearchBubble.prototype; | |
| 21 el.decorate(); | |
| 22 }; | |
| 23 | |
| 24 SearchBubble.prototype = { | |
| 25 __proto__: HTMLDivElement.prototype, | |
| 26 | |
| 27 decorate: function() { | |
| 28 this.className = 'search-bubble'; | |
| 29 | |
| 30 // We create a timer to periodically update the position of the bubble. | |
| 31 // While this isn't all that desirable, it's the only sure-fire way of | |
| 32 // making sure the bubbles stay in the correct location as sections | |
| 33 // may dynamically change size at any time. | |
| 34 var self = this; | |
| 35 this.intervalId = setInterval(this.updatePosition.bind(this), 250); | |
| 36 }, | |
| 37 | |
| 38 /** | |
| 39 * Clear the interval timer and remove the element from the page. | |
| 40 */ | |
| 41 dispose: function() { | |
| 42 clearInterval(this.intervalId); | |
| 43 | |
| 44 var parent = this.parentNode; | |
| 45 if (parent) | |
| 46 parent.removeChild(this); | |
| 47 }, | |
| 48 | |
| 49 /** | |
| 50 * Update the position of the bubble. Called at creation time and then | |
| 51 * periodically while the bubble remains visible. | |
| 52 */ | |
| 53 updatePosition: function() { | |
| 54 // This bubble is 'owned' by the next sibling. | |
| 55 var owner = this.nextSibling; | |
| 56 | |
| 57 // If there isn't an offset parent, we have nothing to do. | |
| 58 if (!owner.offsetParent) | |
| 59 return; | |
| 60 | |
| 61 // Position the bubble below the location of the owner. | |
| 62 var left = owner.offsetLeft + owner.offsetWidth / 2 - | |
| 63 this.offsetWidth / 2; | |
| 64 var top = owner.offsetTop + owner.offsetHeight; | |
|
arv (Not doing code reviews)
2011/01/21 23:19:31
How do we handle the case where the bubble is disp
csilv
2011/01/24 19:17:34
We don't do anything special, the bubble will be p
| |
| 65 | |
| 66 // Update the position in the CSS. Cache the last values for | |
| 67 // best performance. | |
| 68 if (left != this.lastLeft) { | |
| 69 this.style.left = left + 'px'; | |
| 70 this.lastLeft = left; | |
| 71 } | |
| 72 if (top != this.lastTop) { | |
| 73 this.style.top = top + 'px'; | |
| 74 this.lastTop = top; | |
| 75 } | |
| 76 } | |
| 77 } | |
| 78 | |
| 79 /** | |
| 9 * Encapsulated handling of the search page. | 80 * Encapsulated handling of the search page. |
| 10 * @constructor | 81 * @constructor |
| 11 */ | 82 */ |
| 12 function SearchPage() { | 83 function SearchPage() { |
| 13 OptionsPage.call(this, 'search', templateData.searchPage, 'searchPage'); | 84 OptionsPage.call(this, 'search', templateData.searchPage, 'searchPage'); |
| 14 this.searchActive = false; | 85 this.searchActive = false; |
| 15 } | 86 } |
| 16 | 87 |
| 17 cr.addSingletonGetter(SearchPage); | 88 cr.addSingletonGetter(SearchPage); |
| 18 | 89 |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 101 var pagesToSearch = this.getSearchablePages_(); | 172 var pagesToSearch = this.getSearchablePages_(); |
| 102 for (var key in pagesToSearch) { | 173 for (var key in pagesToSearch) { |
| 103 var page = pagesToSearch[key]; | 174 var page = pagesToSearch[key]; |
| 104 | 175 |
| 105 if (!active) | 176 if (!active) |
| 106 page.visible = false; | 177 page.visible = false; |
| 107 | 178 |
| 108 // Update the visible state of all top-level elements that are not | 179 // Update the visible state of all top-level elements that are not |
| 109 // sections (ie titles, button strips). We do this before changing | 180 // sections (ie titles, button strips). We do this before changing |
| 110 // the page visibility to avoid excessive re-draw. | 181 // the page visibility to avoid excessive re-draw. |
| 111 var length = page.pageDiv.childNodes.length; | 182 var length = page.pageDiv.children.length; |
| 112 var childDiv; | 183 var childDiv; |
| 113 for (var i = 0; i < length; i++) { | 184 for (var i = 0; i < length; i++) { |
|
arv (Not doing code reviews)
2011/01/21 23:19:31
The following pattern is slightly easier on the ey
csilv
2011/01/24 19:17:34
Done.
| |
| 114 childDiv = page.pageDiv.childNodes[i]; | 185 childDiv = page.pageDiv.children[i]; |
| 115 if (childDiv.nodeType == document.ELEMENT_NODE) { | 186 if (active) { |
| 116 if (active) { | 187 if (childDiv.nodeName != 'SECTION') |
|
arv (Not doing code reviews)
2011/01/21 23:19:31
Use tagName instead
csilv
2011/01/24 19:17:34
Done.
| |
| 117 if (childDiv.nodeName.toLowerCase() != 'section') | 188 childDiv.classList.add('search-hidden'); |
| 118 childDiv.classList.add('search-hidden'); | 189 } else { |
| 119 } else { | 190 childDiv.classList.remove('search-hidden'); |
| 120 childDiv.classList.remove('search-hidden'); | |
| 121 } | |
| 122 } | 191 } |
| 123 } | 192 } |
| 124 | 193 |
| 125 if (active) { | 194 if (active) { |
| 126 // When search is active, remove the 'hidden' tag. This tag may have | 195 // When search is active, remove the 'hidden' tag. This tag may have |
| 127 // been added by the OptionsPage. | 196 // been added by the OptionsPage. |
| 128 page.pageDiv.classList.remove('hidden'); | 197 page.pageDiv.classList.remove('hidden'); |
| 129 } | 198 } |
| 130 } | 199 } |
| 131 | 200 |
| 132 // After hiding all page content, remove any highlighted matches. | 201 // After hiding all page content, remove any search results. |
| 133 if (!active) | 202 if (!active) { |
| 134 this.unhighlightMatches_(); | 203 this.unhighlightMatches_(); |
| 204 this.removeSearchBubbles_(); | |
| 205 } | |
| 135 }, | 206 }, |
| 136 | 207 |
| 137 /** | 208 /** |
| 138 * Set the current search criteria. | 209 * Set the current search criteria. |
| 139 * @param {string} text Search text. | 210 * @param {string} text Search text. |
| 140 * @private | 211 * @private |
| 141 */ | 212 */ |
| 142 setSearchText_: function(text) { | 213 setSearchText_: function(text) { |
| 143 var foundMatches = false; | 214 var foundMatches = false; |
| 215 var bubbleControls = []; | |
| 144 | 216 |
| 145 // Remove any highlighted matches. | 217 // Remove any prior search results. |
| 146 this.unhighlightMatches_(); | 218 this.unhighlightMatches_(); |
| 219 this.removeSearchBubbles_(); | |
| 147 | 220 |
| 148 // Generate search text by applying lowercase and escaping any characters | 221 // Generate search text by applying lowercase and escaping any characters |
| 149 // that would be problematic for regular expressions. | 222 // that would be problematic for regular expressions. |
| 150 var searchText = | 223 var searchText = |
| 151 text.toLowerCase().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); | 224 text.toLowerCase().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); |
| 152 | 225 |
| 153 // Generate a regular expression and replace string for hilighting | 226 // Generate a regular expression and replace string for hilighting |
| 154 // search terms. | 227 // search terms. |
| 155 var regEx = new RegExp('(\\b' + searchText + ')', 'ig'); | 228 var regEx = new RegExp('(\\b' + searchText + ')', 'ig'); |
| 156 var replaceString = '<span class="search-highlighted">$1</span>'; | 229 var replaceString = '<span class="search-highlighted">$1</span>'; |
| 157 | 230 |
| 158 // Initialize all sections. If the search string matches a title page, | 231 // Initialize all sections. If the search string matches a title page, |
| 159 // show sections for that page. | 232 // show sections for that page. |
| 160 var page, pageMatch, childDiv; | 233 var page, pageMatch, childDiv, length; |
| 161 var pagesToSearch = this.getSearchablePages_(); | 234 var pagesToSearch = this.getSearchablePages_(); |
| 162 for (var key in pagesToSearch) { | 235 for (var key in pagesToSearch) { |
| 163 page = pagesToSearch[key]; | 236 page = pagesToSearch[key]; |
| 164 pageMatch = false; | 237 pageMatch = false; |
| 165 if (searchText.length) { | 238 if (searchText.length) { |
| 166 pageMatch = this.performReplace_(regEx, replaceString, page.tab); | 239 pageMatch = this.performReplace_(regEx, replaceString, page.tab); |
| 167 } | 240 } |
| 168 if (pageMatch) | 241 if (pageMatch) |
| 169 foundMatches = true; | 242 foundMatches = true; |
| 170 for (var i = 0; i < page.pageDiv.childNodes.length; i++) { | 243 length = page.pageDiv.children.length; |
| 171 childDiv = page.pageDiv.childNodes[i]; | 244 for (var i = 0; i < length; i++) { |
| 172 if (childDiv.nodeType == document.ELEMENT_NODE && | 245 childDiv = page.pageDiv.children[i]; |
| 173 childDiv.nodeName == 'SECTION') { | 246 if (childDiv.nodeName == 'SECTION') { |
|
arv (Not doing code reviews)
2011/01/21 23:19:31
tagName
csilv
2011/01/24 19:17:34
Done. (here and elsewhere)
| |
| 174 if (pageMatch) { | 247 if (pageMatch) { |
| 175 childDiv.classList.remove('search-hidden'); | 248 childDiv.classList.remove('search-hidden'); |
| 176 } else { | 249 } else { |
| 177 childDiv.classList.add('search-hidden'); | 250 childDiv.classList.add('search-hidden'); |
| 178 } | 251 } |
| 179 } | 252 } |
| 180 } | 253 } |
| 181 } | 254 } |
| 182 | 255 |
| 183 if (searchText.length) { | 256 if (searchText.length) { |
| 257 // Search all top-level sections for anchored string matches. | |
| 258 for (var key in pagesToSearch) { | |
| 259 page = pagesToSearch[key]; | |
| 260 length = page.pageDiv.children.length; | |
| 261 for (var i = 0; i < length; i++) { | |
| 262 childDiv = page.pageDiv.children[i]; | |
| 263 if (childDiv.nodeName == 'SECTION' && | |
| 264 this.performReplace_(regEx, replaceString, childDiv)) { | |
| 265 childDiv.classList.remove('search-hidden'); | |
| 266 foundMatches = true; | |
| 267 } | |
| 268 } | |
| 269 } | |
| 270 | |
| 184 // Search all sub-pages, generating an array of top-level sections that | 271 // Search all sub-pages, generating an array of top-level sections that |
| 185 // we need to make visible. | 272 // we need to make visible. |
| 186 var subPagesToSearch = this.getSearchableSubPages_(); | 273 var subPagesToSearch = this.getSearchableSubPages_(); |
| 187 var control, node; | 274 var control, node; |
| 188 for (var key in subPagesToSearch) { | 275 for (var key in subPagesToSearch) { |
| 189 page = subPagesToSearch[key]; | 276 page = subPagesToSearch[key]; |
| 190 if (this.performReplace_(regEx, replaceString, page.pageDiv)) { | 277 if (this.performReplace_(regEx, replaceString, page.pageDiv)) { |
| 278 // Reveal the section for this search result. | |
| 191 section = page.associatedSection; | 279 section = page.associatedSection; |
| 192 if (section) | 280 if (section) |
| 193 section.classList.remove('search-hidden'); | 281 section.classList.remove('search-hidden'); |
| 194 controls = page.associatedControls; | 282 |
| 283 // Identify any controls that should have bubbles. | |
| 284 var controls = page.associatedControls; | |
| 195 if (controls) { | 285 if (controls) { |
| 196 // TODO(csilv): highlight each control. | 286 length = controls.length; |
| 287 for (var i = 0; i < length; i++) | |
| 288 bubbleControls.push(controls[i]); | |
| 197 } | 289 } |
| 198 | 290 |
| 199 foundMatches = true; | 291 foundMatches = true; |
| 200 } | 292 } |
| 201 } | 293 } |
| 202 | |
| 203 // Search all top-level sections for anchored string matches. | |
| 204 for (var key in pagesToSearch) { | |
| 205 page = pagesToSearch[key]; | |
| 206 for (var i = 0; i < page.pageDiv.childNodes.length; i++) { | |
| 207 childDiv = page.pageDiv.childNodes[i]; | |
| 208 if (childDiv.nodeType == document.ELEMENT_NODE && | |
| 209 childDiv.nodeName == 'SECTION' && | |
| 210 this.performReplace_(regEx, replaceString, childDiv)) { | |
| 211 childDiv.classList.remove('search-hidden'); | |
| 212 foundMatches = true; | |
| 213 } | |
| 214 } | |
| 215 } | |
| 216 } | 294 } |
| 217 | 295 |
| 218 // Configure elements on the search results page based on search results. | 296 // Configure elements on the search results page based on search results. |
| 219 if (searchText.length == 0) { | 297 if (searchText.length == 0) { |
| 220 $('searchPageInfo').classList.remove('search-hidden'); | 298 $('searchPageInfo').classList.remove('search-hidden'); |
| 221 $('searchPageNoMatches').classList.add('search-hidden'); | 299 $('searchPageNoMatches').classList.add('search-hidden'); |
| 222 } else if (foundMatches) { | 300 } else if (foundMatches) { |
| 223 $('searchPageInfo').classList.add('search-hidden'); | 301 $('searchPageInfo').classList.add('search-hidden'); |
| 224 $('searchPageNoMatches').classList.add('search-hidden'); | 302 $('searchPageNoMatches').classList.add('search-hidden'); |
| 225 } else { | 303 } else { |
| 226 $('searchPageInfo').classList.add('search-hidden'); | 304 $('searchPageInfo').classList.add('search-hidden'); |
| 227 $('searchPageNoMatches').classList.remove('search-hidden'); | 305 $('searchPageNoMatches').classList.remove('search-hidden'); |
| 228 } | 306 } |
| 307 | |
| 308 // Create search balloons for sub-page results. | |
| 309 length = bubbleControls.length; | |
| 310 for (var i = 0; i < length; i++) | |
| 311 this.createSearchBubble_(bubbleControls[i], text); | |
| 229 }, | 312 }, |
| 230 | 313 |
| 231 /** | 314 /** |
| 232 * Performs a string replacement based on a regex and replace string. | 315 * Performs a string replacement based on a regex and replace string. |
| 233 * @param {RegEx} regex A regular expression for finding search matches. | 316 * @param {RegEx} regex A regular expression for finding search matches. |
| 234 * @param {String} replace A string to apply the replace operation. | 317 * @param {String} replace A string to apply the replace operation. |
| 235 * @param {Element} element An HTML container element. | 318 * @param {Element} element An HTML container element. |
| 236 * @returns {Boolean} true if the element was changed. | 319 * @returns {Boolean} true if the element was changed. |
| 237 * @private | 320 * @private |
| 238 */ | 321 */ |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 295 | 378 |
| 296 // Replace the highlight element with the first child (the text node). | 379 // Replace the highlight element with the first child (the text node). |
| 297 parent.replaceChild(node.firstChild, node); | 380 parent.replaceChild(node.firstChild, node); |
| 298 | 381 |
| 299 // Normalize the parent so that multiple text nodes will be combined. | 382 // Normalize the parent so that multiple text nodes will be combined. |
| 300 parent.normalize(); | 383 parent.normalize(); |
| 301 } | 384 } |
| 302 }, | 385 }, |
| 303 | 386 |
| 304 /** | 387 /** |
| 388 * Creates a search result bubble attached to an element. | |
| 389 * @param {Element} element An HTML element, usually a button. | |
| 390 * @param {string} text A string to show in the bubble. | |
| 391 * @private | |
| 392 */ | |
| 393 createSearchBubble_: function(element, text) { | |
|
arv (Not doing code reviews)
2011/01/21 23:19:31
I would just make this a local function instead
| |
| 394 // avoid appending multiple ballons to a button. | |
| 395 var sibling = element.previousSibling; | |
|
arv (Not doing code reviews)
2011/01/21 23:19:31
previousElementSibling so you can skip testing for
csilv
2011/01/24 19:17:34
Done.
| |
| 396 if (sibling && sibling.classList && | |
| 397 sibling.classList.contains('search-bubble')) | |
| 398 return; | |
| 399 | |
| 400 var parent = element.parentNode; | |
|
arv (Not doing code reviews)
2011/01/21 23:19:31
parentElement
csilv
2011/01/24 19:17:34
Done.
| |
| 401 if (parent) { | |
| 402 var bubble = SearchBubble(text); | |
|
arv (Not doing code reviews)
2011/01/21 23:19:31
missing new
csilv
2011/01/24 19:17:34
Done.
| |
| 403 parent.insertBefore(bubble, element); | |
| 404 bubble.updatePosition(); | |
| 405 } | |
| 406 }, | |
| 407 | |
| 408 /** | |
| 409 * Removes all search match bubbles. | |
| 410 * @private | |
| 411 */ | |
| 412 removeSearchBubbles_: function() { | |
| 413 var elements = document.querySelectorAll('.search-bubble'); | |
| 414 var length = elements.length; | |
| 415 for (var i = 0; i < length; i++) | |
| 416 elements[i].dispose(); | |
| 417 }, | |
| 418 | |
| 419 /** | |
| 305 * Builds a list of top-level pages to search. Omits the search page and | 420 * Builds a list of top-level pages to search. Omits the search page and |
| 306 * all sub-pages. | 421 * all sub-pages. |
| 307 * @returns {Array} An array of pages to search. | 422 * @returns {Array} An array of pages to search. |
| 308 * @private | 423 * @private |
| 309 */ | 424 */ |
| 310 getSearchablePages_: function() { | 425 getSearchablePages_: function() { |
| 311 var name, page, pages = []; | 426 var name, page, pages = []; |
| 312 for (name in OptionsPage.registeredPages) { | 427 for (name in OptionsPage.registeredPages) { |
| 313 if (name != this.name) { | 428 if (name != this.name) { |
| 314 page = OptionsPage.registeredPages[name]; | 429 page = OptionsPage.registeredPages[name]; |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 340 return pages; | 455 return pages; |
| 341 } | 456 } |
| 342 }; | 457 }; |
| 343 | 458 |
| 344 // Export | 459 // Export |
| 345 return { | 460 return { |
| 346 SearchPage: SearchPage | 461 SearchPage: SearchPage |
| 347 }; | 462 }; |
| 348 | 463 |
| 349 }); | 464 }); |
| OLD | NEW |