blob: 6b1e525aceac6a694b198cf357fac0d74294dca8 [file] [log] [blame]
Scott Maine4d8f1b2012-06-21 18:03:05 -07001/**
2 * jQuery history event v0.1
3 * Copyright (c) 2008 Tom Rodenberg <tarodenberg gmail com>
4 * Licensed under the GPL (http://www.gnu.org/licenses/gpl.html) license.
5 */
6(function($) {
7 var currentHash, previousNav, timer, hashTrim = /^.*#/;
8
9 var msie = {
10 iframe: null,
11 getDoc: function() {
12 return msie.iframe.contentWindow.document;
13 },
14 getHash: function() {
15 return msie.getDoc().location.hash;
16 },
17 setHash: function(hash) {
18 var d = msie.getDoc();
19 d.open();
20 d.close();
21 d.location.hash = hash;
22 }
23 };
24
25 var historycheck = function() {
26 var hash = msie.iframe ? msie.getHash() : location.hash;
27 if (hash != currentHash) {
28 currentHash = hash;
29 if (msie.iframe) {
30 location.hash = currentHash;
31 }
32 var current = $.history.getCurrent();
33 $.event.trigger('history', [current, previousNav]);
34 previousNav = current;
35 }
36 };
37
38 $.history = {
39 add: function(hash) {
40 hash = '#' + hash.replace(hashTrim, '');
41 if (currentHash != hash) {
42 var previous = $.history.getCurrent();
43 location.hash = currentHash = hash;
44 if (msie.iframe) {
45 msie.setHash(currentHash);
46 }
47 $.event.trigger('historyadd', [$.history.getCurrent(), previous]);
48 }
49 if (!timer) {
50 timer = setInterval(historycheck, 100);
51 }
52 },
53 getCurrent: function() {
54 if (currentHash) {
55 return currentHash.replace(hashTrim, '');
56 } else {
57 return "";
58 }
59 }
60 };
61
62 $.fn.history = function(fn) {
63 $(this).bind('history', fn);
64 };
65
66 $.fn.historyadd = function(fn) {
67 $(this).bind('historyadd', fn);
68 };
69
70 $(function() {
71 currentHash = location.hash;
72 if ($.browser.msie) {
73 msie.iframe = $('<iframe style="display:none"src="javascript:false;"></iframe>')
74 .prependTo('body')[0];
75 msie.setHash(currentHash);
76 currentHash = msie.getHash();
77 }
78 });
79})(jQuery);
80
81
82
83
84
85
86
87
88
89
90
91
92var gSelectedIndex = -1;
93var gSelectedID = -1;
94var gMatches = new Array();
95var gLastText = "";
96var ROW_COUNT = 20;
97var gInitialized = false;
98
99function set_item_selected($li, selected)
100{
101 if (selected) {
102 $li.attr('class','jd-autocomplete jd-selected');
103 } else {
104 $li.attr('class','jd-autocomplete');
105 }
106}
107
108function set_item_values(toroot, $li, match)
109{
110 var $link = $('a',$li);
111 $link.html(match.__hilabel || match.label);
112 $link.attr('href',toroot + match.link);
113}
114
115function sync_selection_table(toroot)
116{
117 var $list = $("#search_filtered");
118 var $li; //list item jquery object
119 var i; //list item iterator
120 gSelectedID = -1;
121
122 //initialize the table; draw it for the first time (but not visible).
123 if (!gInitialized) {
124 for (i=0; i<ROW_COUNT; i++) {
125 var $li = $("<li class='jd-autocomplete'></li>");
126 $list.append($li);
127
128 $li.mousedown(function() {
129 window.location = this.firstChild.getAttribute("href");
130 });
131 $li.mouseover(function() {
132 $('#search_filtered li').removeClass('jd-selected');
133 $(this).addClass('jd-selected');
134 gSelectedIndex = $('#search_filtered li').index(this);
135 });
136 $li.append('<a></a>');
137 }
138 gInitialized = true;
139 }
140
141 //if we have results, make the table visible and initialize result info
142 if (gMatches.length > 0) {
143 $('#search_filtered_div').removeClass('no-display');
144 var N = gMatches.length < ROW_COUNT ? gMatches.length : ROW_COUNT;
145 for (i=0; i<N; i++) {
146 $li = $('#search_filtered li:nth-child('+(i+1)+')');
147 $li.attr('class','show-item');
148 set_item_values(toroot, $li, gMatches[i]);
149 set_item_selected($li, i == gSelectedIndex);
150 if (i == gSelectedIndex) {
151 gSelectedID = gMatches[i].id;
152 }
153 }
154 //start hiding rows that are no longer matches
155 for (; i<ROW_COUNT; i++) {
156 $li = $('#search_filtered li:nth-child('+(i+1)+')');
157 $li.attr('class','no-display');
158 }
159 //if there are more results we're not showing, so say so.
160/* if (gMatches.length > ROW_COUNT) {
161 li = list.rows[ROW_COUNT];
162 li.className = "show-item";
163 c1 = li.cells[0];
164 c1.innerHTML = "plus " + (gMatches.length-ROW_COUNT) + " more";
165 } else {
166 list.rows[ROW_COUNT].className = "hide-item";
167 }*/
168 //if we have no results, hide the table
169 } else {
170 $('#search_filtered_div').addClass('no-display');
171 }
172}
173
174function search_changed(e, kd, toroot)
175{
176 var search = document.getElementById("search_autocomplete");
177 var text = search.value.replace(/(^ +)|( +$)/g, '');
178
179 // show/hide the close button
180 if (text != '') {
181 $(".search .close").removeClass("hide");
182 } else {
183 $(".search .close").addClass("hide");
184 }
185
186 // 13 = enter
187 if (e.keyCode == 13) {
188 $('#search_filtered_div').addClass('no-display');
189 if (!$('#search_filtered_div').hasClass('no-display') || (gSelectedIndex < 0)) {
190 return true;
191 } else if (kd && gSelectedIndex >= 0) {
192 window.location = toroot + gMatches[gSelectedIndex].link;
193 return false;
194 }
195 }
196 // 38 -- arrow up
197 else if (kd && (e.keyCode == 38)) {
198 if (gSelectedIndex >= 0) {
199 $('#search_filtered li').removeClass('jd-selected');
200 gSelectedIndex--;
201 $('#search_filtered li:nth-child('+(gSelectedIndex+1)+')').addClass('jd-selected');
202 }
203 return false;
204 }
205 // 40 -- arrow down
206 else if (kd && (e.keyCode == 40)) {
207 if (gSelectedIndex < gMatches.length-1
208 && gSelectedIndex < ROW_COUNT-1) {
209 $('#search_filtered li').removeClass('jd-selected');
210 gSelectedIndex++;
211 $('#search_filtered li:nth-child('+(gSelectedIndex+1)+')').addClass('jd-selected');
212 }
213 return false;
214 }
215 else if (!kd && (e.keyCode != 40) && (e.keyCode != 38)) {
216 gMatches = new Array();
217 matchedCount = 0;
218 gSelectedIndex = -1;
219 for (var i=0; i<DATA.length; i++) {
220 var s = DATA[i];
221 if (text.length != 0 &&
222 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
223 gMatches[matchedCount] = s;
224 matchedCount++;
225 }
226 }
227 rank_autocomplete_results(text);
228 for (var i=0; i<gMatches.length; i++) {
229 var s = gMatches[i];
230 if (gSelectedID == s.id) {
231 gSelectedIndex = i;
232 }
233 }
234 highlight_autocomplete_result_labels(text);
235 sync_selection_table(toroot);
236 return true; // allow the event to bubble up to the search api
237 }
238}
239
240function rank_autocomplete_results(query) {
241 query = query || '';
242 if (!gMatches || !gMatches.length)
243 return;
244
245 // helper function that gets the last occurence index of the given regex
246 // in the given string, or -1 if not found
247 var _lastSearch = function(s, re) {
248 if (s == '')
249 return -1;
250 var l = -1;
251 var tmp;
252 while ((tmp = s.search(re)) >= 0) {
253 if (l < 0) l = 0;
254 l += tmp;
255 s = s.substr(tmp + 1);
256 }
257 return l;
258 };
259
260 // helper function that counts the occurrences of a given character in
261 // a given string
262 var _countChar = function(s, c) {
263 var n = 0;
264 for (var i=0; i<s.length; i++)
265 if (s.charAt(i) == c) ++n;
266 return n;
267 };
268
269 var queryLower = query.toLowerCase();
270 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
271 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
272 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
273
274 var _resultScoreFn = function(result) {
275 // scores are calculated based on exact and prefix matches,
276 // and then number of path separators (dots) from the last
277 // match (i.e. favoring classes and deep package names)
278 var score = 1.0;
279 var labelLower = result.label.toLowerCase();
280 var t;
281 t = _lastSearch(labelLower, partExactAlnumRE);
282 if (t >= 0) {
283 // exact part match
284 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
285 score *= 200 / (partsAfter + 1);
286 } else {
287 t = _lastSearch(labelLower, partPrefixAlnumRE);
288 if (t >= 0) {
289 // part prefix match
290 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
291 score *= 20 / (partsAfter + 1);
292 }
293 }
294
295 return score;
296 };
297
298 for (var i=0; i<gMatches.length; i++) {
299 gMatches[i].__resultScore = _resultScoreFn(gMatches[i]);
300 }
301
302 gMatches.sort(function(a,b){
303 var n = b.__resultScore - a.__resultScore;
304 if (n == 0) // lexicographical sort if scores are the same
305 n = (a.label < b.label) ? -1 : 1;
306 return n;
307 });
308}
309
310function highlight_autocomplete_result_labels(query) {
311 query = query || '';
312 if (!gMatches || !gMatches.length)
313 return;
314
315 var queryLower = query.toLowerCase();
316 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
317 var queryRE = new RegExp(
318 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
319 for (var i=0; i<gMatches.length; i++) {
320 gMatches[i].__hilabel = gMatches[i].label.replace(
321 queryRE, '<b>$1</b>');
322 }
323}
324
325function search_focus_changed(obj, focused)
326{
327 if (!focused) {
328 if(obj.value == ""){
329 $(".search .close").addClass("hide");
330 }
331 document.getElementById("search_filtered_div").className = "no-display";
332 }
333}
334
335function submit_search() {
336 var query = document.getElementById('search_autocomplete').value;
337 location.hash = 'q=' + query;
338 $.history.add('q=' + query);
339 loadSearchResults();
340 $("#searchResults").slideDown();
341 return false;
342}
343
344
345function hideResults() {
346 $("#searchResults").slideUp();
347 $(".search .close").addClass("hide");
348 location.hash = '';
349 drawOptions.setInput(document.getElementById("searchResults"));
350
351 $("#search_autocomplete").blur();
352 return false;
353}
354
355
356
357
358
359
360
361
362
363
364
365
366/************ SEARCH ENGINE ***************/
367
368
369 google.load('search', '1');
370
371 function loadSearchResults() {
372 if (location.hash.indexOf("q=") == -1) {
373 // if there's no query in the url, don't search and make sure results are hidden
374 $('#searchResults').hide();
375 return;
376 }
377
378 var $results = $("#searchResults");
379 if ($results.is(":hidden")) {
380 $results.slideDown();
381 }
382
383 document.getElementById("search_autocomplete").style.color = "#000";
384
385 // create search control
386 searchControl = new google.search.SearchControl();
387
388 // use our existing search form and use tabs when multiple searchers are used
389 drawOptions = new google.search.DrawOptions();
390 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
391 drawOptions.setInput(document.getElementById("search_autocomplete"));
392
393 // configure search result options
394 searchOptions = new google.search.SearcherOptions();
395 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
396
397 // configure each of the searchers, for each tab
398 devSiteSearcher = new google.search.WebSearch();
399 devSiteSearcher.setUserDefinedLabel("All");
400 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
401
402 designSearcher = new google.search.WebSearch();
403 designSearcher.setUserDefinedLabel("Design");
404 designSearcher.setSiteRestriction("http://developer.android.com/design/");
405
406 trainingSearcher = new google.search.WebSearch();
407 trainingSearcher.setUserDefinedLabel("Training");
408 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
409
410 guidesSearcher = new google.search.WebSearch();
411 guidesSearcher.setUserDefinedLabel("Guides");
412 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
413
414 referenceSearcher = new google.search.WebSearch();
415 referenceSearcher.setUserDefinedLabel("Reference");
416 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
417
418 blogSearcher = new google.search.WebSearch();
419 blogSearcher.setUserDefinedLabel("Blog");
420 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
421
422 // add each searcher to the search control
423 searchControl.addSearcher(devSiteSearcher, searchOptions);
424 searchControl.addSearcher(designSearcher, searchOptions);
425 searchControl.addSearcher(trainingSearcher, searchOptions);
426 searchControl.addSearcher(guidesSearcher, searchOptions);
427 searchControl.addSearcher(referenceSearcher, searchOptions);
428 searchControl.addSearcher(blogSearcher, searchOptions);
429
430 // configure result options
431 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
432 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
433 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_LONG);
434 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
435
436 // upon ajax search, refresh the url and search title
437 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
438 updateResultTitle(query);
439 var query = document.getElementById('search_autocomplete').value;
440 location.hash = 'q=' + query;
441 $.history.add('q=' + query);
442 });
443
444 // draw the search results box
445 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
446
447 // get query and execute the search
448 searchControl.execute(decodeURI(getQuery(location.hash)));
449
450 document.getElementById("search_autocomplete").focus();
451 addTabListeners();
452 }
453 // End of loadSearchResults
454
455
456 google.setOnLoadCallback(loadSearchResults, true);
457
458 // when an event on the browser history occurs (back, forward, load) perform a search
459 $(window).history(function(e, hash) {
460 var query = decodeURI(getQuery(hash));
461 if (query == "undefined") {
462 hideResults();
463 return;
464 }
465 searchControl.execute(query);
466
467 updateResultTitle(query);
468 });
469
470 function updateResultTitle(query) {
471 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
472 }
473
474 // forcefully regain key-up event control (previously jacked by search api)
475 $("#search_autocomplete").keyup(function(event) {
Scott Main1b3db112012-07-03 14:06:22 -0700476 return search_changed(event, false, toRoot);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700477 });
478
479 // add event listeners to each tab so we can track the browser history
480 function addTabListeners() {
481 var tabHeaders = $(".gsc-tabHeader");
482 for (var i = 0; i < tabHeaders.length; i++) {
483 $(tabHeaders[i]).attr("id",i).click(function() {
484 /*
485 // make a copy of the page numbers for the search left pane
486 setTimeout(function() {
487 // remove any residual page numbers
488 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
489 // move the page numbers to the left position; make a clone,
490 // because the element is drawn to the DOM only once
491 // and because we're going to remove it (previous line),
492 // we need it to be available to move again as the user navigates
493 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
494 .clone().appendTo('#searchResults .gsc-tabsArea');
495 }, 200);
496 */
497 });
498 }
499 setTimeout(function(){$(tabHeaders[0]).click()},200);
500 }
501
502
503 function getQuery(hash) {
504 var queryParts = hash.split('=');
505 return queryParts[1];
506 }
507
508 /* returns the given string with all HTML brackets converted to entities
509 TODO: move this to the site's JS library */
510 function escapeHTML(string) {
511 return string.replace(/</g,"&lt;")
512 .replace(/>/g,"&gt;");
513 }
514
515