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