Scott Main | a214d84 | 2012-07-16 17:14:40 -0700 | [diff] [blame^] | 1 | /* |
| 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 Main | e4d8f1b | 2012-06-21 18:03:05 -0700 | [diff] [blame] | 8 | */ |
Scott Main | a214d84 | 2012-07-16 17:14:40 -0700 | [diff] [blame^] | 9 | (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 Main | e4d8f1b | 2012-06-21 18:03:05 -0700 | [diff] [blame] | 10 | |
| 11 | |
| 12 | |
| 13 | |
| 14 | |
| 15 | |
| 16 | |
| 17 | |
| 18 | |
| 19 | |
| 20 | |
| 21 | |
| 22 | var gSelectedIndex = -1; |
| 23 | var gSelectedID = -1; |
| 24 | var gMatches = new Array(); |
| 25 | var gLastText = ""; |
| 26 | var ROW_COUNT = 20; |
| 27 | var gInitialized = false; |
| 28 | |
| 29 | function 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 | |
| 38 | function 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 | |
| 45 | function 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 | |
| 104 | function 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 Main | a214d84 | 2012-07-16 17:14:40 -0700 | [diff] [blame^] | 120 | 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 Main | e4d8f1b | 2012-06-21 18:03:05 -0700 | [diff] [blame] | 128 | } 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 | |
| 177 | function 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 | |
| 247 | function 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 | |
| 262 | function 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 | |
| 272 | function submit_search() { |
| 273 | var query = document.getElementById('search_autocomplete').value; |
| 274 | location.hash = 'q=' + query; |
Scott Main | e4d8f1b | 2012-06-21 18:03:05 -0700 | [diff] [blame] | 275 | loadSearchResults(); |
Scott Main | a214d84 | 2012-07-16 17:14:40 -0700 | [diff] [blame^] | 276 | $("#searchResults").slideDown('slow'); |
Scott Main | e4d8f1b | 2012-06-21 18:03:05 -0700 | [diff] [blame] | 277 | return false; |
| 278 | } |
| 279 | |
| 280 | |
| 281 | function hideResults() { |
| 282 | $("#searchResults").slideUp(); |
| 283 | $(".search .close").addClass("hide"); |
| 284 | location.hash = ''; |
| 285 | drawOptions.setInput(document.getElementById("searchResults")); |
| 286 | |
Scott Main | a214d84 | 2012-07-16 17:14:40 -0700 | [diff] [blame^] | 287 | $("#search_autocomplete").val("").blur(); |
| 288 | |
| 289 | // reset the ajax search callback to nothing, so results don't appear unless ENTER |
| 290 | searchControl.setSearchStartingCallback(this, function(control, searcher, query) {}); |
Scott Main | e4d8f1b | 2012-06-21 18:03:05 -0700 | [diff] [blame] | 291 | return false; |
| 292 | } |
| 293 | |
| 294 | |
| 295 | |
| 296 | |
| 297 | |
| 298 | |
| 299 | |
| 300 | |
| 301 | |
| 302 | |
| 303 | |
| 304 | |
| 305 | /************ SEARCH ENGINE ***************/ |
| 306 | |
| 307 | |
| 308 | google.load('search', '1'); |
Scott Main | a214d84 | 2012-07-16 17:14:40 -0700 | [diff] [blame^] | 309 | var searchControl; |
Scott Main | e4d8f1b | 2012-06-21 18:03:05 -0700 | [diff] [blame] | 310 | |
| 311 | function loadSearchResults() { |
Scott Main | e4d8f1b | 2012-06-21 18:03:05 -0700 | [diff] [blame] | 312 | |
| 313 | document.getElementById("search_autocomplete").style.color = "#000"; |
| 314 | |
| 315 | // create search control |
| 316 | searchControl = new google.search.SearchControl(); |
| 317 | |
| 318 | // use our existing search form and use tabs when multiple searchers are used |
| 319 | drawOptions = new google.search.DrawOptions(); |
| 320 | drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED); |
| 321 | drawOptions.setInput(document.getElementById("search_autocomplete")); |
| 322 | |
| 323 | // configure search result options |
| 324 | searchOptions = new google.search.SearcherOptions(); |
| 325 | searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN); |
| 326 | |
| 327 | // configure each of the searchers, for each tab |
| 328 | devSiteSearcher = new google.search.WebSearch(); |
| 329 | devSiteSearcher.setUserDefinedLabel("All"); |
| 330 | devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u"); |
| 331 | |
| 332 | designSearcher = new google.search.WebSearch(); |
| 333 | designSearcher.setUserDefinedLabel("Design"); |
| 334 | designSearcher.setSiteRestriction("http://developer.android.com/design/"); |
| 335 | |
| 336 | trainingSearcher = new google.search.WebSearch(); |
| 337 | trainingSearcher.setUserDefinedLabel("Training"); |
| 338 | trainingSearcher.setSiteRestriction("http://developer.android.com/training/"); |
| 339 | |
| 340 | guidesSearcher = new google.search.WebSearch(); |
| 341 | guidesSearcher.setUserDefinedLabel("Guides"); |
| 342 | guidesSearcher.setSiteRestriction("http://developer.android.com/guide/"); |
| 343 | |
| 344 | referenceSearcher = new google.search.WebSearch(); |
| 345 | referenceSearcher.setUserDefinedLabel("Reference"); |
| 346 | referenceSearcher.setSiteRestriction("http://developer.android.com/reference/"); |
| 347 | |
| 348 | blogSearcher = new google.search.WebSearch(); |
| 349 | blogSearcher.setUserDefinedLabel("Blog"); |
| 350 | blogSearcher.setSiteRestriction("http://android-developers.blogspot.com"); |
| 351 | |
| 352 | // add each searcher to the search control |
| 353 | searchControl.addSearcher(devSiteSearcher, searchOptions); |
| 354 | searchControl.addSearcher(designSearcher, searchOptions); |
| 355 | searchControl.addSearcher(trainingSearcher, searchOptions); |
| 356 | searchControl.addSearcher(guidesSearcher, searchOptions); |
| 357 | searchControl.addSearcher(referenceSearcher, searchOptions); |
| 358 | searchControl.addSearcher(blogSearcher, searchOptions); |
| 359 | |
| 360 | // configure result options |
| 361 | searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET); |
| 362 | searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF); |
Scott Main | a214d84 | 2012-07-16 17:14:40 -0700 | [diff] [blame^] | 363 | searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT); |
Scott Main | e4d8f1b | 2012-06-21 18:03:05 -0700 | [diff] [blame] | 364 | searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING); |
| 365 | |
| 366 | // upon ajax search, refresh the url and search title |
| 367 | searchControl.setSearchStartingCallback(this, function(control, searcher, query) { |
| 368 | updateResultTitle(query); |
| 369 | var query = document.getElementById('search_autocomplete').value; |
| 370 | location.hash = 'q=' + query; |
Scott Main | e4d8f1b | 2012-06-21 18:03:05 -0700 | [diff] [blame] | 371 | }); |
| 372 | |
| 373 | // draw the search results box |
| 374 | searchControl.draw(document.getElementById("leftSearchControl"), drawOptions); |
| 375 | |
| 376 | // get query and execute the search |
| 377 | searchControl.execute(decodeURI(getQuery(location.hash))); |
| 378 | |
| 379 | document.getElementById("search_autocomplete").focus(); |
| 380 | addTabListeners(); |
| 381 | } |
| 382 | // End of loadSearchResults |
| 383 | |
| 384 | |
Scott Main | a214d84 | 2012-07-16 17:14:40 -0700 | [diff] [blame^] | 385 | google.setOnLoadCallback(function(){ |
| 386 | if (location.hash.indexOf("q=") == -1) { |
| 387 | // if there's no query in the url, don't search and make sure results are hidden |
| 388 | $('#searchResults').hide(); |
| 389 | return; |
| 390 | } else { |
| 391 | // first time loading search results for this page |
| 392 | $('#searchResults').slideDown('slow'); |
| 393 | $(".search .close").removeClass("hide"); |
| 394 | loadSearchResults(); |
| 395 | } |
| 396 | }, true); |
Scott Main | e4d8f1b | 2012-06-21 18:03:05 -0700 | [diff] [blame] | 397 | |
Scott Main | a214d84 | 2012-07-16 17:14:40 -0700 | [diff] [blame^] | 398 | // when an event on the browser history occurs (back, forward, load) requery hash and do search |
| 399 | $(window).hashchange( function(){ |
| 400 | var query = decodeURI(getQuery(location.hash)); |
Scott Main | e4d8f1b | 2012-06-21 18:03:05 -0700 | [diff] [blame] | 401 | if (query == "undefined") { |
| 402 | hideResults(); |
| 403 | return; |
| 404 | } |
| 405 | searchControl.execute(query); |
Scott Main | a214d84 | 2012-07-16 17:14:40 -0700 | [diff] [blame^] | 406 | $('#searchResults').slideDown('slow'); |
| 407 | $("#search_autocomplete").focus(); |
| 408 | $(".search .close").removeClass("hide"); |
Scott Main | e4d8f1b | 2012-06-21 18:03:05 -0700 | [diff] [blame] | 409 | |
| 410 | updateResultTitle(query); |
| 411 | }); |
| 412 | |
| 413 | function updateResultTitle(query) { |
| 414 | $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>"); |
| 415 | } |
| 416 | |
| 417 | // forcefully regain key-up event control (previously jacked by search api) |
| 418 | $("#search_autocomplete").keyup(function(event) { |
Scott Main | 1b3db11 | 2012-07-03 14:06:22 -0700 | [diff] [blame] | 419 | return search_changed(event, false, toRoot); |
Scott Main | e4d8f1b | 2012-06-21 18:03:05 -0700 | [diff] [blame] | 420 | }); |
| 421 | |
| 422 | // add event listeners to each tab so we can track the browser history |
| 423 | function addTabListeners() { |
| 424 | var tabHeaders = $(".gsc-tabHeader"); |
| 425 | for (var i = 0; i < tabHeaders.length; i++) { |
| 426 | $(tabHeaders[i]).attr("id",i).click(function() { |
| 427 | /* |
| 428 | // make a copy of the page numbers for the search left pane |
| 429 | setTimeout(function() { |
| 430 | // remove any residual page numbers |
| 431 | $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove(); |
| 432 | // move the page numbers to the left position; make a clone, |
| 433 | // because the element is drawn to the DOM only once |
| 434 | // and because we're going to remove it (previous line), |
| 435 | // we need it to be available to move again as the user navigates |
| 436 | $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible') |
| 437 | .clone().appendTo('#searchResults .gsc-tabsArea'); |
| 438 | }, 200); |
| 439 | */ |
| 440 | }); |
| 441 | } |
| 442 | setTimeout(function(){$(tabHeaders[0]).click()},200); |
| 443 | } |
| 444 | |
| 445 | |
| 446 | function getQuery(hash) { |
| 447 | var queryParts = hash.split('='); |
| 448 | return queryParts[1]; |
| 449 | } |
| 450 | |
| 451 | /* returns the given string with all HTML brackets converted to entities |
| 452 | TODO: move this to the site's JS library */ |
| 453 | function escapeHTML(string) { |
| 454 | return string.replace(/</g,"<") |
| 455 | .replace(/>/g,">"); |
| 456 | } |
| 457 | |
| 458 | |