Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(532)

Side by Side Diff: chrome/browser/resources/options/search_page.js

Issue 6141002: dom-ui settings: display bubbles for sub-page search results.... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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
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 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698