docs.js revision 29e934367b927fb13417bee77008600ad5a39dcc
1var classesNav; 2var devdocNav; 3var sidenav; 4var cookie_namespace = 'android_developer'; 5var NAV_PREF_TREE = "tree"; 6var NAV_PREF_PANELS = "panels"; 7var nav_pref; 8var isMobile = false; // true if mobile, so we can adjust some layout 9var mPagePath; // initialized in ready() function 10 11var basePath = getBaseUri(location.pathname); 12var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1)); 13var GOOGLE_DATA; // combined data for google service apis, used for search suggest 14 15// Ensure that all ajax getScript() requests allow caching 16$.ajaxSetup({ 17 cache: true 18}); 19 20/****** ON LOAD SET UP STUFF *********/ 21 22$(document).ready(function() { 23 24 // show lang dialog if the URL includes /intl/ 25 //if (location.pathname.substring(0,6) == "/intl/") { 26 // var lang = location.pathname.split('/')[2]; 27 // if (lang != getLangPref()) { 28 // $("#langMessage a.yes").attr("onclick","changeLangPref('" + lang 29 // + "', true); $('#langMessage').hide(); return false;"); 30 // $("#langMessage .lang." + lang).show(); 31 // $("#langMessage").show(); 32 // } 33 //} 34 35 // load json file for JD doc search suggestions 36 $.getScript(toRoot + 'jd_lists_unified.js'); 37 // load json file for Android API search suggestions 38 $.getScript(toRoot + 'reference/lists.js'); 39 // load json files for Google services API suggestions 40 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) { 41 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data 42 if(jqxhr.status === 200) { 43 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) { 44 if(jqxhr.status === 200) { 45 // combine GCM and GMS data 46 GOOGLE_DATA = GMS_DATA; 47 var start = GOOGLE_DATA.length; 48 for (var i=0; i<GCM_DATA.length; i++) { 49 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label, 50 link:GCM_DATA[i].link, type:GCM_DATA[i].type}); 51 } 52 } 53 }); 54 } 55 }); 56 57 // setup keyboard listener for search shortcut 58 $('body').keyup(function(event) { 59 if (event.which == 191) { 60 $('#search_autocomplete').focus(); 61 } 62 }); 63 64 // init the fullscreen toggle click event 65 $('#nav-swap .fullscreen').click(function(){ 66 if ($(this).hasClass('disabled')) { 67 toggleFullscreen(true); 68 } else { 69 toggleFullscreen(false); 70 } 71 }); 72 73 // initialize the divs with custom scrollbars 74 $('.scroll-pane').jScrollPane( {verticalGutter:0} ); 75 76 // add HRs below all H2s (except for a few other h2 variants) 77 $('h2').not('#qv h2') 78 .not('#tb h2') 79 .not('.sidebox h2') 80 .not('#devdoc-nav h2') 81 .not('h2.norule').css({marginBottom:0}) 82 .after('<hr/>'); 83 84 // set up the search close button 85 $('#search-close').click(function() { 86 $searchInput = $('#search_autocomplete'); 87 $searchInput.attr('value', ''); 88 $(this).addClass("hide"); 89 $("#search-container").removeClass('active'); 90 $("#search_autocomplete").blur(); 91 search_focus_changed($searchInput.get(), false); 92 hideResults(); 93 }); 94 95 96 //Set up search 97 $("#search_autocomplete").focus(function() { 98 $("#search-container").addClass('active'); 99 }) 100 $("#search-container").mouseover(function() { 101 $("#search-container").addClass('active'); 102 $("#search_autocomplete").focus(); 103 }) 104 $("#search-container").mouseout(function() { 105 if ($("#search_autocomplete").is(":focus")) return; 106 if ($("#search_autocomplete").val() == '') { 107 setTimeout(function(){ 108 $("#search-container").removeClass('active'); 109 $("#search_autocomplete").blur(); 110 },250); 111 } 112 }) 113 $("#search_autocomplete").blur(function() { 114 if ($("#search_autocomplete").val() == '') { 115 $("#search-container").removeClass('active'); 116 } 117 }) 118 119 120 // prep nav expandos 121 var pagePath = document.location.pathname; 122 // account for intl docs by removing the intl/*/ path 123 if (pagePath.indexOf("/intl/") == 0) { 124 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last / 125 } 126 127 if (pagePath.indexOf(SITE_ROOT) == 0) { 128 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') { 129 pagePath += 'index.html'; 130 } 131 } 132 133 // Need a copy of the pagePath before it gets changed in the next block; 134 // it's needed to perform proper tab highlighting in offline docs (see rootDir below) 135 var pagePathOriginal = pagePath; 136 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') { 137 // If running locally, SITE_ROOT will be a relative path, so account for that by 138 // finding the relative URL to this page. This will allow us to find links on the page 139 // leading back to this page. 140 var pathParts = pagePath.split('/'); 141 var relativePagePathParts = []; 142 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3; 143 for (var i = 0; i < upDirs; i++) { 144 relativePagePathParts.push('..'); 145 } 146 for (var i = 0; i < upDirs; i++) { 147 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]); 148 } 149 relativePagePathParts.push(pathParts[pathParts.length - 1]); 150 pagePath = relativePagePathParts.join('/'); 151 } else { 152 // Otherwise the page path is already an absolute URL 153 } 154 155 // Highlight the header tabs... 156 // highlight Design tab 157 var urlSegments = pagePathOriginal.split('/'); 158 var navEl = $(".dac-nav-list"); 159 var subNavEl = navEl.find(".dac-nav-secondary"); 160 var parentNavEl; 161 162 if ($("body").hasClass("design")) { 163 navEl.find("> li.design > a").addClass("selected"); 164 // highlight About tabs 165 } else if ($("body").hasClass("about")) { 166 if (urlSegments[1] == "about" || urlSegments[1] == "wear" || urlSegments[1] == "tv" || urlSegments[1] == "auto") { 167 navEl.find("> li.home > a").addClass('has-subnav'); 168 subNavEl.find("li." + urlSegments[1] + " > a").addClass("selected"); 169 } else { 170 navEl.find("> li.home > a").addClass('selected'); 171 } 172 // highlight Develop tab 173 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) { 174 parentNavEl = navEl.find("> li.develop > a"); 175 parentNavEl.addClass('has-subnav'); 176 177 // In Develop docs, also highlight appropriate sub-tab 178 if (urlSegments[1] == "training") { 179 subNavEl.find("li.training > a").addClass("selected"); 180 } else if (urlSegments[1] == "guide") { 181 subNavEl.find("li.guide > a").addClass("selected"); 182 } else if (urlSegments[1] == "reference") { 183 // If the root is reference, but page is also part of Google Services, select Google 184 if ($("body").hasClass("google")) { 185 subNavEl.find("li.google > a").addClass("selected"); 186 } else { 187 subNavEl.find("li.reference > a").addClass("selected"); 188 } 189 } else if ((urlSegments[1] == "tools") || (urlSegments[1] == "sdk")) { 190 subNavEl.find("li.tools > a").addClass("selected"); 191 } else if ($("body").hasClass("google")) { 192 subNavEl.find("li.google > a").addClass("selected"); 193 } else if ($("body").hasClass("samples")) { 194 subNavEl.find("li.samples > a").addClass("selected"); 195 } else { 196 parentNavEl.removeClass('has-subnav').addClass("selected"); 197 } 198 // highlight Distribute tab 199 } else if ($("body").hasClass("distribute")) { 200 parentNavEl = navEl.find("> li.distribute > a"); 201 parentNavEl.addClass('has-subnav'); 202 203 if (urlSegments[2] == "users") { 204 subNavEl.find("li.users > a").addClass("selected"); 205 } else if (urlSegments[2] == "engage") { 206 subNavEl.find("li.engage > a").addClass("selected"); 207 } else if (urlSegments[2] == "monetize") { 208 subNavEl.find("li.monetize > a").addClass("selected"); 209 } else if (urlSegments[2] == "analyze") { 210 subNavEl.find("li.analyze > a").addClass("selected"); 211 } else if (urlSegments[2] == "tools") { 212 subNavEl.find("li.disttools > a").addClass("selected"); 213 } else if (urlSegments[2] == "stories") { 214 subNavEl.find("li.stories > a").addClass("selected"); 215 } else if (urlSegments[2] == "essentials") { 216 subNavEl.find("li.essentials > a").addClass("selected"); 217 } else if (urlSegments[2] == "googleplay") { 218 subNavEl.find("li.googleplay > a").addClass("selected"); 219 } else { 220 parentNavEl.removeClass('has-subnav').addClass("selected"); 221 } 222 } 223 224 // set global variable so we can highlight the sidenav a bit later (such as for google reference) 225 // and highlight the sidenav 226 mPagePath = pagePath; 227 highlightSidenav(); 228 buildBreadcrumbs(); 229 230 // set up prev/next links if they exist 231 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]'); 232 var $selListItem; 233 if ($selNavLink.length) { 234 $selListItem = $selNavLink.closest('li'); 235 236 // set up prev links 237 var $prevLink = []; 238 var $prevListItem = $selListItem.prev('li'); 239 240 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true : 241false; // navigate across topic boundaries only in design docs 242 if ($prevListItem.length) { 243 if ($prevListItem.hasClass('nav-section') || crossBoundaries) { 244 // jump to last topic of previous section 245 $prevLink = $prevListItem.find('a:last'); 246 } else if (!$selListItem.hasClass('nav-section')) { 247 // jump to previous topic in this section 248 $prevLink = $prevListItem.find('a:eq(0)'); 249 } 250 } else { 251 // jump to this section's index page (if it exists) 252 var $parentListItem = $selListItem.parents('li'); 253 $prevLink = $selListItem.parents('li').find('a'); 254 255 // except if cross boundaries aren't allowed, and we're at the top of a section already 256 // (and there's another parent) 257 if (!crossBoundaries && $parentListItem.hasClass('nav-section') 258 && $selListItem.hasClass('nav-section')) { 259 $prevLink = []; 260 } 261 } 262 263 // set up next links 264 var $nextLink = []; 265 var startClass = false; 266 var isCrossingBoundary = false; 267 268 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) { 269 // we're on an index page, jump to the first topic 270 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)'); 271 272 // if there aren't any children, go to the next section (required for About pages) 273 if($nextLink.length == 0) { 274 $nextLink = $selListItem.next('li').find('a'); 275 } else if ($('.topic-start-link').length) { 276 // as long as there's a child link and there is a "topic start link" (we're on a landing) 277 // then set the landing page "start link" text to be the first doc title 278 $('.topic-start-link').text($nextLink.text().toUpperCase()); 279 } 280 281 // If the selected page has a description, then it's a class or article homepage 282 if ($selListItem.find('a[description]').length) { 283 // this means we're on a class landing page 284 startClass = true; 285 } 286 } else { 287 // jump to the next topic in this section (if it exists) 288 $nextLink = $selListItem.next('li').find('a:eq(0)'); 289 if ($nextLink.length == 0) { 290 isCrossingBoundary = true; 291 // no more topics in this section, jump to the first topic in the next section 292 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)'); 293 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course) 294 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)'); 295 if ($nextLink.length == 0) { 296 // if that doesn't work, we're at the end of the list, so disable NEXT link 297 $('.next-page-link').attr('href','').addClass("disabled") 298 .click(function() { return false; }); 299 // and completely hide the one in the footer 300 $('.content-footer .next-page-link').hide(); 301 } 302 } 303 } 304 } 305 306 if (startClass) { 307 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide"); 308 309 // if there's no training bar (below the start button), 310 // then we need to add a bottom border to button 311 if (!$("#tb").length) { 312 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'}); 313 } 314 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries 315 $('.content-footer.next-class').show(); 316 $('.next-page-link').attr('href','') 317 .removeClass("hide").addClass("disabled") 318 .click(function() { return false; }); 319 // and completely hide the one in the footer 320 $('.content-footer .next-page-link').hide(); 321 if ($nextLink.length) { 322 $('.next-class-link').attr('href',$nextLink.attr('href')) 323 .removeClass("hide") 324 .append(": " + $nextLink.html()); 325 $('.next-class-link').find('.new').empty(); 326 } 327 } else { 328 $('.next-page-link').attr('href', $nextLink.attr('href')) 329 .removeClass("hide"); 330 // for the footer link, also add the next page title 331 $('.content-footer .next-page-link').append(": " + $nextLink.html()); 332 } 333 334 if (!startClass && $prevLink.length) { 335 var prevHref = $prevLink.attr('href'); 336 if (prevHref == SITE_ROOT + 'index.html') { 337 // Don't show Previous when it leads to the homepage 338 } else { 339 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide"); 340 } 341 } 342 343 } 344 345 346 347 // Set up the course landing pages for Training with class names and descriptions 348 if ($('body.trainingcourse').length) { 349 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a'); 350 351 // create an array for all the class descriptions 352 var $classDescriptions = new Array($classLinks.length); 353 var lang = getLangPref(); 354 $classLinks.each(function(index) { 355 var langDescr = $(this).attr(lang + "-description"); 356 if (typeof langDescr !== 'undefined' && langDescr !== false) { 357 // if there's a class description in the selected language, use that 358 $classDescriptions[index] = langDescr; 359 } else { 360 // otherwise, use the default english description 361 $classDescriptions[index] = $(this).attr("description"); 362 } 363 }); 364 365 var $olClasses = $('<ol class="class-list"></ol>'); 366 var $liClass; 367 var $h2Title; 368 var $pSummary; 369 var $olLessons; 370 var $liLesson; 371 $classLinks.each(function(index) { 372 $liClass = $('<li class="clearfix"></li>'); 373 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>'); 374 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>'); 375 376 $olLessons = $('<ol class="lesson-list"></ol>'); 377 378 $lessons = $(this).closest('li').find('ul li a'); 379 380 if ($lessons.length) { 381 $lessons.each(function(index) { 382 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>'); 383 }); 384 } else { 385 $pSummary.addClass('article'); 386 } 387 388 $liClass.append($h2Title).append($pSummary).append($olLessons); 389 $olClasses.append($liClass); 390 }); 391 $('.jd-descr').append($olClasses); 392 } 393 394 // Set up expand/collapse behavior 395 initExpandableNavItems("#nav"); 396 397 398 $(".scroll-pane").scroll(function(event) { 399 event.preventDefault(); 400 return false; 401 }); 402 403 /* Resize nav height when window height changes */ 404 $(window).resize(function() { 405 if ($('#side-nav').length == 0) return; 406 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]'); 407 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed 408 // make sidenav behave when resizing the window and side-scolling is a concern 409 if (sticky) { 410 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) { 411 updateSideNavPosition(); 412 } else { 413 updateSidenavFullscreenWidth(); 414 } 415 } 416 resizeNav(); 417 }); 418 419 420 var navBarLeftPos; 421 if ($('#devdoc-nav').length) { 422 setNavBarLeftPos(); 423 } 424 425 426 // Set up play-on-hover <video> tags. 427 $('video.play-on-hover').bind('click', function(){ 428 $(this).get(0).load(); // in case the video isn't seekable 429 $(this).get(0).play(); 430 }); 431 432 // Set up tooltips 433 var TOOLTIP_MARGIN = 10; 434 $('acronym,.tooltip-link').each(function() { 435 var $target = $(this); 436 var $tooltip = $('<div>') 437 .addClass('tooltip-box') 438 .append($target.attr('title')) 439 .hide() 440 .appendTo('body'); 441 $target.removeAttr('title'); 442 443 $target.hover(function() { 444 // in 445 var targetRect = $target.offset(); 446 targetRect.width = $target.width(); 447 targetRect.height = $target.height(); 448 449 $tooltip.css({ 450 left: targetRect.left, 451 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN 452 }); 453 $tooltip.addClass('below'); 454 $tooltip.show(); 455 }, function() { 456 // out 457 $tooltip.hide(); 458 }); 459 }); 460 461 // Set up <h2> deeplinks 462 $('h2').click(function() { 463 var id = $(this).attr('id'); 464 if (id) { 465 document.location.hash = id; 466 } 467 }); 468 469 //Loads the +1 button 470 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true; 471 po.src = 'https://apis.google.com/js/plusone.js'; 472 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s); 473 474 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller 475 476 if ($(".scroll-pane").length > 1) { 477 // Check if there's a user preference for the panel heights 478 var cookieHeight = readCookie("reference_height"); 479 if (cookieHeight) { 480 restoreHeight(cookieHeight); 481 } 482 } 483 484 // Resize once loading is finished 485 resizeNav(); 486 // Check if there's an anchor that we need to scroll into view. 487 // A delay is needed, because some browsers do not immediately scroll down to the anchor 488 window.setTimeout(offsetScrollForSticky, 100); 489 490 /* init the language selector based on user cookie for lang */ 491 loadLangPref(); 492 changeNavLang(getLangPref()); 493 494 /* setup event handlers to ensure the overflow menu is visible while picking lang */ 495 $("#language select") 496 .mousedown(function() { 497 $("div.morehover").addClass("hover"); }) 498 .blur(function() { 499 $("div.morehover").removeClass("hover"); }); 500 501 /* some global variable setup */ 502 resizePackagesNav = $("#resize-packages-nav"); 503 classesNav = $("#classes-nav"); 504 devdocNav = $("#devdoc-nav"); 505 506 var cookiePath = ""; 507 if (location.href.indexOf("/reference/") != -1) { 508 cookiePath = "reference_"; 509 } else if (location.href.indexOf("/guide/") != -1) { 510 cookiePath = "guide_"; 511 } else if (location.href.indexOf("/tools/") != -1) { 512 cookiePath = "tools_"; 513 } else if (location.href.indexOf("/training/") != -1) { 514 cookiePath = "training_"; 515 } else if (location.href.indexOf("/design/") != -1) { 516 cookiePath = "design_"; 517 } else if (location.href.indexOf("/distribute/") != -1) { 518 cookiePath = "distribute_"; 519 } 520 521 522 /* setup shadowbox for any videos that want it */ 523 var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video"); 524 if ($videoLinks.length) { 525 // if there's at least one, add the shadowbox HTML to the body 526 $('body').prepend( 527'<div id="video-container">'+ 528 '<div id="video-frame">'+ 529 '<div class="video-close">'+ 530 '<span id="icon-video-close" onclick="closeVideo()"> </span>'+ 531 '</div>'+ 532 '<div id="youTubePlayer"></div>'+ 533 '</div>'+ 534'</div>'); 535 536 // loads the IFrame Player API code asynchronously. 537 $.getScript("https://www.youtube.com/iframe_api"); 538 539 $videoLinks.each(function() { 540 var videoId = $(this).attr('href').split('?v=')[1]; 541 $(this).click(function(event) { 542 event.preventDefault(); 543 startYouTubePlayer(videoId); 544 }); 545 }); 546 } 547 548 // Responsive testing 549 var responsiveParam = location.href.match(/[?&]responsive=?(|true|false)/); 550 if (responsiveParam) { 551 localStorage['test-responsive'] = ['', 'true'].indexOf(responsiveParam) > -1; 552 } 553 if (localStorage['test-responsive']) { 554 $(document.body).addClass('responsive'); 555 } 556}); 557// END of the onload event 558 559 560var youTubePlayer; 561function onYouTubeIframeAPIReady() { 562} 563 564/* Returns the height the shadowbox video should be. It's based on the current 565 height of the "video-frame" element, which is 100% height for the window. 566 Then minus the margin so the video isn't actually the full window height. */ 567function getVideoHeight() { 568 var frameHeight = $("#video-frame").height(); 569 var marginTop = $("#video-frame").css('margin-top').split('px')[0]; 570 return frameHeight - (marginTop * 2); 571} 572 573var mPlayerPaused = false; 574 575function startYouTubePlayer(videoId) { 576 $("#video-container").show(); 577 $("#video-frame").show(); 578 mPlayerPaused = false; 579 580 // compute the size of the player so it's centered in window 581 var maxWidth = 940; // the width of the web site content 582 var videoAspect = .5625; // based on 1280x720 resolution 583 var maxHeight = maxWidth * videoAspect; 584 var videoHeight = getVideoHeight(); 585 var videoWidth = videoHeight / videoAspect; 586 if (videoWidth > maxWidth) { 587 videoWidth = maxWidth; 588 videoHeight = maxHeight; 589 } 590 $("#video-frame").css('width', videoWidth); 591 592 // check if we've already created this player 593 if (youTubePlayer == null) { 594 // check if there's a start time specified 595 var idAndHash = videoId.split("#"); 596 var startTime = 0; 597 if (idAndHash.length > 1) { 598 startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0; 599 } 600 // enable localized player 601 var lang = getLangPref(); 602 var captionsOn = lang == 'en' ? 0 : 1; 603 604 youTubePlayer = new YT.Player('youTubePlayer', { 605 height: videoHeight, 606 width: videoWidth, 607 videoId: idAndHash[0], 608 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn}, 609 events: { 610 'onReady': onPlayerReady, 611 'onStateChange': onPlayerStateChange 612 } 613 }); 614 } else { 615 // reset the size in case the user adjusted the window since last play 616 youTubePlayer.setSize(videoWidth, videoHeight); 617 // if a video different from the one already playing was requested, cue it up 618 if (videoId != youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]) { 619 youTubePlayer.cueVideoById(videoId); 620 } 621 youTubePlayer.playVideo(); 622 } 623} 624 625function onPlayerReady(event) { 626 event.target.playVideo(); 627 mPlayerPaused = false; 628} 629 630function closeVideo() { 631 try { 632 youTubePlayer.pauseVideo(); 633 } catch(e) { 634 } 635 $("#video-container").fadeOut(200); 636} 637 638/* Track youtube playback for analytics */ 639function onPlayerStateChange(event) { 640 // Video starts, send the video ID 641 if (event.data == YT.PlayerState.PLAYING) { 642 if (mPlayerPaused) { 643 ga('send', 'event', 'Videos', 'Resume', 644 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]); 645 } else { 646 // track the start playing event so we know from which page the video was selected 647 ga('send', 'event', 'Videos', 'Start: ' + 648 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0], 649 'on: ' + document.location.href); 650 } 651 mPlayerPaused = false; 652 } 653 // Video paused, send video ID and video elapsed time 654 if (event.data == YT.PlayerState.PAUSED) { 655 ga('send', 'event', 'Videos', 'Paused', 656 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0], 657 youTubePlayer.getCurrentTime()); 658 mPlayerPaused = true; 659 } 660 // Video finished, send video ID and video elapsed time 661 if (event.data == YT.PlayerState.ENDED) { 662 ga('send', 'event', 'Videos', 'Finished', 663 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0], 664 youTubePlayer.getCurrentTime()); 665 mPlayerPaused = true; 666 } 667} 668 669 670 671function initExpandableNavItems(rootTag) { 672 $(rootTag + ' li.nav-section .nav-section-header').click(function() { 673 var section = $(this).closest('li.nav-section'); 674 if (section.hasClass('expanded')) { 675 /* hide me and descendants */ 676 section.find('ul').slideUp(250, function() { 677 // remove 'expanded' class from my section and any children 678 section.closest('li').removeClass('expanded'); 679 $('li.nav-section', section).removeClass('expanded'); 680 resizeNav(); 681 }); 682 } else { 683 /* show me */ 684 // first hide all other siblings 685 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky'); 686 $others.removeClass('expanded').children('ul').slideUp(250); 687 688 // now expand me 689 section.closest('li').addClass('expanded'); 690 section.children('ul').slideDown(250, function() { 691 resizeNav(); 692 }); 693 } 694 }); 695 696 // Stop expand/collapse behavior when clicking on nav section links 697 // (since we're navigating away from the page) 698 // This selector captures the first instance of <a>, but not those with "#" as the href. 699 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) { 700 window.location.href = $(this).attr('href'); 701 return false; 702 }); 703} 704 705 706/** Create the list of breadcrumb links in the sticky header */ 707function buildBreadcrumbs() { 708 var $breadcrumbUl = $(".dac-header-crumbs"); 709 var primaryNavLink = ".dac-nav-list > .dac-nav-item > .dac-nav-link"; 710 711 // Add the secondary horizontal nav item, if provided 712 var $selectedSecondNav = $(".dac-nav-secondary .dac-nav-link.selected").clone() 713 .attr('class', 'dac-header-crumbs-link'); 714 715 if ($selectedSecondNav.length) { 716 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedSecondNav)); 717 } 718 719 // Add the primary horizontal nav 720 var $selectedFirstNav = $(primaryNavLink + ".selected, " + primaryNavLink + ".has-subnav").clone() 721 .attr('class', 'dac-header-crumbs-link'); 722 723 // If there's no header nav item, use the logo link and title from alt text 724 if ($selectedFirstNav.length < 1) { 725 $selectedFirstNav = $('<a class="dac-header-crumbs-link">') 726 .attr('href', $("div#header .logo a").attr('href')) 727 .text($("div#header .logo img").attr('alt')); 728 } 729 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedFirstNav)); 730} 731 732 733 734/** Highlight the current page in sidenav, expanding children as appropriate */ 735function highlightSidenav() { 736 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index) 737 if ($("ul#nav li.selected").length) { 738 unHighlightSidenav(); 739 } 740 // look for URL in sidenav, including the hash 741 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]'); 742 743 // If the selNavLink is still empty, look for it without the hash 744 if ($selNavLink.length == 0) { 745 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]'); 746 } 747 748 var $selListItem; 749 if ($selNavLink.length) { 750 // Find this page's <li> in sidenav and set selected 751 $selListItem = $selNavLink.closest('li'); 752 $selListItem.addClass('selected'); 753 754 // Traverse up the tree and expand all parent nav-sections 755 $selNavLink.parents('li.nav-section').each(function() { 756 $(this).addClass('expanded'); 757 $(this).children('ul').show(); 758 }); 759 } 760} 761 762function unHighlightSidenav() { 763 $("ul#nav li.selected").removeClass("selected"); 764 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide(); 765} 766 767function toggleFullscreen(enable) { 768 var delay = 20; 769 var enabled = true; 770 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]'); 771 if (enable) { 772 // Currently NOT USING fullscreen; enable fullscreen 773 stylesheet.removeAttr('disabled'); 774 $('#nav-swap .fullscreen').removeClass('disabled'); 775 $('#devdoc-nav').css({left:''}); 776 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch 777 enabled = true; 778 } else { 779 // Currently USING fullscreen; disable fullscreen 780 stylesheet.attr('disabled', 'disabled'); 781 $('#nav-swap .fullscreen').addClass('disabled'); 782 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch 783 enabled = false; 784 } 785 writeCookie("fullscreen", enabled, null); 786 setNavBarLeftPos(); 787 resizeNav(delay); 788 updateSideNavPosition(); 789 setTimeout(initSidenavHeightResize,delay); 790} 791 792 793function setNavBarLeftPos() { 794 navBarLeftPos = $('#body-content').offset().left; 795} 796 797 798function updateSideNavPosition() { 799 var newLeft = $(window).scrollLeft() - navBarLeftPos; 800 $('#devdoc-nav').css({left: -newLeft}); 801 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('padding-left')))}); 802} 803 804// TODO: use $(document).ready instead 805function addLoadEvent(newfun) { 806 var current = window.onload; 807 if (typeof window.onload != 'function') { 808 window.onload = newfun; 809 } else { 810 window.onload = function() { 811 current(); 812 newfun(); 813 } 814 } 815} 816 817var agent = navigator['userAgent'].toLowerCase(); 818// If a mobile phone, set flag and do mobile setup 819if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod 820 (agent.indexOf("blackberry") != -1) || 821 (agent.indexOf("webos") != -1) || 822 (agent.indexOf("mini") != -1)) { // opera mini browsers 823 isMobile = true; 824} 825 826 827$(document).ready(function() { 828 $("pre:not(.no-pretty-print)").addClass("prettyprint"); 829 prettyPrint(); 830}); 831 832 833 834 835/* ######### RESIZE THE SIDENAV HEIGHT ########## */ 836 837function resizeNav(delay) { 838 var $nav = $("#devdoc-nav"); 839 var $window = $(window); 840 var navHeight; 841 842 // Get the height of entire window and the total header height. 843 // Then figure out based on scroll position whether the header is visible 844 var windowHeight = $window.height(); 845 var scrollTop = $window.scrollTop(); 846 var headerHeight = $('#header-wrapper').outerHeight(); 847 var headerVisible = scrollTop < stickyTop; 848 849 // get the height of space between nav and top of window. 850 // Could be either margin or top position, depending on whether the nav is fixed. 851 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1; 852 // add 1 for the #side-nav bottom margin 853 854 // Depending on whether the header is visible, set the side nav's height. 855 if (headerVisible) { 856 // The sidenav height grows as the header goes off screen 857 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin; 858 } else { 859 // Once header is off screen, the nav height is almost full window height 860 navHeight = windowHeight - topMargin; 861 } 862 863 864 865 $scrollPanes = $(".scroll-pane"); 866 if ($scrollPanes.length > 1) { 867 // subtract the height of the api level widget and nav swapper from the available nav height 868 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true)); 869 870 $("#swapper").css({height:navHeight + "px"}); 871 if ($("#nav-tree").is(":visible")) { 872 $("#nav-tree").css({height:navHeight}); 873 } 874 875 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px"; 876 //subtract 10px to account for drag bar 877 878 // if the window becomes small enough to make the class panel height 0, 879 // then the package panel should begin to shrink 880 if (parseInt(classesHeight) <= 0) { 881 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar 882 $("#packages-nav").css({height:navHeight - 10}); 883 } 884 885 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'}); 886 $("#classes-nav .jspContainer").css({height:classesHeight}); 887 888 889 } else { 890 $nav.height(navHeight); 891 } 892 893 if (delay) { 894 updateFromResize = true; 895 delayedReInitScrollbars(delay); 896 } else { 897 reInitScrollbars(); 898 } 899 900} 901 902var updateScrollbars = false; 903var updateFromResize = false; 904 905/* Re-initialize the scrollbars to account for changed nav size. 906 * This method postpones the actual update by a 1/4 second in order to optimize the 907 * scroll performance while the header is still visible, because re-initializing the 908 * scroll panes is an intensive process. 909 */ 910function delayedReInitScrollbars(delay) { 911 // If we're scheduled for an update, but have received another resize request 912 // before the scheduled resize has occured, just ignore the new request 913 // (and wait for the scheduled one). 914 if (updateScrollbars && updateFromResize) { 915 updateFromResize = false; 916 return; 917 } 918 919 // We're scheduled for an update and the update request came from this method's setTimeout 920 if (updateScrollbars && !updateFromResize) { 921 reInitScrollbars(); 922 updateScrollbars = false; 923 } else { 924 updateScrollbars = true; 925 updateFromResize = false; 926 setTimeout('delayedReInitScrollbars()',delay); 927 } 928} 929 930/* Re-initialize the scrollbars to account for changed nav size. */ 931function reInitScrollbars() { 932 var pane = $(".scroll-pane").each(function(){ 933 var api = $(this).data('jsp'); 934 if (!api) { setTimeout(reInitScrollbars,300); return;} 935 api.reinitialise( {verticalGutter:0} ); 936 }); 937 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller 938} 939 940 941/* Resize the height of the nav panels in the reference, 942 * and save the new size to a cookie */ 943function saveNavPanels() { 944 var basePath = getBaseUri(location.pathname); 945 var section = basePath.substring(1,basePath.indexOf("/",1)); 946 writeCookie("height", resizePackagesNav.css("height"), section); 947} 948 949 950 951function restoreHeight(packageHeight) { 952 $("#resize-packages-nav").height(packageHeight); 953 $("#packages-nav").height(packageHeight); 954 // var classesHeight = navHeight - packageHeight; 955 // $("#classes-nav").css({height:classesHeight}); 956 // $("#classes-nav .jspContainer").css({height:classesHeight}); 957} 958 959 960 961/* ######### END RESIZE THE SIDENAV HEIGHT ########## */ 962 963 964 965 966 967/** Scroll the jScrollPane to make the currently selected item visible 968 This is called when the page finished loading. */ 969function scrollIntoView(nav) { 970 var $nav = $("#"+nav); 971 var element = $nav.jScrollPane({/* ...settings... */}); 972 var api = element.data('jsp'); 973 974 if ($nav.is(':visible')) { 975 var $selected = $(".selected", $nav); 976 if ($selected.length == 0) { 977 // If no selected item found, exit 978 return; 979 } 980 // get the selected item's offset from its container nav by measuring the item's offset 981 // relative to the document then subtract the container nav's offset relative to the document 982 var selectedOffset = $selected.offset().top - $nav.offset().top; 983 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item 984 // if it's more than 80% down the nav 985 // scroll the item up by an amount equal to 80% the container nav's height 986 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false); 987 } 988 } 989} 990 991 992 993 994 995 996/* Show popup dialogs */ 997function showDialog(id) { 998 $dialog = $("#"+id); 999 $dialog.prepend('<div class="box-border"><div class="top"> <div class="left"></div> <div class="right"></div></div><div class="bottom"> <div class="left"></div> <div class="right"></div> </div> </div>'); 1000 $dialog.wrapInner('<div/>'); 1001 $dialog.removeClass("hide"); 1002} 1003 1004 1005 1006 1007 1008/* ######### COOKIES! ########## */ 1009 1010function readCookie(cookie) { 1011 var myCookie = cookie_namespace+"_"+cookie+"="; 1012 if (document.cookie) { 1013 var index = document.cookie.indexOf(myCookie); 1014 if (index != -1) { 1015 var valStart = index + myCookie.length; 1016 var valEnd = document.cookie.indexOf(";", valStart); 1017 if (valEnd == -1) { 1018 valEnd = document.cookie.length; 1019 } 1020 var val = document.cookie.substring(valStart, valEnd); 1021 return val; 1022 } 1023 } 1024 return 0; 1025} 1026 1027function writeCookie(cookie, val, section) { 1028 if (val==undefined) return; 1029 section = section == null ? "_" : "_"+section+"_"; 1030 var age = 2*365*24*60*60; // set max-age to 2 years 1031 var cookieValue = cookie_namespace + section + cookie + "=" + val 1032 + "; max-age=" + age +"; path=/"; 1033 document.cookie = cookieValue; 1034} 1035 1036/* ######### END COOKIES! ########## */ 1037 1038 1039var sticky = false; 1040var stickyTop; 1041var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll 1042/* Sets the vertical scoll position at which the sticky bar should appear. 1043 This method is called to reset the position when search results appear or hide */ 1044function setStickyTop() { 1045 stickyTop = $('#header-wrapper').outerHeight() - $('#header > .dac-header-inner').outerHeight(); 1046} 1047 1048/* 1049 * Displays sticky nav bar on pages when dac header scrolls out of view 1050 */ 1051$(window).scroll(function(event) { 1052 1053 setStickyTop(); 1054 var hiding = false; 1055 var $headerEl = $('#header'); 1056 // Exit if there's no sidenav 1057 if ($('#side-nav').length == 0) return; 1058 // Exit if the mouse target is a DIV, because that means the event is coming 1059 // from a scrollable div and so there's no need to make adjustments to our layout 1060 if ($(event.target).nodeName == "DIV") { 1061 return; 1062 } 1063 1064 var top = $(window).scrollTop(); 1065 // we set the navbar fixed when the scroll position is beyond the height of the site header... 1066 var shouldBeSticky = top >= stickyTop; 1067 // ... except if the document content is shorter than the sidenav height. 1068 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing) 1069 if ($("#doc-col").height() < $("#side-nav").height()) { 1070 shouldBeSticky = false; 1071 } 1072 // Account for horizontal scroll 1073 var scrollLeft = $(window).scrollLeft(); 1074 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match 1075 if (sticky && (scrollLeft != prevScrollLeft)) { 1076 updateSideNavPosition(); 1077 prevScrollLeft = scrollLeft; 1078 } 1079 1080 // Don't continue if the header is sufficently far away 1081 // (to avoid intensive resizing that slows scrolling) 1082 if (sticky == shouldBeSticky) { 1083 return; 1084 } 1085 1086 // If sticky header visible and position is now near top, hide sticky 1087 if (sticky && !shouldBeSticky) { 1088 sticky = false; 1089 hiding = true; 1090 // make the sidenav static again 1091 $('#devdoc-nav') 1092 .removeClass('fixed') 1093 .css({'width':'auto','margin':''}) 1094 .prependTo('#side-nav'); 1095 // delay hide the sticky 1096 $headerEl.removeClass('is-sticky'); 1097 hiding = false; 1098 1099 // update the sidenaav position for side scrolling 1100 updateSideNavPosition(); 1101 } else if (!sticky && shouldBeSticky) { 1102 sticky = true; 1103 $headerEl.addClass('is-sticky'); 1104 1105 // make the sidenav fixed 1106 var width = $('#devdoc-nav').width(); 1107 $('#devdoc-nav') 1108 .addClass('fixed') 1109 .css({'width':width+'px'}) 1110 .prependTo('#body-content'); 1111 1112 // update the sidenaav position for side scrolling 1113 updateSideNavPosition(); 1114 1115 } else if (hiding && top < 15) { 1116 $headerEl.removeClass('is-sticky'); 1117 hiding = false; 1118 } 1119 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance 1120}); 1121 1122/* 1123 * Manages secion card states and nav resize to conclude loading 1124 */ 1125(function() { 1126 $(document).ready(function() { 1127 1128 // Stack hover states 1129 $('.section-card-menu').each(function(index, el) { 1130 var height = $(el).height(); 1131 $(el).css({height:height+'px', position:'relative'}); 1132 var $cardInfo = $(el).find('.card-info'); 1133 1134 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'}); 1135 }); 1136 1137 }); 1138 1139})(); 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154/* MISC LIBRARY FUNCTIONS */ 1155 1156 1157 1158 1159 1160function toggle(obj, slide) { 1161 var ul = $("ul:first", obj); 1162 var li = ul.parent(); 1163 if (li.hasClass("closed")) { 1164 if (slide) { 1165 ul.slideDown("fast"); 1166 } else { 1167 ul.show(); 1168 } 1169 li.removeClass("closed"); 1170 li.addClass("open"); 1171 $(".toggle-img", li).attr("title", "hide pages"); 1172 } else { 1173 ul.slideUp("fast"); 1174 li.removeClass("open"); 1175 li.addClass("closed"); 1176 $(".toggle-img", li).attr("title", "show pages"); 1177 } 1178} 1179 1180 1181function buildToggleLists() { 1182 $(".toggle-list").each( 1183 function(i) { 1184 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>"); 1185 $(this).addClass("closed"); 1186 }); 1187} 1188 1189 1190 1191function hideNestedItems(list, toggle) { 1192 $list = $(list); 1193 // hide nested lists 1194 if($list.hasClass('showing')) { 1195 $("li ol", $list).hide('fast'); 1196 $list.removeClass('showing'); 1197 // show nested lists 1198 } else { 1199 $("li ol", $list).show('fast'); 1200 $list.addClass('showing'); 1201 } 1202 $(".more,.less",$(toggle)).toggle(); 1203} 1204 1205 1206/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */ 1207function setupIdeDocToggle() { 1208 $( "select.ide" ).change(function() { 1209 var selected = $(this).find("option:selected").attr("value"); 1210 $(".select-ide").hide(); 1211 $(".select-ide."+selected).show(); 1212 1213 $("select.ide").val(selected); 1214 }); 1215} 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240/* REFERENCE NAV SWAP */ 1241 1242 1243function getNavPref() { 1244 var v = readCookie('reference_nav'); 1245 if (v != NAV_PREF_TREE) { 1246 v = NAV_PREF_PANELS; 1247 } 1248 return v; 1249} 1250 1251function chooseDefaultNav() { 1252 nav_pref = getNavPref(); 1253 if (nav_pref == NAV_PREF_TREE) { 1254 $("#nav-panels").toggle(); 1255 $("#panel-link").toggle(); 1256 $("#nav-tree").toggle(); 1257 $("#tree-link").toggle(); 1258 } 1259} 1260 1261function swapNav() { 1262 if (nav_pref == NAV_PREF_TREE) { 1263 nav_pref = NAV_PREF_PANELS; 1264 } else { 1265 nav_pref = NAV_PREF_TREE; 1266 init_default_navtree(toRoot); 1267 } 1268 writeCookie("nav", nav_pref, "reference"); 1269 1270 $("#nav-panels").toggle(); 1271 $("#panel-link").toggle(); 1272 $("#nav-tree").toggle(); 1273 $("#tree-link").toggle(); 1274 1275 resizeNav(); 1276 1277 // Gross nasty hack to make tree view show up upon first swap by setting height manually 1278 $("#nav-tree .jspContainer:visible") 1279 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'}); 1280 // Another nasty hack to make the scrollbar appear now that we have height 1281 resizeNav(); 1282 1283 if ($("#nav-tree").is(':visible')) { 1284 scrollIntoView("nav-tree"); 1285 } else { 1286 scrollIntoView("packages-nav"); 1287 scrollIntoView("classes-nav"); 1288 } 1289} 1290 1291 1292 1293/* ############################################ */ 1294/* ########## LOCALIZATION ############ */ 1295/* ############################################ */ 1296 1297function getBaseUri(uri) { 1298 var intlUrl = (uri.substring(0,6) == "/intl/"); 1299 if (intlUrl) { 1300 base = uri.substring(uri.indexOf('intl/')+5,uri.length); 1301 base = base.substring(base.indexOf('/')+1, base.length); 1302 //alert("intl, returning base url: /" + base); 1303 return ("/" + base); 1304 } else { 1305 //alert("not intl, returning uri as found."); 1306 return uri; 1307 } 1308} 1309 1310function requestAppendHL(uri) { 1311//append "?hl=<lang> to an outgoing request (such as to blog) 1312 var lang = getLangPref(); 1313 if (lang) { 1314 var q = 'hl=' + lang; 1315 uri += '?' + q; 1316 window.location = uri; 1317 return false; 1318 } else { 1319 return true; 1320 } 1321} 1322 1323 1324function changeNavLang(lang) { 1325 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]"); 1326 $links.each(function(i){ // for each link with a translation 1327 var $link = $(this); 1328 if (lang != "en") { // No need to worry about English, because a language change invokes new request 1329 // put the desired language from the attribute as the text 1330 $link.text($link.attr(lang+"-lang")) 1331 } 1332 }); 1333} 1334 1335function changeLangPref(lang, submit) { 1336 writeCookie("pref_lang", lang, null); 1337 1338 // ####### TODO: Remove this condition once we're stable on devsite ####### 1339 // This condition is only needed if we still need to support legacy GAE server 1340 if (devsite) { 1341 // Switch language when on Devsite server 1342 if (submit) { 1343 $("#setlang").submit(); 1344 } 1345 } else { 1346 // Switch language when on legacy GAE server 1347 if (submit) { 1348 window.location = getBaseUri(location.pathname); 1349 } 1350 } 1351} 1352 1353function loadLangPref() { 1354 var lang = readCookie("pref_lang"); 1355 if (lang != 0) { 1356 $("#language").find("option[value='"+lang+"']").attr("selected",true); 1357 } 1358} 1359 1360function getLangPref() { 1361 var lang = $("#language").find(":selected").attr("value"); 1362 if (!lang) { 1363 lang = readCookie("pref_lang"); 1364 } 1365 return (lang != 0) ? lang : 'en'; 1366} 1367 1368/* ########## END LOCALIZATION ############ */ 1369 1370 1371 1372 1373 1374 1375/* Used to hide and reveal supplemental content, such as long code samples. 1376 See the companion CSS in android-developer-docs.css */ 1377function toggleContent(obj) { 1378 var div = $(obj).closest(".toggle-content"); 1379 var toggleMe = $(".toggle-content-toggleme:eq(0)",div); 1380 if (div.hasClass("closed")) { // if it's closed, open it 1381 toggleMe.slideDown(); 1382 $(".toggle-content-text:eq(0)", obj).toggle(); 1383 div.removeClass("closed").addClass("open"); 1384 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot 1385 + "assets/images/triangle-opened.png"); 1386 } else { // if it's open, close it 1387 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow 1388 $(".toggle-content-text:eq(0)", obj).toggle(); 1389 div.removeClass("open").addClass("closed"); 1390 div.find(".toggle-content").removeClass("open").addClass("closed") 1391 .find(".toggle-content-toggleme").hide(); 1392 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot 1393 + "assets/images/triangle-closed.png"); 1394 }); 1395 } 1396 return false; 1397} 1398 1399 1400/* New version of expandable content */ 1401function toggleExpandable(link,id) { 1402 if($(id).is(':visible')) { 1403 $(id).slideUp(); 1404 $(link).removeClass('expanded'); 1405 } else { 1406 $(id).slideDown(); 1407 $(link).addClass('expanded'); 1408 } 1409} 1410 1411function hideExpandable(ids) { 1412 $(ids).slideUp(); 1413 $(ids).prev('h4').find('a.expandable').removeClass('expanded'); 1414} 1415 1416 1417 1418 1419 1420/* 1421 * Slideshow 1.0 1422 * Used on /index.html and /develop/index.html for carousel 1423 * 1424 * Sample usage: 1425 * HTML - 1426 * <div class="slideshow-container"> 1427 * <a href="" class="slideshow-prev">Prev</a> 1428 * <a href="" class="slideshow-next">Next</a> 1429 * <ul> 1430 * <li class="item"><img src="images/marquee1.jpg"></li> 1431 * <li class="item"><img src="images/marquee2.jpg"></li> 1432 * <li class="item"><img src="images/marquee3.jpg"></li> 1433 * <li class="item"><img src="images/marquee4.jpg"></li> 1434 * </ul> 1435 * </div> 1436 * 1437 * <script type="text/javascript"> 1438 * $('.slideshow-container').dacSlideshow({ 1439 * auto: true, 1440 * btnPrev: '.slideshow-prev', 1441 * btnNext: '.slideshow-next' 1442 * }); 1443 * </script> 1444 * 1445 * Options: 1446 * btnPrev: optional identifier for previous button 1447 * btnNext: optional identifier for next button 1448 * btnPause: optional identifier for pause button 1449 * auto: whether or not to auto-proceed 1450 * speed: animation speed 1451 * autoTime: time between auto-rotation 1452 * easing: easing function for transition 1453 * start: item to select by default 1454 * scroll: direction to scroll in 1455 * pagination: whether or not to include dotted pagination 1456 * 1457 */ 1458 1459 (function($) { 1460 $.fn.dacSlideshow = function(o) { 1461 1462 //Options - see above 1463 o = $.extend({ 1464 btnPrev: null, 1465 btnNext: null, 1466 btnPause: null, 1467 auto: true, 1468 speed: 500, 1469 autoTime: 12000, 1470 easing: null, 1471 start: 0, 1472 scroll: 1, 1473 pagination: true 1474 1475 }, o || {}); 1476 1477 //Set up a carousel for each 1478 return this.each(function() { 1479 1480 var running = false; 1481 var animCss = o.vertical ? "top" : "left"; 1482 var sizeCss = o.vertical ? "height" : "width"; 1483 var div = $(this); 1484 var ul = $("ul", div); 1485 var tLi = $("li", ul); 1486 var tl = tLi.size(); 1487 var timer = null; 1488 1489 var li = $("li", ul); 1490 var itemLength = li.size(); 1491 var curr = o.start; 1492 1493 li.css({float: o.vertical ? "none" : "left"}); 1494 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"}); 1495 div.css({position: "relative", "z-index": "2", left: "0px"}); 1496 1497 var liSize = o.vertical ? height(li) : width(li); 1498 var ulSize = liSize * itemLength; 1499 var divSize = liSize; 1500 1501 li.css({width: li.width(), height: li.height()}); 1502 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize)); 1503 1504 div.css(sizeCss, divSize+"px"); 1505 1506 //Pagination 1507 if (o.pagination) { 1508 var pagination = $("<div class='pagination'></div>"); 1509 var pag_ul = $("<ul></ul>"); 1510 if (tl > 1) { 1511 for (var i=0;i<tl;i++) { 1512 var li = $("<li>"+i+"</li>"); 1513 pag_ul.append(li); 1514 if (i==o.start) li.addClass('active'); 1515 li.click(function() { 1516 go(parseInt($(this).text())); 1517 }) 1518 } 1519 pagination.append(pag_ul); 1520 div.append(pagination); 1521 } 1522 } 1523 1524 //Previous button 1525 if(o.btnPrev) 1526 $(o.btnPrev).click(function(e) { 1527 e.preventDefault(); 1528 return go(curr-o.scroll); 1529 }); 1530 1531 //Next button 1532 if(o.btnNext) 1533 $(o.btnNext).click(function(e) { 1534 e.preventDefault(); 1535 return go(curr+o.scroll); 1536 }); 1537 1538 //Pause button 1539 if(o.btnPause) 1540 $(o.btnPause).click(function(e) { 1541 e.preventDefault(); 1542 if ($(this).hasClass('paused')) { 1543 startRotateTimer(); 1544 } else { 1545 pauseRotateTimer(); 1546 } 1547 }); 1548 1549 //Auto rotation 1550 if(o.auto) startRotateTimer(); 1551 1552 function startRotateTimer() { 1553 clearInterval(timer); 1554 timer = setInterval(function() { 1555 if (curr == tl-1) { 1556 go(0); 1557 } else { 1558 go(curr+o.scroll); 1559 } 1560 }, o.autoTime); 1561 $(o.btnPause).removeClass('paused'); 1562 } 1563 1564 function pauseRotateTimer() { 1565 clearInterval(timer); 1566 $(o.btnPause).addClass('paused'); 1567 } 1568 1569 //Go to an item 1570 function go(to) { 1571 if(!running) { 1572 1573 if(to<0) { 1574 to = itemLength-1; 1575 } else if (to>itemLength-1) { 1576 to = 0; 1577 } 1578 curr = to; 1579 1580 running = true; 1581 1582 ul.animate( 1583 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing, 1584 function() { 1585 running = false; 1586 } 1587 ); 1588 1589 $(o.btnPrev + "," + o.btnNext).removeClass("disabled"); 1590 $( (curr-o.scroll<0 && o.btnPrev) 1591 || 1592 (curr+o.scroll > itemLength && o.btnNext) 1593 || 1594 [] 1595 ).addClass("disabled"); 1596 1597 1598 var nav_items = $('li', pagination); 1599 nav_items.removeClass('active'); 1600 nav_items.eq(to).addClass('active'); 1601 1602 1603 } 1604 if(o.auto) startRotateTimer(); 1605 return false; 1606 }; 1607 }); 1608 }; 1609 1610 function css(el, prop) { 1611 return parseInt($.css(el[0], prop)) || 0; 1612 }; 1613 function width(el) { 1614 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight'); 1615 }; 1616 function height(el) { 1617 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom'); 1618 }; 1619 1620 })(jQuery); 1621 1622 1623/* 1624 * dacSlideshow 1.0 1625 * Used on develop/index.html for side-sliding tabs 1626 * 1627 * Sample usage: 1628 * HTML - 1629 * <div class="slideshow-container"> 1630 * <a href="" class="slideshow-prev">Prev</a> 1631 * <a href="" class="slideshow-next">Next</a> 1632 * <ul> 1633 * <li class="item"><img src="images/marquee1.jpg"></li> 1634 * <li class="item"><img src="images/marquee2.jpg"></li> 1635 * <li class="item"><img src="images/marquee3.jpg"></li> 1636 * <li class="item"><img src="images/marquee4.jpg"></li> 1637 * </ul> 1638 * </div> 1639 * 1640 * <script type="text/javascript"> 1641 * $('.slideshow-container').dacSlideshow({ 1642 * auto: true, 1643 * btnPrev: '.slideshow-prev', 1644 * btnNext: '.slideshow-next' 1645 * }); 1646 * </script> 1647 * 1648 * Options: 1649 * btnPrev: optional identifier for previous button 1650 * btnNext: optional identifier for next button 1651 * auto: whether or not to auto-proceed 1652 * speed: animation speed 1653 * autoTime: time between auto-rotation 1654 * easing: easing function for transition 1655 * start: item to select by default 1656 * scroll: direction to scroll in 1657 * pagination: whether or not to include dotted pagination 1658 * 1659 */ 1660 (function($) { 1661 $.fn.dacTabbedList = function(o) { 1662 1663 //Options - see above 1664 o = $.extend({ 1665 speed : 250, 1666 easing: null, 1667 nav_id: null, 1668 frame_id: null 1669 }, o || {}); 1670 1671 //Set up a carousel for each 1672 return this.each(function() { 1673 1674 var curr = 0; 1675 var running = false; 1676 var animCss = "margin-left"; 1677 var sizeCss = "width"; 1678 var div = $(this); 1679 1680 var nav = $(o.nav_id, div); 1681 var nav_li = $("li", nav); 1682 var nav_size = nav_li.size(); 1683 var frame = div.find(o.frame_id); 1684 var content_width = $(frame).find('ul').width(); 1685 //Buttons 1686 $(nav_li).click(function(e) { 1687 go($(nav_li).index($(this))); 1688 }) 1689 1690 //Go to an item 1691 function go(to) { 1692 if(!running) { 1693 curr = to; 1694 running = true; 1695 1696 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing, 1697 function() { 1698 running = false; 1699 } 1700 ); 1701 1702 1703 nav_li.removeClass('active'); 1704 nav_li.eq(to).addClass('active'); 1705 1706 1707 } 1708 return false; 1709 }; 1710 }); 1711 }; 1712 1713 function css(el, prop) { 1714 return parseInt($.css(el[0], prop)) || 0; 1715 }; 1716 function width(el) { 1717 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight'); 1718 }; 1719 function height(el) { 1720 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom'); 1721 }; 1722 1723 })(jQuery); 1724 1725 1726 1727 1728 1729/* ######################################################## */ 1730/* ################ SEARCH SUGGESTIONS ################## */ 1731/* ######################################################## */ 1732 1733 1734 1735var gSelectedIndex = -1; // the index position of currently highlighted suggestion 1736var gSelectedColumn = -1; // which column of suggestion lists is currently focused 1737 1738var gMatches = new Array(); 1739var gLastText = ""; 1740var gInitialized = false; 1741var ROW_COUNT_FRAMEWORK = 20; // max number of results in list 1742var gListLength = 0; 1743 1744 1745var gGoogleMatches = new Array(); 1746var ROW_COUNT_GOOGLE = 15; // max number of results in list 1747var gGoogleListLength = 0; 1748 1749var gDocsMatches = new Array(); 1750var ROW_COUNT_DOCS = 100; // max number of results in list 1751var gDocsListLength = 0; 1752 1753function onSuggestionClick(link) { 1754 // When user clicks a suggested document, track it 1755 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'), 1756 'query: ' + $("#search_autocomplete").val().toLowerCase()); 1757} 1758 1759function set_item_selected($li, selected) 1760{ 1761 if (selected) { 1762 $li.attr('class','jd-autocomplete jd-selected'); 1763 } else { 1764 $li.attr('class','jd-autocomplete'); 1765 } 1766} 1767 1768function set_item_values(toroot, $li, match) 1769{ 1770 var $link = $('a',$li); 1771 $link.html(match.__hilabel || match.label); 1772 $link.attr('href',toroot + match.link); 1773} 1774 1775function set_item_values_jd(toroot, $li, match) 1776{ 1777 var $link = $('a',$li); 1778 $link.html(match.title); 1779 $link.attr('href',toroot + match.url); 1780} 1781 1782function new_suggestion($list) { 1783 var $li = $("<li class='jd-autocomplete'></li>"); 1784 $list.append($li); 1785 1786 $li.mousedown(function() { 1787 window.location = this.firstChild.getAttribute("href"); 1788 }); 1789 $li.mouseover(function() { 1790 $('.search_filtered_wrapper li').removeClass('jd-selected'); 1791 $(this).addClass('jd-selected'); 1792 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered')); 1793 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this); 1794 }); 1795 $li.append("<a onclick='onSuggestionClick(this)'></a>"); 1796 $li.attr('class','show-item'); 1797 return $li; 1798} 1799 1800function sync_selection_table(toroot) 1801{ 1802 var $li; //list item jquery object 1803 var i; //list item iterator 1804 1805 // if there are NO results at all, hide all columns 1806 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) { 1807 $('.suggest-card').hide(300); 1808 return; 1809 } 1810 1811 // if there are api results 1812 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) { 1813 // reveal suggestion list 1814 $('.suggest-card.reference').show(); 1815 var listIndex = 0; // list index position 1816 1817 // reset the lists 1818 $(".suggest-card.reference li").remove(); 1819 1820 // ########### ANDROID RESULTS ############# 1821 if (gMatches.length > 0) { 1822 1823 // determine android results to show 1824 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ? 1825 gMatches.length : ROW_COUNT_FRAMEWORK; 1826 for (i=0; i<gListLength; i++) { 1827 var $li = new_suggestion($(".suggest-card.reference ul")); 1828 set_item_values(toroot, $li, gMatches[i]); 1829 set_item_selected($li, i == gSelectedIndex); 1830 } 1831 } 1832 1833 // ########### GOOGLE RESULTS ############# 1834 if (gGoogleMatches.length > 0) { 1835 // show header for list 1836 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>"); 1837 1838 // determine google results to show 1839 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE; 1840 for (i=0; i<gGoogleListLength; i++) { 1841 var $li = new_suggestion($(".suggest-card.reference ul")); 1842 set_item_values(toroot, $li, gGoogleMatches[i]); 1843 set_item_selected($li, i == gSelectedIndex); 1844 } 1845 } 1846 } else { 1847 $('.suggest-card.reference').hide(); 1848 } 1849 1850 // ########### JD DOC RESULTS ############# 1851 if (gDocsMatches.length > 0) { 1852 // reset the lists 1853 $(".suggest-card:not(.reference) li").remove(); 1854 1855 // determine google results to show 1856 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC: 1857 // The order must match the reverse order that each section appears as a card in 1858 // the suggestion UI... this may be only for the "develop" grouped items though. 1859 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS; 1860 for (i=0; i<gDocsListLength; i++) { 1861 var sugg = gDocsMatches[i]; 1862 var $li; 1863 if (sugg.type == "design") { 1864 $li = new_suggestion($(".suggest-card.design ul")); 1865 } else 1866 if (sugg.type == "distribute") { 1867 $li = new_suggestion($(".suggest-card.distribute ul")); 1868 } else 1869 if (sugg.type == "samples") { 1870 $li = new_suggestion($(".suggest-card.develop .child-card.samples")); 1871 } else 1872 if (sugg.type == "training") { 1873 $li = new_suggestion($(".suggest-card.develop .child-card.training")); 1874 } else 1875 if (sugg.type == "about"||"guide"||"tools"||"google") { 1876 $li = new_suggestion($(".suggest-card.develop .child-card.guides")); 1877 } else { 1878 continue; 1879 } 1880 1881 set_item_values_jd(toroot, $li, sugg); 1882 set_item_selected($li, i == gSelectedIndex); 1883 } 1884 1885 // add heading and show or hide card 1886 if ($(".suggest-card.design li").length > 0) { 1887 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>"); 1888 $(".suggest-card.design").show(300); 1889 } else { 1890 $('.suggest-card.design').hide(300); 1891 } 1892 if ($(".suggest-card.distribute li").length > 0) { 1893 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>"); 1894 $(".suggest-card.distribute").show(300); 1895 } else { 1896 $('.suggest-card.distribute').hide(300); 1897 } 1898 if ($(".child-card.guides li").length > 0) { 1899 $(".child-card.guides").prepend("<li class='header'>Guides:</li>"); 1900 $(".child-card.guides li").appendTo(".suggest-card.develop ul"); 1901 } 1902 if ($(".child-card.training li").length > 0) { 1903 $(".child-card.training").prepend("<li class='header'>Training:</li>"); 1904 $(".child-card.training li").appendTo(".suggest-card.develop ul"); 1905 } 1906 if ($(".child-card.samples li").length > 0) { 1907 $(".child-card.samples").prepend("<li class='header'>Samples:</li>"); 1908 $(".child-card.samples li").appendTo(".suggest-card.develop ul"); 1909 } 1910 1911 if ($(".suggest-card.develop li").length > 0) { 1912 $(".suggest-card.develop").show(300); 1913 } else { 1914 $('.suggest-card.develop').hide(300); 1915 } 1916 1917 } else { 1918 $('.suggest-card:not(.reference)').hide(300); 1919 } 1920} 1921 1922/** Called by the search input's onkeydown and onkeyup events. 1923 * Handles navigation with keyboard arrows, Enter key to invoke search, 1924 * otherwise invokes search suggestions on key-up event. 1925 * @param e The JS event 1926 * @param kd True if the event is key-down 1927 * @param toroot A string for the site's root path 1928 * @returns True if the event should bubble up 1929 */ 1930function search_changed(e, kd, toroot) 1931{ 1932 var currentLang = getLangPref(); 1933 var search = document.getElementById("search_autocomplete"); 1934 var text = search.value.replace(/(^ +)|( +$)/g, ''); 1935 // get the ul hosting the currently selected item 1936 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0; 1937 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible"); 1938 var $selectedUl = $columns[gSelectedColumn]; 1939 1940 // show/hide the close button 1941 if (text != '') { 1942 $("#search-close").removeClass("hide"); 1943 } else { 1944 $("#search-close").addClass("hide"); 1945 } 1946 // 27 = esc 1947 if (e.keyCode == 27) { 1948 // close all search results 1949 if (kd) $('#search-close').trigger('click'); 1950 return true; 1951 } 1952 // 13 = enter 1953 else if (e.keyCode == 13) { 1954 if (gSelectedIndex < 0) { 1955 $('.suggest-card').hide(); 1956 if ($("#searchResults").is(":hidden") && (search.value != "")) { 1957 // if results aren't showing (and text not empty), return true to allow search to execute 1958 $('body,html').animate({scrollTop:0}, '500', 'swing'); 1959 return true; 1960 } else { 1961 // otherwise, results are already showing, so allow ajax to auto refresh the results 1962 // and ignore this Enter press to avoid the reload. 1963 return false; 1964 } 1965 } else if (kd && gSelectedIndex >= 0) { 1966 // click the link corresponding to selected item 1967 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click(); 1968 return false; 1969 } 1970 } 1971 // If Google results are showing, return true to allow ajax search to execute 1972 else if ($("#searchResults").is(":visible")) { 1973 // Also, if search_results is scrolled out of view, scroll to top to make results visible 1974 if ((sticky ) && (search.value != "")) { 1975 $('body,html').animate({scrollTop:0}, '500', 'swing'); 1976 } 1977 return true; 1978 } 1979 // 38 UP ARROW 1980 else if (kd && (e.keyCode == 38)) { 1981 // if the next item is a header, skip it 1982 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) { 1983 gSelectedIndex--; 1984 } 1985 if (gSelectedIndex >= 0) { 1986 $('li', $selectedUl).removeClass('jd-selected'); 1987 gSelectedIndex--; 1988 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected'); 1989 // If user reaches top, reset selected column 1990 if (gSelectedIndex < 0) { 1991 gSelectedColumn = -1; 1992 } 1993 } 1994 return false; 1995 } 1996 // 40 DOWN ARROW 1997 else if (kd && (e.keyCode == 40)) { 1998 // if the next item is a header, skip it 1999 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) { 2000 gSelectedIndex++; 2001 } 2002 if ((gSelectedIndex < $("li", $selectedUl).length-1) || 2003 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) { 2004 $('li', $selectedUl).removeClass('jd-selected'); 2005 gSelectedIndex++; 2006 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected'); 2007 } 2008 return false; 2009 } 2010 // Consider left/right arrow navigation 2011 // NOTE: Order of suggest columns are reverse order (index position 0 is on right) 2012 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) { 2013 // 37 LEFT ARROW 2014 // go left only if current column is not left-most column (last column) 2015 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) { 2016 $('li', $selectedUl).removeClass('jd-selected'); 2017 gSelectedColumn++; 2018 $selectedUl = $columns[gSelectedColumn]; 2019 // keep or reset the selected item to last item as appropriate 2020 gSelectedIndex = gSelectedIndex > 2021 $("li", $selectedUl).length-1 ? 2022 $("li", $selectedUl).length-1 : gSelectedIndex; 2023 // if the corresponding item is a header, move down 2024 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) { 2025 gSelectedIndex++; 2026 } 2027 // set item selected 2028 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected'); 2029 return false; 2030 } 2031 // 39 RIGHT ARROW 2032 // go right only if current column is not the right-most column (first column) 2033 else if (e.keyCode == 39 && gSelectedColumn > 0) { 2034 $('li', $selectedUl).removeClass('jd-selected'); 2035 gSelectedColumn--; 2036 $selectedUl = $columns[gSelectedColumn]; 2037 // keep or reset the selected item to last item as appropriate 2038 gSelectedIndex = gSelectedIndex > 2039 $("li", $selectedUl).length-1 ? 2040 $("li", $selectedUl).length-1 : gSelectedIndex; 2041 // if the corresponding item is a header, move down 2042 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) { 2043 gSelectedIndex++; 2044 } 2045 // set item selected 2046 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected'); 2047 return false; 2048 } 2049 } 2050 2051 // if key-up event and not arrow down/up/left/right, 2052 // read the search query and add suggestions to gMatches 2053 else if (!kd && (e.keyCode != 40) 2054 && (e.keyCode != 38) 2055 && (e.keyCode != 37) 2056 && (e.keyCode != 39)) { 2057 gSelectedIndex = -1; 2058 gMatches = new Array(); 2059 matchedCount = 0; 2060 gGoogleMatches = new Array(); 2061 matchedCountGoogle = 0; 2062 gDocsMatches = new Array(); 2063 matchedCountDocs = 0; 2064 2065 // Search for Android matches 2066 for (var i=0; i<DATA.length; i++) { 2067 var s = DATA[i]; 2068 if (text.length != 0 && 2069 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) { 2070 gMatches[matchedCount] = s; 2071 matchedCount++; 2072 } 2073 } 2074 rank_autocomplete_api_results(text, gMatches); 2075 for (var i=0; i<gMatches.length; i++) { 2076 var s = gMatches[i]; 2077 } 2078 2079 2080 // Search for Google matches 2081 for (var i=0; i<GOOGLE_DATA.length; i++) { 2082 var s = GOOGLE_DATA[i]; 2083 if (text.length != 0 && 2084 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) { 2085 gGoogleMatches[matchedCountGoogle] = s; 2086 matchedCountGoogle++; 2087 } 2088 } 2089 rank_autocomplete_api_results(text, gGoogleMatches); 2090 for (var i=0; i<gGoogleMatches.length; i++) { 2091 var s = gGoogleMatches[i]; 2092 } 2093 2094 highlight_autocomplete_result_labels(text); 2095 2096 2097 2098 // Search for matching JD docs 2099 if (text.length >= 2) { 2100 // Regex to match only the beginning of a word 2101 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g"); 2102 2103 2104 // Search for Training classes 2105 for (var i=0; i<TRAINING_RESOURCES.length; i++) { 2106 // current search comparison, with counters for tag and title, 2107 // used later to improve ranking 2108 var s = TRAINING_RESOURCES[i]; 2109 s.matched_tag = 0; 2110 s.matched_title = 0; 2111 var matched = false; 2112 2113 // Check if query matches any tags; work backwards toward 1 to assist ranking 2114 for (var j = s.keywords.length - 1; j >= 0; j--) { 2115 // it matches a tag 2116 if (s.keywords[j].toLowerCase().match(textRegex)) { 2117 matched = true; 2118 s.matched_tag = j + 1; // add 1 to index position 2119 } 2120 } 2121 // Don't consider doc title for lessons (only for class landing pages), 2122 // unless the lesson has a tag that already matches 2123 if ((s.lang == currentLang) && 2124 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) { 2125 // it matches the doc title 2126 if (s.title.toLowerCase().match(textRegex)) { 2127 matched = true; 2128 s.matched_title = 1; 2129 } 2130 } 2131 if (matched) { 2132 gDocsMatches[matchedCountDocs] = s; 2133 matchedCountDocs++; 2134 } 2135 } 2136 2137 2138 // Search for API Guides 2139 for (var i=0; i<GUIDE_RESOURCES.length; i++) { 2140 // current search comparison, with counters for tag and title, 2141 // used later to improve ranking 2142 var s = GUIDE_RESOURCES[i]; 2143 s.matched_tag = 0; 2144 s.matched_title = 0; 2145 var matched = false; 2146 2147 // Check if query matches any tags; work backwards toward 1 to assist ranking 2148 for (var j = s.keywords.length - 1; j >= 0; j--) { 2149 // it matches a tag 2150 if (s.keywords[j].toLowerCase().match(textRegex)) { 2151 matched = true; 2152 s.matched_tag = j + 1; // add 1 to index position 2153 } 2154 } 2155 // Check if query matches the doc title, but only for current language 2156 if (s.lang == currentLang) { 2157 // if query matches the doc title 2158 if (s.title.toLowerCase().match(textRegex)) { 2159 matched = true; 2160 s.matched_title = 1; 2161 } 2162 } 2163 if (matched) { 2164 gDocsMatches[matchedCountDocs] = s; 2165 matchedCountDocs++; 2166 } 2167 } 2168 2169 2170 // Search for Tools Guides 2171 for (var i=0; i<TOOLS_RESOURCES.length; i++) { 2172 // current search comparison, with counters for tag and title, 2173 // used later to improve ranking 2174 var s = TOOLS_RESOURCES[i]; 2175 s.matched_tag = 0; 2176 s.matched_title = 0; 2177 var matched = false; 2178 2179 // Check if query matches any tags; work backwards toward 1 to assist ranking 2180 for (var j = s.keywords.length - 1; j >= 0; j--) { 2181 // it matches a tag 2182 if (s.keywords[j].toLowerCase().match(textRegex)) { 2183 matched = true; 2184 s.matched_tag = j + 1; // add 1 to index position 2185 } 2186 } 2187 // Check if query matches the doc title, but only for current language 2188 if (s.lang == currentLang) { 2189 // if query matches the doc title 2190 if (s.title.toLowerCase().match(textRegex)) { 2191 matched = true; 2192 s.matched_title = 1; 2193 } 2194 } 2195 if (matched) { 2196 gDocsMatches[matchedCountDocs] = s; 2197 matchedCountDocs++; 2198 } 2199 } 2200 2201 2202 // Search for About docs 2203 for (var i=0; i<ABOUT_RESOURCES.length; i++) { 2204 // current search comparison, with counters for tag and title, 2205 // used later to improve ranking 2206 var s = ABOUT_RESOURCES[i]; 2207 s.matched_tag = 0; 2208 s.matched_title = 0; 2209 var matched = false; 2210 2211 // Check if query matches any tags; work backwards toward 1 to assist ranking 2212 for (var j = s.keywords.length - 1; j >= 0; j--) { 2213 // it matches a tag 2214 if (s.keywords[j].toLowerCase().match(textRegex)) { 2215 matched = true; 2216 s.matched_tag = j + 1; // add 1 to index position 2217 } 2218 } 2219 // Check if query matches the doc title, but only for current language 2220 if (s.lang == currentLang) { 2221 // if query matches the doc title 2222 if (s.title.toLowerCase().match(textRegex)) { 2223 matched = true; 2224 s.matched_title = 1; 2225 } 2226 } 2227 if (matched) { 2228 gDocsMatches[matchedCountDocs] = s; 2229 matchedCountDocs++; 2230 } 2231 } 2232 2233 2234 // Search for Design guides 2235 for (var i=0; i<DESIGN_RESOURCES.length; i++) { 2236 // current search comparison, with counters for tag and title, 2237 // used later to improve ranking 2238 var s = DESIGN_RESOURCES[i]; 2239 s.matched_tag = 0; 2240 s.matched_title = 0; 2241 var matched = false; 2242 2243 // Check if query matches any tags; work backwards toward 1 to assist ranking 2244 for (var j = s.keywords.length - 1; j >= 0; j--) { 2245 // it matches a tag 2246 if (s.keywords[j].toLowerCase().match(textRegex)) { 2247 matched = true; 2248 s.matched_tag = j + 1; // add 1 to index position 2249 } 2250 } 2251 // Check if query matches the doc title, but only for current language 2252 if (s.lang == currentLang) { 2253 // if query matches the doc title 2254 if (s.title.toLowerCase().match(textRegex)) { 2255 matched = true; 2256 s.matched_title = 1; 2257 } 2258 } 2259 if (matched) { 2260 gDocsMatches[matchedCountDocs] = s; 2261 matchedCountDocs++; 2262 } 2263 } 2264 2265 2266 // Search for Distribute guides 2267 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) { 2268 // current search comparison, with counters for tag and title, 2269 // used later to improve ranking 2270 var s = DISTRIBUTE_RESOURCES[i]; 2271 s.matched_tag = 0; 2272 s.matched_title = 0; 2273 var matched = false; 2274 2275 // Check if query matches any tags; work backwards toward 1 to assist ranking 2276 for (var j = s.keywords.length - 1; j >= 0; j--) { 2277 // it matches a tag 2278 if (s.keywords[j].toLowerCase().match(textRegex)) { 2279 matched = true; 2280 s.matched_tag = j + 1; // add 1 to index position 2281 } 2282 } 2283 // Check if query matches the doc title, but only for current language 2284 if (s.lang == currentLang) { 2285 // if query matches the doc title 2286 if (s.title.toLowerCase().match(textRegex)) { 2287 matched = true; 2288 s.matched_title = 1; 2289 } 2290 } 2291 if (matched) { 2292 gDocsMatches[matchedCountDocs] = s; 2293 matchedCountDocs++; 2294 } 2295 } 2296 2297 2298 // Search for Google guides 2299 for (var i=0; i<GOOGLE_RESOURCES.length; i++) { 2300 // current search comparison, with counters for tag and title, 2301 // used later to improve ranking 2302 var s = GOOGLE_RESOURCES[i]; 2303 s.matched_tag = 0; 2304 s.matched_title = 0; 2305 var matched = false; 2306 2307 // Check if query matches any tags; work backwards toward 1 to assist ranking 2308 for (var j = s.keywords.length - 1; j >= 0; j--) { 2309 // it matches a tag 2310 if (s.keywords[j].toLowerCase().match(textRegex)) { 2311 matched = true; 2312 s.matched_tag = j + 1; // add 1 to index position 2313 } 2314 } 2315 // Check if query matches the doc title, but only for current language 2316 if (s.lang == currentLang) { 2317 // if query matches the doc title 2318 if (s.title.toLowerCase().match(textRegex)) { 2319 matched = true; 2320 s.matched_title = 1; 2321 } 2322 } 2323 if (matched) { 2324 gDocsMatches[matchedCountDocs] = s; 2325 matchedCountDocs++; 2326 } 2327 } 2328 2329 2330 // Search for Samples 2331 for (var i=0; i<SAMPLES_RESOURCES.length; i++) { 2332 // current search comparison, with counters for tag and title, 2333 // used later to improve ranking 2334 var s = SAMPLES_RESOURCES[i]; 2335 s.matched_tag = 0; 2336 s.matched_title = 0; 2337 var matched = false; 2338 // Check if query matches any tags; work backwards toward 1 to assist ranking 2339 for (var j = s.keywords.length - 1; j >= 0; j--) { 2340 // it matches a tag 2341 if (s.keywords[j].toLowerCase().match(textRegex)) { 2342 matched = true; 2343 s.matched_tag = j + 1; // add 1 to index position 2344 } 2345 } 2346 // Check if query matches the doc title, but only for current language 2347 if (s.lang == currentLang) { 2348 // if query matches the doc title.t 2349 if (s.title.toLowerCase().match(textRegex)) { 2350 matched = true; 2351 s.matched_title = 1; 2352 } 2353 } 2354 if (matched) { 2355 gDocsMatches[matchedCountDocs] = s; 2356 matchedCountDocs++; 2357 } 2358 } 2359 2360 // Rank/sort all the matched pages 2361 rank_autocomplete_doc_results(text, gDocsMatches); 2362 } 2363 2364 // draw the suggestions 2365 sync_selection_table(toroot); 2366 return true; // allow the event to bubble up to the search api 2367 } 2368} 2369 2370/* Order the jd doc result list based on match quality */ 2371function rank_autocomplete_doc_results(query, matches) { 2372 query = query || ''; 2373 if (!matches || !matches.length) 2374 return; 2375 2376 var _resultScoreFn = function(match) { 2377 var score = 1.0; 2378 2379 // if the query matched a tag 2380 if (match.matched_tag > 0) { 2381 // multiply score by factor relative to position in tags list (max of 3) 2382 score *= 3 / match.matched_tag; 2383 2384 // if it also matched the title 2385 if (match.matched_title > 0) { 2386 score *= 2; 2387 } 2388 } else if (match.matched_title > 0) { 2389 score *= 3; 2390 } 2391 2392 return score; 2393 }; 2394 2395 for (var i=0; i<matches.length; i++) { 2396 matches[i].__resultScore = _resultScoreFn(matches[i]); 2397 } 2398 2399 matches.sort(function(a,b){ 2400 var n = b.__resultScore - a.__resultScore; 2401 if (n == 0) // lexicographical sort if scores are the same 2402 n = (a.label < b.label) ? -1 : 1; 2403 return n; 2404 }); 2405} 2406 2407/* Order the result list based on match quality */ 2408function rank_autocomplete_api_results(query, matches) { 2409 query = query || ''; 2410 if (!matches || !matches.length) 2411 return; 2412 2413 // helper function that gets the last occurence index of the given regex 2414 // in the given string, or -1 if not found 2415 var _lastSearch = function(s, re) { 2416 if (s == '') 2417 return -1; 2418 var l = -1; 2419 var tmp; 2420 while ((tmp = s.search(re)) >= 0) { 2421 if (l < 0) l = 0; 2422 l += tmp; 2423 s = s.substr(tmp + 1); 2424 } 2425 return l; 2426 }; 2427 2428 // helper function that counts the occurrences of a given character in 2429 // a given string 2430 var _countChar = function(s, c) { 2431 var n = 0; 2432 for (var i=0; i<s.length; i++) 2433 if (s.charAt(i) == c) ++n; 2434 return n; 2435 }; 2436 2437 var queryLower = query.toLowerCase(); 2438 var queryAlnum = (queryLower.match(/\w+/) || [''])[0]; 2439 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum); 2440 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b'); 2441 2442 var _resultScoreFn = function(result) { 2443 // scores are calculated based on exact and prefix matches, 2444 // and then number of path separators (dots) from the last 2445 // match (i.e. favoring classes and deep package names) 2446 var score = 1.0; 2447 var labelLower = result.label.toLowerCase(); 2448 var t; 2449 t = _lastSearch(labelLower, partExactAlnumRE); 2450 if (t >= 0) { 2451 // exact part match 2452 var partsAfter = _countChar(labelLower.substr(t + 1), '.'); 2453 score *= 200 / (partsAfter + 1); 2454 } else { 2455 t = _lastSearch(labelLower, partPrefixAlnumRE); 2456 if (t >= 0) { 2457 // part prefix match 2458 var partsAfter = _countChar(labelLower.substr(t + 1), '.'); 2459 score *= 20 / (partsAfter + 1); 2460 } 2461 } 2462 2463 return score; 2464 }; 2465 2466 for (var i=0; i<matches.length; i++) { 2467 // if the API is deprecated, default score is 0; otherwise, perform scoring 2468 if (matches[i].deprecated == "true") { 2469 matches[i].__resultScore = 0; 2470 } else { 2471 matches[i].__resultScore = _resultScoreFn(matches[i]); 2472 } 2473 } 2474 2475 matches.sort(function(a,b){ 2476 var n = b.__resultScore - a.__resultScore; 2477 if (n == 0) // lexicographical sort if scores are the same 2478 n = (a.label < b.label) ? -1 : 1; 2479 return n; 2480 }); 2481} 2482 2483/* Add emphasis to part of string that matches query */ 2484function highlight_autocomplete_result_labels(query) { 2485 query = query || ''; 2486 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length)) 2487 return; 2488 2489 var queryLower = query.toLowerCase(); 2490 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0]; 2491 var queryRE = new RegExp( 2492 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig'); 2493 for (var i=0; i<gMatches.length; i++) { 2494 gMatches[i].__hilabel = gMatches[i].label.replace( 2495 queryRE, '<b>$1</b>'); 2496 } 2497 for (var i=0; i<gGoogleMatches.length; i++) { 2498 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace( 2499 queryRE, '<b>$1</b>'); 2500 } 2501} 2502 2503function search_focus_changed(obj, focused) 2504{ 2505 if (!focused) { 2506 if(obj.value == ""){ 2507 $("#search-close").addClass("hide"); 2508 } 2509 $(".suggest-card").hide(); 2510 } 2511} 2512 2513function submit_search() { 2514 var query = document.getElementById('search_autocomplete').value; 2515 location.hash = 'q=' + query; 2516 loadSearchResults(); 2517 $("#searchResults").slideDown('slow', setStickyTop); 2518 return false; 2519} 2520 2521 2522function hideResults() { 2523 $("#searchResults").slideUp('fast', setStickyTop); 2524 $("#search-close").addClass("hide"); 2525 location.hash = ''; 2526 2527 $("#search_autocomplete").val("").blur(); 2528 2529 // reset the ajax search callback to nothing, so results don't appear unless ENTER 2530 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {}); 2531 2532 // forcefully regain key-up event control (previously jacked by search api) 2533 $("#search_autocomplete").keyup(function(event) { 2534 return search_changed(event, false, toRoot); 2535 }); 2536 2537 return false; 2538} 2539 2540 2541 2542/* ########################################################## */ 2543/* ################ CUSTOM SEARCH ENGINE ################## */ 2544/* ########################################################## */ 2545 2546var searchControl; 2547google.load('search', '1', {"callback" : function() { 2548 searchControl = new google.search.SearchControl(); 2549 } }); 2550 2551function loadSearchResults() { 2552 document.getElementById("search_autocomplete").style.color = "#000"; 2553 2554 searchControl = new google.search.SearchControl(); 2555 2556 // use our existing search form and use tabs when multiple searchers are used 2557 drawOptions = new google.search.DrawOptions(); 2558 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED); 2559 drawOptions.setInput(document.getElementById("search_autocomplete")); 2560 2561 // configure search result options 2562 searchOptions = new google.search.SearcherOptions(); 2563 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN); 2564 2565 // configure each of the searchers, for each tab 2566 devSiteSearcher = new google.search.WebSearch(); 2567 devSiteSearcher.setUserDefinedLabel("All"); 2568 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u"); 2569 2570 designSearcher = new google.search.WebSearch(); 2571 designSearcher.setUserDefinedLabel("Design"); 2572 designSearcher.setSiteRestriction("http://developer.android.com/design/"); 2573 2574 trainingSearcher = new google.search.WebSearch(); 2575 trainingSearcher.setUserDefinedLabel("Training"); 2576 trainingSearcher.setSiteRestriction("http://developer.android.com/training/"); 2577 2578 guidesSearcher = new google.search.WebSearch(); 2579 guidesSearcher.setUserDefinedLabel("Guides"); 2580 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/"); 2581 2582 referenceSearcher = new google.search.WebSearch(); 2583 referenceSearcher.setUserDefinedLabel("Reference"); 2584 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/"); 2585 2586 googleSearcher = new google.search.WebSearch(); 2587 googleSearcher.setUserDefinedLabel("Google Services"); 2588 googleSearcher.setSiteRestriction("http://developer.android.com/google/"); 2589 2590 blogSearcher = new google.search.WebSearch(); 2591 blogSearcher.setUserDefinedLabel("Blog"); 2592 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com"); 2593 2594 // add each searcher to the search control 2595 searchControl.addSearcher(devSiteSearcher, searchOptions); 2596 searchControl.addSearcher(designSearcher, searchOptions); 2597 searchControl.addSearcher(trainingSearcher, searchOptions); 2598 searchControl.addSearcher(guidesSearcher, searchOptions); 2599 searchControl.addSearcher(referenceSearcher, searchOptions); 2600 searchControl.addSearcher(googleSearcher, searchOptions); 2601 searchControl.addSearcher(blogSearcher, searchOptions); 2602 2603 // configure result options 2604 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET); 2605 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF); 2606 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT); 2607 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING); 2608 2609 // upon ajax search, refresh the url and search title 2610 searchControl.setSearchStartingCallback(this, function(control, searcher, query) { 2611 updateResultTitle(query); 2612 var query = document.getElementById('search_autocomplete').value; 2613 location.hash = 'q=' + query; 2614 }); 2615 2616 // once search results load, set up click listeners 2617 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) { 2618 addResultClickListeners(); 2619 }); 2620 2621 // draw the search results box 2622 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions); 2623 2624 // get query and execute the search 2625 searchControl.execute(decodeURI(getQuery(location.hash))); 2626 2627 document.getElementById("search_autocomplete").focus(); 2628 addTabListeners(); 2629} 2630// End of loadSearchResults 2631 2632 2633google.setOnLoadCallback(function(){ 2634 if (location.hash.indexOf("q=") == -1) { 2635 // if there's no query in the url, don't search and make sure results are hidden 2636 $('#searchResults').hide(); 2637 return; 2638 } else { 2639 // first time loading search results for this page 2640 $('#searchResults').slideDown('slow', setStickyTop); 2641 $("#search-close").removeClass("hide"); 2642 loadSearchResults(); 2643 } 2644}, true); 2645 2646/* Adjust the scroll position to account for sticky header, only if the hash matches an id. 2647 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */ 2648function offsetScrollForSticky() { 2649 // Ignore if there's no search bar (some special pages have no header) 2650 if ($("#search-container").length < 1) return; 2651 2652 var hash = escape(location.hash.substr(1)); 2653 var $matchingElement = $("#"+hash); 2654 // Sanity check that there's an element with that ID on the page 2655 if ($matchingElement.length) { 2656 // If the position of the target element is near the top of the page (<20px, where we expect it 2657 // to be because we need to move it down 60px to become in view), then move it down 60px 2658 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) { 2659 $(window).scrollTop($(window).scrollTop() - 60); 2660 } 2661 } 2662} 2663 2664// when an event on the browser history occurs (back, forward, load) requery hash and do search 2665$(window).hashchange( function(){ 2666 // Ignore if there's no search bar (some special pages have no header) 2667 if ($("#search-container").length < 1) return; 2668 2669 // If the hash isn't a search query or there's an error in the query, 2670 // then adjust the scroll position to account for sticky header, then exit. 2671 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) { 2672 // If the results pane is open, close it. 2673 if (!$("#searchResults").is(":hidden")) { 2674 hideResults(); 2675 } 2676 offsetScrollForSticky(); 2677 return; 2678 } 2679 2680 // Otherwise, we have a search to do 2681 var query = decodeURI(getQuery(location.hash)); 2682 searchControl.execute(query); 2683 $('#searchResults').slideDown('slow', setStickyTop); 2684 $("#search_autocomplete").focus(); 2685 $("#search-close").removeClass("hide"); 2686 2687 updateResultTitle(query); 2688}); 2689 2690function updateResultTitle(query) { 2691 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>"); 2692} 2693 2694// forcefully regain key-up event control (previously jacked by search api) 2695$("#search_autocomplete").keyup(function(event) { 2696 return search_changed(event, false, toRoot); 2697}); 2698 2699// add event listeners to each tab so we can track the browser history 2700function addTabListeners() { 2701 var tabHeaders = $(".gsc-tabHeader"); 2702 for (var i = 0; i < tabHeaders.length; i++) { 2703 $(tabHeaders[i]).attr("id",i).click(function() { 2704 /* 2705 // make a copy of the page numbers for the search left pane 2706 setTimeout(function() { 2707 // remove any residual page numbers 2708 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove(); 2709 // move the page numbers to the left position; make a clone, 2710 // because the element is drawn to the DOM only once 2711 // and because we're going to remove it (previous line), 2712 // we need it to be available to move again as the user navigates 2713 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible') 2714 .clone().appendTo('#searchResults .gsc-tabsArea'); 2715 }, 200); 2716 */ 2717 }); 2718 } 2719 setTimeout(function(){$(tabHeaders[0]).click()},200); 2720} 2721 2722// add analytics tracking events to each result link 2723function addResultClickListeners() { 2724 $("#searchResults a.gs-title").each(function(index, link) { 2725 // When user clicks enter for Google search results, track it 2726 $(link).click(function() { 2727 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'), 2728 'query: ' + $("#search_autocomplete").val().toLowerCase()); 2729 }); 2730 }); 2731} 2732 2733 2734function getQuery(hash) { 2735 var queryParts = hash.split('='); 2736 return queryParts[1]; 2737} 2738 2739/* returns the given string with all HTML brackets converted to entities 2740 TODO: move this to the site's JS library */ 2741function escapeHTML(string) { 2742 return string.replace(/</g,"<") 2743 .replace(/>/g,">"); 2744} 2745 2746 2747 2748 2749 2750 2751 2752/* ######################################################## */ 2753/* ################# JAVADOC REFERENCE ################### */ 2754/* ######################################################## */ 2755 2756/* Initialize some droiddoc stuff, but only if we're in the reference */ 2757if (location.pathname.indexOf("/reference") == 0) { 2758 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0) 2759 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0) 2760 && !(location.pathname.indexOf("/reference/com/google") == 0)) { 2761 $(document).ready(function() { 2762 // init available apis based on user pref 2763 changeApiLevel(); 2764 initSidenavHeightResize() 2765 }); 2766 } 2767} 2768 2769var API_LEVEL_COOKIE = "api_level"; 2770var minLevel = 1; 2771var maxLevel = 1; 2772 2773/******* SIDENAV DIMENSIONS ************/ 2774 2775 function initSidenavHeightResize() { 2776 // Change the drag bar size to nicely fit the scrollbar positions 2777 var $dragBar = $(".ui-resizable-s"); 2778 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"}); 2779 2780 $( "#resize-packages-nav" ).resizable({ 2781 containment: "#nav-panels", 2782 handles: "s", 2783 alsoResize: "#packages-nav", 2784 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */ 2785 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */ 2786 }); 2787 2788 } 2789 2790function updateSidenavFixedWidth() { 2791 if (!sticky) return; 2792 $('#devdoc-nav').css({ 2793 'width' : $('#side-nav').css('width'), 2794 'margin' : $('#side-nav').css('margin') 2795 }); 2796 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'}); 2797 2798 initSidenavHeightResize(); 2799} 2800 2801function updateSidenavFullscreenWidth() { 2802 if (!sticky) return; 2803 $('#devdoc-nav').css({ 2804 'width' : $('#side-nav').css('width'), 2805 'margin' : $('#side-nav').css('margin') 2806 }); 2807 $('#devdoc-nav .totop').css({'left': 'inherit'}); 2808 2809 initSidenavHeightResize(); 2810} 2811 2812function buildApiLevelSelector() { 2813 maxLevel = SINCE_DATA.length; 2814 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE)); 2815 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default 2816 2817 minLevel = parseInt($("#doc-api-level").attr("class")); 2818 // Handle provisional api levels; the provisional level will always be the highest possible level 2819 // Provisional api levels will also have a length; other stuff that's just missing a level won't, 2820 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class) 2821 if (isNaN(minLevel) && minLevel.length) { 2822 minLevel = maxLevel; 2823 } 2824 var select = $("#apiLevelSelector").html("").change(changeApiLevel); 2825 for (var i = maxLevel-1; i >= 0; i--) { 2826 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]); 2827 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames) 2828 select.append(option); 2829 } 2830 2831 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true) 2832 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0); 2833 selectedLevelItem.setAttribute('selected',true); 2834} 2835 2836function changeApiLevel() { 2837 maxLevel = SINCE_DATA.length; 2838 var selectedLevel = maxLevel; 2839 2840 selectedLevel = parseInt($("#apiLevelSelector option:selected").val()); 2841 toggleVisisbleApis(selectedLevel, "body"); 2842 2843 writeCookie(API_LEVEL_COOKIE, selectedLevel, null); 2844 2845 if (selectedLevel < minLevel) { 2846 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class"; 2847 $("#naMessage").show().html("<div><p><strong>This " + thing 2848 + " requires API level " + minLevel + " or higher.</strong></p>" 2849 + "<p>This document is hidden because your selected API level for the documentation is " 2850 + selectedLevel + ". You can change the documentation API level with the selector " 2851 + "above the left navigation.</p>" 2852 + "<p>For more information about specifying the API level your app requires, " 2853 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'" 2854 + ">Supporting Different Platform Versions</a>.</p>" 2855 + "<input type='button' value='OK, make this page visible' " 2856 + "title='Change the API level to " + minLevel + "' " 2857 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />" 2858 + "</div>"); 2859 } else { 2860 $("#naMessage").hide(); 2861 } 2862} 2863 2864function toggleVisisbleApis(selectedLevel, context) { 2865 var apis = $(".api",context); 2866 apis.each(function(i) { 2867 var obj = $(this); 2868 var className = obj.attr("class"); 2869 var apiLevelIndex = className.lastIndexOf("-")+1; 2870 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex); 2871 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length; 2872 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex); 2873 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail 2874 return; 2875 } 2876 apiLevel = parseInt(apiLevel); 2877 2878 // Handle provisional api levels; if this item's level is the provisional one, set it to the max 2879 var selectedLevelNum = parseInt(selectedLevel) 2880 var apiLevelNum = parseInt(apiLevel); 2881 if (isNaN(apiLevelNum)) { 2882 apiLevelNum = maxLevel; 2883 } 2884 2885 // Grey things out that aren't available and give a tooltip title 2886 if (apiLevelNum > selectedLevelNum) { 2887 obj.addClass("absent").attr("title","Requires API Level \"" 2888 + apiLevel + "\" or higher. To reveal, change the target API level " 2889 + "above the left navigation."); 2890 } 2891 else obj.removeClass("absent").removeAttr("title"); 2892 }); 2893} 2894 2895 2896 2897 2898/* ################# SIDENAV TREE VIEW ################### */ 2899 2900function new_node(me, mom, text, link, children_data, api_level) 2901{ 2902 var node = new Object(); 2903 node.children = Array(); 2904 node.children_data = children_data; 2905 node.depth = mom.depth + 1; 2906 2907 node.li = document.createElement("li"); 2908 mom.get_children_ul().appendChild(node.li); 2909 2910 node.label_div = document.createElement("div"); 2911 node.label_div.className = "label"; 2912 if (api_level != null) { 2913 $(node.label_div).addClass("api"); 2914 $(node.label_div).addClass("api-level-"+api_level); 2915 } 2916 node.li.appendChild(node.label_div); 2917 2918 if (children_data != null) { 2919 node.expand_toggle = document.createElement("a"); 2920 node.expand_toggle.href = "javascript:void(0)"; 2921 node.expand_toggle.onclick = function() { 2922 if (node.expanded) { 2923 $(node.get_children_ul()).slideUp("fast"); 2924 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png"; 2925 node.expanded = false; 2926 } else { 2927 expand_node(me, node); 2928 } 2929 }; 2930 node.label_div.appendChild(node.expand_toggle); 2931 2932 node.plus_img = document.createElement("img"); 2933 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png"; 2934 node.plus_img.className = "plus"; 2935 node.plus_img.width = "8"; 2936 node.plus_img.border = "0"; 2937 node.expand_toggle.appendChild(node.plus_img); 2938 2939 node.expanded = false; 2940 } 2941 2942 var a = document.createElement("a"); 2943 node.label_div.appendChild(a); 2944 node.label = document.createTextNode(text); 2945 a.appendChild(node.label); 2946 if (link) { 2947 a.href = me.toroot + link; 2948 } else { 2949 if (children_data != null) { 2950 a.className = "nolink"; 2951 a.href = "javascript:void(0)"; 2952 a.onclick = node.expand_toggle.onclick; 2953 // This next line shouldn't be necessary. I'll buy a beer for the first 2954 // person who figures out how to remove this line and have the link 2955 // toggle shut on the first try. --joeo@android.com 2956 node.expanded = false; 2957 } 2958 } 2959 2960 2961 node.children_ul = null; 2962 node.get_children_ul = function() { 2963 if (!node.children_ul) { 2964 node.children_ul = document.createElement("ul"); 2965 node.children_ul.className = "children_ul"; 2966 node.children_ul.style.display = "none"; 2967 node.li.appendChild(node.children_ul); 2968 } 2969 return node.children_ul; 2970 }; 2971 2972 return node; 2973} 2974 2975 2976 2977 2978function expand_node(me, node) 2979{ 2980 if (node.children_data && !node.expanded) { 2981 if (node.children_visited) { 2982 $(node.get_children_ul()).slideDown("fast"); 2983 } else { 2984 get_node(me, node); 2985 if ($(node.label_div).hasClass("absent")) { 2986 $(node.get_children_ul()).addClass("absent"); 2987 } 2988 $(node.get_children_ul()).slideDown("fast"); 2989 } 2990 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png"; 2991 node.expanded = true; 2992 2993 // perform api level toggling because new nodes are new to the DOM 2994 var selectedLevel = $("#apiLevelSelector option:selected").val(); 2995 toggleVisisbleApis(selectedLevel, "#side-nav"); 2996 } 2997} 2998 2999function get_node(me, mom) 3000{ 3001 mom.children_visited = true; 3002 for (var i in mom.children_data) { 3003 var node_data = mom.children_data[i]; 3004 mom.children[i] = new_node(me, mom, node_data[0], node_data[1], 3005 node_data[2], node_data[3]); 3006 } 3007} 3008 3009function this_page_relative(toroot) 3010{ 3011 var full = document.location.pathname; 3012 var file = ""; 3013 if (toroot.substr(0, 1) == "/") { 3014 if (full.substr(0, toroot.length) == toroot) { 3015 return full.substr(toroot.length); 3016 } else { 3017 // the file isn't under toroot. Fail. 3018 return null; 3019 } 3020 } else { 3021 if (toroot != "./") { 3022 toroot = "./" + toroot; 3023 } 3024 do { 3025 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") { 3026 var pos = full.lastIndexOf("/"); 3027 file = full.substr(pos) + file; 3028 full = full.substr(0, pos); 3029 toroot = toroot.substr(0, toroot.length-3); 3030 } 3031 } while (toroot != "" && toroot != "/"); 3032 return file.substr(1); 3033 } 3034} 3035 3036function find_page(url, data) 3037{ 3038 var nodes = data; 3039 var result = null; 3040 for (var i in nodes) { 3041 var d = nodes[i]; 3042 if (d[1] == url) { 3043 return new Array(i); 3044 } 3045 else if (d[2] != null) { 3046 result = find_page(url, d[2]); 3047 if (result != null) { 3048 return (new Array(i).concat(result)); 3049 } 3050 } 3051 } 3052 return null; 3053} 3054 3055function init_default_navtree(toroot) { 3056 // load json file for navtree data 3057 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) { 3058 // when the file is loaded, initialize the tree 3059 if(jqxhr.status === 200) { 3060 init_navtree("tree-list", toroot, NAVTREE_DATA); 3061 } 3062 }); 3063 3064 // perform api level toggling because because the whole tree is new to the DOM 3065 var selectedLevel = $("#apiLevelSelector option:selected").val(); 3066 toggleVisisbleApis(selectedLevel, "#side-nav"); 3067} 3068 3069function init_navtree(navtree_id, toroot, root_nodes) 3070{ 3071 var me = new Object(); 3072 me.toroot = toroot; 3073 me.node = new Object(); 3074 3075 me.node.li = document.getElementById(navtree_id); 3076 me.node.children_data = root_nodes; 3077 me.node.children = new Array(); 3078 me.node.children_ul = document.createElement("ul"); 3079 me.node.get_children_ul = function() { return me.node.children_ul; }; 3080 //me.node.children_ul.className = "children_ul"; 3081 me.node.li.appendChild(me.node.children_ul); 3082 me.node.depth = 0; 3083 3084 get_node(me, me.node); 3085 3086 me.this_page = this_page_relative(toroot); 3087 me.breadcrumbs = find_page(me.this_page, root_nodes); 3088 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) { 3089 var mom = me.node; 3090 for (var i in me.breadcrumbs) { 3091 var j = me.breadcrumbs[i]; 3092 mom = mom.children[j]; 3093 expand_node(me, mom); 3094 } 3095 mom.label_div.className = mom.label_div.className + " selected"; 3096 addLoadEvent(function() { 3097 scrollIntoView("nav-tree"); 3098 }); 3099 } 3100} 3101 3102 3103 3104 3105 3106 3107 3108 3109/* TODO: eliminate redundancy with non-google functions */ 3110function init_google_navtree(navtree_id, toroot, root_nodes) 3111{ 3112 var me = new Object(); 3113 me.toroot = toroot; 3114 me.node = new Object(); 3115 3116 me.node.li = document.getElementById(navtree_id); 3117 me.node.children_data = root_nodes; 3118 me.node.children = new Array(); 3119 me.node.children_ul = document.createElement("ul"); 3120 me.node.get_children_ul = function() { return me.node.children_ul; }; 3121 //me.node.children_ul.className = "children_ul"; 3122 me.node.li.appendChild(me.node.children_ul); 3123 me.node.depth = 0; 3124 3125 get_google_node(me, me.node); 3126} 3127 3128function new_google_node(me, mom, text, link, children_data, api_level) 3129{ 3130 var node = new Object(); 3131 var child; 3132 node.children = Array(); 3133 node.children_data = children_data; 3134 node.depth = mom.depth + 1; 3135 node.get_children_ul = function() { 3136 if (!node.children_ul) { 3137 node.children_ul = document.createElement("ul"); 3138 node.children_ul.className = "tree-list-children"; 3139 node.li.appendChild(node.children_ul); 3140 } 3141 return node.children_ul; 3142 }; 3143 node.li = document.createElement("li"); 3144 3145 mom.get_children_ul().appendChild(node.li); 3146 3147 3148 if(link) { 3149 child = document.createElement("a"); 3150 3151 } 3152 else { 3153 child = document.createElement("span"); 3154 child.className = "tree-list-subtitle"; 3155 3156 } 3157 if (children_data != null) { 3158 node.li.className="nav-section"; 3159 node.label_div = document.createElement("div"); 3160 node.label_div.className = "nav-section-header-ref"; 3161 node.li.appendChild(node.label_div); 3162 get_google_node(me, node); 3163 node.label_div.appendChild(child); 3164 } 3165 else { 3166 node.li.appendChild(child); 3167 } 3168 if(link) { 3169 child.href = me.toroot + link; 3170 } 3171 node.label = document.createTextNode(text); 3172 child.appendChild(node.label); 3173 3174 node.children_ul = null; 3175 3176 return node; 3177} 3178 3179function get_google_node(me, mom) 3180{ 3181 mom.children_visited = true; 3182 var linkText; 3183 for (var i in mom.children_data) { 3184 var node_data = mom.children_data[i]; 3185 linkText = node_data[0]; 3186 3187 if(linkText.match("^"+"com.google.android")=="com.google.android"){ 3188 linkText = linkText.substr(19, linkText.length); 3189 } 3190 mom.children[i] = new_google_node(me, mom, linkText, node_data[1], 3191 node_data[2], node_data[3]); 3192 } 3193} 3194 3195 3196 3197 3198 3199 3200/****** NEW version of script to build google and sample navs dynamically ******/ 3201// TODO: update Google reference docs to tolerate this new implementation 3202 3203var NODE_NAME = 0; 3204var NODE_HREF = 1; 3205var NODE_GROUP = 2; 3206var NODE_TAGS = 3; 3207var NODE_CHILDREN = 4; 3208 3209function init_google_navtree2(navtree_id, data) 3210{ 3211 var $containerUl = $("#"+navtree_id); 3212 for (var i in data) { 3213 var node_data = data[i]; 3214 $containerUl.append(new_google_node2(node_data)); 3215 } 3216 3217 // Make all third-generation list items 'sticky' to prevent them from collapsing 3218 $containerUl.find('li li li.nav-section').addClass('sticky'); 3219 3220 initExpandableNavItems("#"+navtree_id); 3221} 3222 3223function new_google_node2(node_data) 3224{ 3225 var linkText = node_data[NODE_NAME]; 3226 if(linkText.match("^"+"com.google.android")=="com.google.android"){ 3227 linkText = linkText.substr(19, linkText.length); 3228 } 3229 var $li = $('<li>'); 3230 var $a; 3231 if (node_data[NODE_HREF] != null) { 3232 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >' 3233 + linkText + '</a>'); 3234 } else { 3235 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >' 3236 + linkText + '/</a>'); 3237 } 3238 var $childUl = $('<ul>'); 3239 if (node_data[NODE_CHILDREN] != null) { 3240 $li.addClass("nav-section"); 3241 $a = $('<div class="nav-section-header">').append($a); 3242 if (node_data[NODE_HREF] == null) $a.addClass('empty'); 3243 3244 for (var i in node_data[NODE_CHILDREN]) { 3245 var child_node_data = node_data[NODE_CHILDREN][i]; 3246 $childUl.append(new_google_node2(child_node_data)); 3247 } 3248 $li.append($childUl); 3249 } 3250 $li.prepend($a); 3251 3252 return $li; 3253} 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265function showGoogleRefTree() { 3266 init_default_google_navtree(toRoot); 3267 init_default_gcm_navtree(toRoot); 3268} 3269 3270function init_default_google_navtree(toroot) { 3271 // load json file for navtree data 3272 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) { 3273 // when the file is loaded, initialize the tree 3274 if(jqxhr.status === 200) { 3275 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA); 3276 highlightSidenav(); 3277 resizeNav(); 3278 } 3279 }); 3280} 3281 3282function init_default_gcm_navtree(toroot) { 3283 // load json file for navtree data 3284 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) { 3285 // when the file is loaded, initialize the tree 3286 if(jqxhr.status === 200) { 3287 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA); 3288 highlightSidenav(); 3289 resizeNav(); 3290 } 3291 }); 3292} 3293 3294function showSamplesRefTree() { 3295 init_default_samples_navtree(toRoot); 3296} 3297 3298function init_default_samples_navtree(toroot) { 3299 // load json file for navtree data 3300 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) { 3301 // when the file is loaded, initialize the tree 3302 if(jqxhr.status === 200) { 3303 // hack to remove the "about the samples" link then put it back in 3304 // after we nuke the list to remove the dummy static list of samples 3305 var $firstLi = $("#nav.samples-nav > li:first-child").clone(); 3306 $("#nav.samples-nav").empty(); 3307 $("#nav.samples-nav").append($firstLi); 3308 3309 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA); 3310 highlightSidenav(); 3311 resizeNav(); 3312 if ($("#jd-content #samples").length) { 3313 showSamples(); 3314 } 3315 } 3316 }); 3317} 3318 3319/* TOGGLE INHERITED MEMBERS */ 3320 3321/* Toggle an inherited class (arrow toggle) 3322 * @param linkObj The link that was clicked. 3323 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed. 3324 * 'null' to simply toggle. 3325 */ 3326function toggleInherited(linkObj, expand) { 3327 var base = linkObj.getAttribute("id"); 3328 var list = document.getElementById(base + "-list"); 3329 var summary = document.getElementById(base + "-summary"); 3330 var trigger = document.getElementById(base + "-trigger"); 3331 var a = $(linkObj); 3332 if ( (expand == null && a.hasClass("closed")) || expand ) { 3333 list.style.display = "none"; 3334 summary.style.display = "block"; 3335 trigger.src = toRoot + "assets/images/triangle-opened.png"; 3336 a.removeClass("closed"); 3337 a.addClass("opened"); 3338 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) { 3339 list.style.display = "block"; 3340 summary.style.display = "none"; 3341 trigger.src = toRoot + "assets/images/triangle-closed.png"; 3342 a.removeClass("opened"); 3343 a.addClass("closed"); 3344 } 3345 return false; 3346} 3347 3348/* Toggle all inherited classes in a single table (e.g. all inherited methods) 3349 * @param linkObj The link that was clicked. 3350 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed. 3351 * 'null' to simply toggle. 3352 */ 3353function toggleAllInherited(linkObj, expand) { 3354 var a = $(linkObj); 3355 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody 3356 var expandos = $(".jd-expando-trigger", table); 3357 if ( (expand == null && a.text() == "[Expand]") || expand ) { 3358 expandos.each(function(i) { 3359 toggleInherited(this, true); 3360 }); 3361 a.text("[Collapse]"); 3362 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) { 3363 expandos.each(function(i) { 3364 toggleInherited(this, false); 3365 }); 3366 a.text("[Expand]"); 3367 } 3368 return false; 3369} 3370 3371/* Toggle all inherited members in the class (link in the class title) 3372 */ 3373function toggleAllClassInherited() { 3374 var a = $("#toggleAllClassInherited"); // get toggle link from class title 3375 var toggles = $(".toggle-all", $("#body-content")); 3376 if (a.text() == "[Expand All]") { 3377 toggles.each(function(i) { 3378 toggleAllInherited(this, true); 3379 }); 3380 a.text("[Collapse All]"); 3381 } else { 3382 toggles.each(function(i) { 3383 toggleAllInherited(this, false); 3384 }); 3385 a.text("[Expand All]"); 3386 } 3387 return false; 3388} 3389 3390/* Expand all inherited members in the class. Used when initiating page search */ 3391function ensureAllInheritedExpanded() { 3392 var toggles = $(".toggle-all", $("#body-content")); 3393 toggles.each(function(i) { 3394 toggleAllInherited(this, true); 3395 }); 3396 $("#toggleAllClassInherited").text("[Collapse All]"); 3397} 3398 3399 3400/* HANDLE KEY EVENTS 3401 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search) 3402 */ 3403var agent = navigator['userAgent'].toLowerCase(); 3404var mac = agent.indexOf("macintosh") != -1; 3405 3406$(document).keydown( function(e) { 3407var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key 3408 if (control && e.which == 70) { // 70 is "F" 3409 ensureAllInheritedExpanded(); 3410 } 3411}); 3412 3413 3414 3415 3416 3417 3418/* On-demand functions */ 3419 3420/** Move sample code line numbers out of PRE block and into non-copyable column */ 3421function initCodeLineNumbers() { 3422 var numbers = $("#codesample-block a.number"); 3423 if (numbers.length) { 3424 $("#codesample-line-numbers").removeClass("hidden").append(numbers); 3425 } 3426 3427 $(document).ready(function() { 3428 // select entire line when clicked 3429 $("span.code-line").click(function() { 3430 if (!shifted) { 3431 selectText(this); 3432 } 3433 }); 3434 // invoke line link on double click 3435 $(".code-line").dblclick(function() { 3436 document.location.hash = $(this).attr('id'); 3437 }); 3438 // highlight the line when hovering on the number 3439 $("#codesample-line-numbers a.number").mouseover(function() { 3440 var id = $(this).attr('href'); 3441 $(id).css('background','#e7e7e7'); 3442 }); 3443 $("#codesample-line-numbers a.number").mouseout(function() { 3444 var id = $(this).attr('href'); 3445 $(id).css('background','none'); 3446 }); 3447 }); 3448} 3449 3450// create SHIFT key binder to avoid the selectText method when selecting multiple lines 3451var shifted = false; 3452$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} ); 3453 3454// courtesy of jasonedelman.com 3455function selectText(element) { 3456 var doc = document 3457 , range, selection 3458 ; 3459 if (doc.body.createTextRange) { //ms 3460 range = doc.body.createTextRange(); 3461 range.moveToElementText(element); 3462 range.select(); 3463 } else if (window.getSelection) { //all others 3464 selection = window.getSelection(); 3465 range = doc.createRange(); 3466 range.selectNodeContents(element); 3467 selection.removeAllRanges(); 3468 selection.addRange(range); 3469 } 3470} 3471 3472 3473 3474 3475/** Display links and other information about samples that match the 3476 group specified by the URL */ 3477function showSamples() { 3478 var group = $("#samples").attr('class'); 3479 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>"); 3480 3481 var $ul = $("<ul>"); 3482 $selectedLi = $("#nav li.selected"); 3483 3484 $selectedLi.children("ul").children("li").each(function() { 3485 var $li = $("<li>").append($(this).find("a").first().clone()); 3486 $ul.append($li); 3487 }); 3488 3489 $("#samples").append($ul); 3490 3491} 3492 3493 3494 3495/* ########################################################## */ 3496/* ################### RESOURCE CARDS ##################### */ 3497/* ########################################################## */ 3498 3499/** Handle resource queries, collections, and grids (sections). Requires 3500 jd_tag_helpers.js and the *_unified_data.js to be loaded. */ 3501 3502(function() { 3503 // Prevent the same resource from being loaded more than once per page. 3504 var addedPageResources = {}; 3505 3506 $(document).ready(function() { 3507 // Need to initialize hero carousel before other sections for dedupe 3508 // to work correctly. 3509 $('[data-carousel-query]').dacCarouselQuery(); 3510 3511 $('.resource-widget').each(function() { 3512 initResourceWidget(this); 3513 }); 3514 3515 /* Pass the line height to ellipsisfade() to adjust the height of the 3516 text container to show the max number of lines possible, without 3517 showing lines that are cut off. This works with the css ellipsis 3518 classes to fade last text line and apply an ellipsis char. */ 3519 3520 //card text currently uses 20px line height. 3521 var lineHeight = 20; 3522 $('.card-info .text').ellipsisfade(lineHeight); 3523 }); 3524 3525 /* 3526 Three types of resource layouts: 3527 Flow - Uses a fixed row-height flow using float left style. 3528 Carousel - Single card slideshow all same dimension absolute. 3529 Stack - Uses fixed columns and flexible element height. 3530 */ 3531 function initResourceWidget(widget) { 3532 var $widget = $(widget); 3533 var isFlow = $widget.hasClass('resource-flow-layout'), 3534 isCarousel = $widget.hasClass('resource-carousel-layout'), 3535 isStack = $widget.hasClass('resource-stack-layout'); 3536 3537 // remove illegal col-x class which is not relevant anymore thanks to responsive styles. 3538 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/); 3539 if (m && !$widget.is('.cols > *')) { 3540 $widget.removeClass('col-' + m[1]); 3541 } 3542 3543 var opts = { 3544 cardSizes: ($widget.data('cardsizes') || '').split(','), 3545 maxResults: parseInt($widget.data('maxresults') || '100', 10), 3546 itemsPerPage: $widget.data('itemsperpage'), 3547 sortOrder: $widget.data('sortorder'), 3548 query: $widget.data('query'), 3549 section: $widget.data('section'), 3550 /* Added by LFL 6/6/14 */ 3551 resourceStyle: $widget.data('resourcestyle') || 'card', 3552 stackSort: $widget.data('stacksort') || 'true' 3553 }; 3554 3555 // run the search for the set of resources to show 3556 3557 var resources = buildResourceList(opts); 3558 3559 if (isFlow) { 3560 drawResourcesFlowWidget($widget, opts, resources); 3561 } else if (isCarousel) { 3562 drawResourcesCarouselWidget($widget, opts, resources); 3563 } else if (isStack) { 3564 /* Looks like this got removed and is not used, so repurposing for the 3565 homepage style layout. 3566 Modified by LFL 6/6/14 3567 */ 3568 //var sections = buildSectionList(opts); 3569 opts['numStacks'] = $widget.data('numstacks'); 3570 drawResourcesStackWidget($widget, opts, resources/*, sections*/); 3571 } 3572 } 3573 3574 /* Initializes a Resource Carousel Widget */ 3575 function drawResourcesCarouselWidget($widget, opts, resources) { 3576 $widget.empty(); 3577 var plusone = false; // stop showing plusone buttons on cards 3578 3579 $widget.addClass('resource-card slideshow-container') 3580 .append($('<a>').addClass('slideshow-prev').text('Prev')) 3581 .append($('<a>').addClass('slideshow-next').text('Next')); 3582 3583 var css = { 'width': $widget.width() + 'px', 3584 'height': $widget.height() + 'px' }; 3585 3586 var $ul = $('<ul>'); 3587 3588 for (var i = 0; i < resources.length; ++i) { 3589 var $card = $('<a>') 3590 .attr('href', cleanUrl(resources[i].url)) 3591 .decorateResourceCard(resources[i],plusone); 3592 3593 $('<li>').css(css) 3594 .append($card) 3595 .appendTo($ul); 3596 } 3597 3598 $('<div>').addClass('frame') 3599 .append($ul) 3600 .appendTo($widget); 3601 3602 $widget.dacSlideshow({ 3603 auto: true, 3604 btnPrev: '.slideshow-prev', 3605 btnNext: '.slideshow-next' 3606 }); 3607 }; 3608 3609 /* Initializes a Resource Card Stack Widget (column-based layout) 3610 Modified by LFL 6/6/14 3611 */ 3612 function drawResourcesStackWidget($widget, opts, resources, sections) { 3613 // Don't empty widget, grab all items inside since they will be the first 3614 // items stacked, followed by the resource query 3615 var plusone = false; // stop showing plusone buttons on cards 3616 var cards = $widget.find('.resource-card').detach().toArray(); 3617 var numStacks = opts.numStacks || 1; 3618 var $stacks = []; 3619 var urlString; 3620 3621 for (var i = 0; i < numStacks; ++i) { 3622 $stacks[i] = $('<div>').addClass('resource-card-stack') 3623 .appendTo($widget); 3624 } 3625 3626 var sectionResources = []; 3627 3628 // Extract any subsections that are actually resource cards 3629 if (sections) { 3630 for (var i = 0; i < sections.length; ++i) { 3631 if (!sections[i].sections || !sections[i].sections.length) { 3632 // Render it as a resource card 3633 sectionResources.push( 3634 $('<a>') 3635 .addClass('resource-card section-card') 3636 .attr('href', cleanUrl(sections[i].resource.url)) 3637 .decorateResourceCard(sections[i].resource,plusone)[0] 3638 ); 3639 3640 } else { 3641 cards.push( 3642 $('<div>') 3643 .addClass('resource-card section-card-menu') 3644 .decorateResourceSection(sections[i],plusone)[0] 3645 ); 3646 } 3647 } 3648 } 3649 3650 cards = cards.concat(sectionResources); 3651 3652 for (var i = 0; i < resources.length; ++i) { 3653 var $card = createResourceElement(resources[i], opts); 3654 3655 if (opts.resourceStyle.indexOf('related') > -1) { 3656 $card.addClass('related-card'); 3657 } 3658 3659 cards.push($card[0]); 3660 } 3661 3662 if (opts.stackSort != 'false') { 3663 for (var i = 0; i < cards.length; ++i) { 3664 // Find the stack with the shortest height, but give preference to 3665 // left to right order. 3666 var minHeight = $stacks[0].height(); 3667 var minIndex = 0; 3668 3669 for (var j = 1; j < numStacks; ++j) { 3670 var height = $stacks[j].height(); 3671 if (height < minHeight - 45) { 3672 minHeight = height; 3673 minIndex = j; 3674 } 3675 } 3676 3677 $stacks[minIndex].append($(cards[i])); 3678 } 3679 } 3680 3681 }; 3682 3683 /* 3684 Create a resource card using the given resource object and a list of html 3685 configured options. Returns a jquery object containing the element. 3686 */ 3687 function createResourceElement(resource, opts, plusone) { 3688 var $el; 3689 3690 // The difference here is that generic cards are not entirely clickable 3691 // so its a div instead of an a tag, also the generic one is not given 3692 // the resource-card class so it appears with a transparent background 3693 // and can be styled in whatever way the css setup. 3694 if (opts.resourceStyle == 'generic') { 3695 $el = $('<div>') 3696 .addClass('resource') 3697 .attr('href', cleanUrl(resource.url)) 3698 .decorateResource(resource, opts); 3699 } else { 3700 var cls = 'resource resource-card'; 3701 3702 $el = $('<a>') 3703 .addClass(cls) 3704 .attr('href', cleanUrl(resource.url)) 3705 .decorateResourceCard(resource, plusone); 3706 } 3707 3708 return $el; 3709 } 3710 3711 function createResponsiveFlowColumn(cardSize) { 3712 var cardWidth = parseInt(cardSize.match(/(\d+)/)[1], 10); 3713 var column = $('<div>').addClass('col-' + (cardWidth / 3) + 'of6'); 3714 if (cardWidth < 9) { 3715 column.addClass('col-tablet-1of2'); 3716 } else if (cardWidth > 9 && cardWidth < 18) { 3717 column.addClass('col-tablet-1of1'); 3718 } 3719 if (cardWidth < 18) { 3720 column.addClass('col-mobile-1of1') 3721 } 3722 return column; 3723 } 3724 3725 /* Initializes a flow widget, see distribute.scss for generating accompanying css */ 3726 function drawResourcesFlowWidget($widget, opts, resources) { 3727 $widget.empty().addClass('cols'); 3728 var cardSizes = opts.cardSizes || ['6x6']; 3729 var i = 0, j = 0; 3730 var plusone = false; // stop showing plusone buttons on cards 3731 3732 while (i < resources.length) { 3733 var cardSize = cardSizes[j++ % cardSizes.length]; 3734 cardSize = cardSize.replace(/^\s+|\s+$/,''); 3735 3736 var column = createResponsiveFlowColumn(cardSize).appendTo($widget); 3737 3738 // A stack has a third dimension which is the number of stacked items 3739 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/); 3740 var stackCount = 0; 3741 var $stackDiv = null; 3742 3743 if (isStack) { 3744 // Create a stack container which should have the dimensions defined 3745 // by the product of the items inside. 3746 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1] 3747 + 'x' + isStack[2] * isStack[3]) .appendTo(column); 3748 } 3749 3750 // Build each stack item or just a single item 3751 do { 3752 var resource = resources[i]; 3753 3754 var $card = createResourceElement(resources[i], opts, plusone); 3755 3756 $card.addClass('resource-card-' + cardSize + 3757 ' resource-card-' + resource.type); 3758 3759 if (isStack) { 3760 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]); 3761 if (++stackCount == parseInt(isStack[3])) { 3762 $card.addClass('resource-card-row-stack-last'); 3763 stackCount = 0; 3764 } 3765 } else { 3766 stackCount = 0; 3767 } 3768 3769 $card.appendTo($stackDiv || column); 3770 3771 } while (++i < resources.length && stackCount > 0); 3772 } 3773 } 3774 3775 /* Build a site map of resources using a section as a root. */ 3776 function buildSectionList(opts) { 3777 if (opts.section && SECTION_BY_ID[opts.section]) { 3778 return SECTION_BY_ID[opts.section].sections || []; 3779 } 3780 return []; 3781 } 3782 3783 function buildResourceList(opts) { 3784 return $.queryResources(opts); 3785 } 3786 3787 $.queryResources = function(opts) { 3788 var maxResults = opts.maxResults || 100; 3789 3790 var query = opts.query || ''; 3791 var expressions = parseResourceQuery(query); 3792 var addedResourceIndices = {}; 3793 var results = []; 3794 3795 for (var i = 0; i < expressions.length; i++) { 3796 var clauses = expressions[i]; 3797 3798 // build initial set of resources from first clause 3799 var firstClause = clauses[0]; 3800 var resources = []; 3801 switch (firstClause.attr) { 3802 case 'type': 3803 resources = ALL_RESOURCES_BY_TYPE[firstClause.value]; 3804 break; 3805 case 'lang': 3806 resources = ALL_RESOURCES_BY_LANG[firstClause.value]; 3807 break; 3808 case 'tag': 3809 resources = ALL_RESOURCES_BY_TAG[firstClause.value]; 3810 break; 3811 case 'collection': 3812 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || []; 3813 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; }); 3814 break; 3815 case 'section': 3816 var urls = SITE_MAP[firstClause.value].sections || []; 3817 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; }); 3818 break; 3819 } 3820 // console.log(firstClause.attr + ':' + firstClause.value); 3821 resources = resources || []; 3822 3823 // use additional clauses to filter corpus 3824 if (clauses.length > 1) { 3825 var otherClauses = clauses.slice(1); 3826 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses)); 3827 } 3828 3829 // filter out resources already added 3830 if (i > 1) { 3831 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices)); 3832 } 3833 3834 // add to list of already added indices 3835 for (var j = 0; j < resources.length; j++) { 3836 if (resources[j]) { 3837 addedResourceIndices[resources[j].index] = 1; 3838 } 3839 } 3840 3841 // concat to final results list 3842 results = results.concat(resources); 3843 } 3844 3845 if (opts.sortOrder && results.length) { 3846 var attr = opts.sortOrder; 3847 3848 if (opts.sortOrder == 'random') { 3849 var i = results.length, j, temp; 3850 while (--i) { 3851 j = Math.floor(Math.random() * (i + 1)); 3852 temp = results[i]; 3853 results[i] = results[j]; 3854 results[j] = temp; 3855 } 3856 } else { 3857 var desc = attr.charAt(0) == '-'; 3858 if (desc) { 3859 attr = attr.substring(1); 3860 } 3861 results = results.sort(function(x,y) { 3862 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10)); 3863 }); 3864 } 3865 } 3866 3867 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources)); 3868 results = results.slice(0, maxResults); 3869 3870 for (var j = 0; j < results.length; ++j) { 3871 addedPageResources[results[j].index] = 1; 3872 } 3873 3874 return results; 3875 } 3876 3877 3878 function getResourceNotAlreadyAddedFilter(addedResourceIndices) { 3879 return function(resource) { 3880 return resource && !addedResourceIndices[resource.index]; 3881 }; 3882 } 3883 3884 3885 function getResourceMatchesClausesFilter(clauses) { 3886 return function(resource) { 3887 return doesResourceMatchClauses(resource, clauses); 3888 }; 3889 } 3890 3891 3892 function doesResourceMatchClauses(resource, clauses) { 3893 for (var i = 0; i < clauses.length; i++) { 3894 var map; 3895 switch (clauses[i].attr) { 3896 case 'type': 3897 map = IS_RESOURCE_OF_TYPE[clauses[i].value]; 3898 break; 3899 case 'lang': 3900 map = IS_RESOURCE_IN_LANG[clauses[i].value]; 3901 break; 3902 case 'tag': 3903 map = IS_RESOURCE_TAGGED[clauses[i].value]; 3904 break; 3905 } 3906 3907 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) { 3908 return clauses[i].negative; 3909 } 3910 } 3911 return true; 3912 } 3913 3914 function cleanUrl(url) 3915 { 3916 if (url && url.indexOf('//') === -1) { 3917 url = toRoot + url; 3918 } 3919 3920 return url; 3921 } 3922 3923 3924 function parseResourceQuery(query) { 3925 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video') 3926 var expressions = []; 3927 var expressionStrs = query.split(',') || []; 3928 for (var i = 0; i < expressionStrs.length; i++) { 3929 var expr = expressionStrs[i] || ''; 3930 3931 // Break expression into clauses (clause e.g. 'tag:foo') 3932 var clauses = []; 3933 var clauseStrs = expr.split(/(?=[\+\-])/); 3934 for (var j = 0; j < clauseStrs.length; j++) { 3935 var clauseStr = clauseStrs[j] || ''; 3936 3937 // Get attribute and value from clause (e.g. attribute='tag', value='foo') 3938 var parts = clauseStr.split(':'); 3939 var clause = {}; 3940 3941 clause.attr = parts[0].replace(/^\s+|\s+$/g,''); 3942 if (clause.attr) { 3943 if (clause.attr.charAt(0) == '+') { 3944 clause.attr = clause.attr.substring(1); 3945 } else if (clause.attr.charAt(0) == '-') { 3946 clause.negative = true; 3947 clause.attr = clause.attr.substring(1); 3948 } 3949 } 3950 3951 if (parts.length > 1) { 3952 clause.value = parts[1].replace(/^\s+|\s+$/g,''); 3953 } 3954 3955 clauses.push(clause); 3956 } 3957 3958 if (!clauses.length) { 3959 continue; 3960 } 3961 3962 expressions.push(clauses); 3963 } 3964 3965 return expressions; 3966 } 3967})(); 3968 3969(function($) { 3970 3971 /* 3972 Utility method for creating dom for the description area of a card. 3973 Used in decorateResourceCard and decorateResource. 3974 */ 3975 function buildResourceCardDescription(resource, plusone) { 3976 var $description = $('<div>').addClass('description ellipsis'); 3977 3978 $description.append($('<div>').addClass('text').html(resource.summary)); 3979 3980 if (resource.cta) { 3981 $description.append($('<a>').addClass('cta').html(resource.cta)); 3982 } 3983 3984 if (plusone) { 3985 var plusurl = resource.url.indexOf("//") > -1 ? resource.url : 3986 "//developer.android.com/" + resource.url; 3987 3988 $description.append($('<div>').addClass('util') 3989 .append($('<div>').addClass('g-plusone') 3990 .attr('data-size', 'small') 3991 .attr('data-align', 'right') 3992 .attr('data-href', plusurl))); 3993 } 3994 3995 return $description; 3996 } 3997 3998 3999 /* Simple jquery function to create dom for a standard resource card */ 4000 $.fn.decorateResourceCard = function(resource,plusone) { 4001 var section = resource.group || resource.type; 4002 var imgUrl = resource.image || 4003 'assets/images/resource-card-default-android.jpg'; 4004 4005 if (imgUrl.indexOf('//') === -1) { 4006 imgUrl = toRoot + imgUrl; 4007 } 4008 4009 $('<div>').addClass('card-bg') 4010 .css('background-image', 'url(' + (imgUrl || toRoot + 4011 'assets/images/resource-card-default-android.jpg') + ')') 4012 .appendTo(this); 4013 4014 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : '')) 4015 .append($('<div>').addClass('section').text(section)) 4016 .append($('<div>').addClass('title').html(resource.title)) 4017 .append(buildResourceCardDescription(resource, plusone)) 4018 .appendTo(this); 4019 4020 return this; 4021 }; 4022 4023 /* Simple jquery function to create dom for a resource section card (menu) */ 4024 $.fn.decorateResourceSection = function(section,plusone) { 4025 var resource = section.resource; 4026 //keep url clean for matching and offline mode handling 4027 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot; 4028 var $base = $('<a>') 4029 .addClass('card-bg') 4030 .attr('href', resource.url) 4031 .append($('<div>').addClass('card-section-icon') 4032 .append($('<div>').addClass('icon')) 4033 .append($('<div>').addClass('section').html(resource.title))) 4034 .appendTo(this); 4035 4036 var $cardInfo = $('<div>').addClass('card-info').appendTo(this); 4037 4038 if (section.sections && section.sections.length) { 4039 // Recurse the section sub-tree to find a resource image. 4040 var stack = [section]; 4041 4042 while (stack.length) { 4043 if (stack[0].resource.image) { 4044 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')'); 4045 break; 4046 } 4047 4048 if (stack[0].sections) { 4049 stack = stack.concat(stack[0].sections); 4050 } 4051 4052 stack.shift(); 4053 } 4054 4055 var $ul = $('<ul>') 4056 .appendTo($cardInfo); 4057 4058 var max = section.sections.length > 3 ? 3 : section.sections.length; 4059 4060 for (var i = 0; i < max; ++i) { 4061 4062 var subResource = section.sections[i]; 4063 if (!plusone) { 4064 $('<li>') 4065 .append($('<a>').attr('href', subResource.url) 4066 .append($('<div>').addClass('title').html(subResource.title)) 4067 .append($('<div>').addClass('description ellipsis') 4068 .append($('<div>').addClass('text').html(subResource.summary)) 4069 .append($('<div>').addClass('util')))) 4070 .appendTo($ul); 4071 } else { 4072 $('<li>') 4073 .append($('<a>').attr('href', subResource.url) 4074 .append($('<div>').addClass('title').html(subResource.title)) 4075 .append($('<div>').addClass('description ellipsis') 4076 .append($('<div>').addClass('text').html(subResource.summary)) 4077 .append($('<div>').addClass('util') 4078 .append($('<div>').addClass('g-plusone') 4079 .attr('data-size', 'small') 4080 .attr('data-align', 'right') 4081 .attr('data-href', resource.url))))) 4082 .appendTo($ul); 4083 } 4084 } 4085 4086 // Add a more row 4087 if (max < section.sections.length) { 4088 $('<li>') 4089 .append($('<a>').attr('href', resource.url) 4090 .append($('<div>') 4091 .addClass('title') 4092 .text('More'))) 4093 .appendTo($ul); 4094 } 4095 } else { 4096 // No sub-resources, just render description? 4097 } 4098 4099 return this; 4100 }; 4101 4102 4103 4104 4105 /* Render other types of resource styles that are not cards. */ 4106 $.fn.decorateResource = function(resource, opts) { 4107 var imgUrl = resource.image || 4108 'assets/images/resource-card-default-android.jpg'; 4109 var linkUrl = resource.url; 4110 4111 if (imgUrl.indexOf('//') === -1) { 4112 imgUrl = toRoot + imgUrl; 4113 } 4114 4115 if (linkUrl && linkUrl.indexOf('//') === -1) { 4116 linkUrl = toRoot + linkUrl; 4117 } 4118 4119 $(this).append( 4120 $('<div>').addClass('image') 4121 .css('background-image', 'url(' + imgUrl + ')'), 4122 $('<div>').addClass('info').append( 4123 $('<h4>').addClass('title').html(resource.title), 4124 $('<p>').addClass('summary').html(resource.summary), 4125 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More') 4126 ) 4127 ); 4128 4129 return this; 4130 }; 4131})(jQuery); 4132 4133 4134/* Calculate the vertical area remaining */ 4135(function($) { 4136 $.fn.ellipsisfade= function(lineHeight) { 4137 this.each(function() { 4138 // get element text 4139 var $this = $(this); 4140 var remainingHeight = $this.parent().parent().height(); 4141 $this.parent().siblings().each(function () 4142 { 4143 if ($(this).is(":visible")) { 4144 var h = $(this).outerHeight(true); 4145 remainingHeight = remainingHeight - h; 4146 } 4147 }); 4148 4149 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight 4150 $this.parent().css({'height': adjustedRemainingHeight}); 4151 $this.css({'height': "auto"}); 4152 }); 4153 4154 return this; 4155 }; 4156}) (jQuery); 4157 4158/* 4159 Fullscreen Carousel 4160 4161 The following allows for an area at the top of the page that takes over the 4162 entire browser height except for its top offset and an optional bottom 4163 padding specified as a data attribute. 4164 4165 HTML: 4166 4167 <div class="fullscreen-carousel"> 4168 <div class="fullscreen-carousel-content"> 4169 <!-- content here --> 4170 </div> 4171 <div class="fullscreen-carousel-content"> 4172 <!-- content here --> 4173 </div> 4174 4175 etc ... 4176 4177 </div> 4178 4179 Control over how the carousel takes over the screen can mostly be defined in 4180 a css file. Setting min-height on the .fullscreen-carousel-content elements 4181 will prevent them from shrinking to far vertically when the browser is very 4182 short, and setting max-height on the .fullscreen-carousel itself will prevent 4183 the area from becoming to long in the case that the browser is stretched very 4184 tall. 4185 4186 There is limited functionality for having multiple sections since that request 4187 was removed, but it is possible to add .next-arrow and .prev-arrow elements to 4188 scroll between multiple content areas. 4189*/ 4190 4191(function() { 4192 $(document).ready(function() { 4193 $('.fullscreen-carousel').each(function() { 4194 initWidget(this); 4195 }); 4196 }); 4197 4198 function initWidget(widget) { 4199 var $widget = $(widget); 4200 4201 var topOffset = $widget.offset().top; 4202 var padBottom = parseInt($widget.data('paddingbottom')) || 0; 4203 var maxHeight = 0; 4204 var minHeight = 0; 4205 var $content = $widget.find('.fullscreen-carousel-content'); 4206 var $nextArrow = $widget.find('.next-arrow'); 4207 var $prevArrow = $widget.find('.prev-arrow'); 4208 var $curSection = $($content[0]); 4209 4210 if ($content.length <= 1) { 4211 $nextArrow.hide(); 4212 $prevArrow.hide(); 4213 } else { 4214 $nextArrow.click(function() { 4215 var index = ($content.index($curSection) + 1); 4216 $curSection.hide(); 4217 $curSection = $($content[index >= $content.length ? 0 : index]); 4218 $curSection.show(); 4219 }); 4220 4221 $prevArrow.click(function() { 4222 var index = ($content.index($curSection) - 1); 4223 $curSection.hide(); 4224 $curSection = $($content[index < 0 ? $content.length - 1 : 0]); 4225 $curSection.show(); 4226 }); 4227 } 4228 4229 // Just hide all content sections except first. 4230 $content.each(function(index) { 4231 if ($(this).height() > minHeight) minHeight = $(this).height(); 4232 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''}); 4233 }); 4234 4235 // Register for changes to window size, and trigger. 4236 $(window).resize(resizeWidget); 4237 resizeWidget(); 4238 4239 function resizeWidget() { 4240 var height = $(window).height() - topOffset - padBottom; 4241 $widget.width($(window).width()); 4242 $widget.height(height < minHeight ? minHeight : 4243 (maxHeight && height > maxHeight ? maxHeight : height)); 4244 } 4245 } 4246})(); 4247 4248 4249 4250 4251 4252/* 4253 Tab Carousel 4254 4255 The following allows tab widgets to be installed via the html below. Each 4256 tab content section should have a data-tab attribute matching one of the 4257 nav items'. Also each tab content section should have a width matching the 4258 tab carousel. 4259 4260 HTML: 4261 4262 <div class="tab-carousel"> 4263 <ul class="tab-nav"> 4264 <li><a href="#" data-tab="handsets">Handsets</a> 4265 <li><a href="#" data-tab="wearable">Wearable</a> 4266 <li><a href="#" data-tab="tv">TV</a> 4267 </ul> 4268 4269 <div class="tab-carousel-content"> 4270 <div data-tab="handsets"> 4271 <!--Full width content here--> 4272 </div> 4273 4274 <div data-tab="wearable"> 4275 <!--Full width content here--> 4276 </div> 4277 4278 <div data-tab="tv"> 4279 <!--Full width content here--> 4280 </div> 4281 </div> 4282 </div> 4283 4284*/ 4285(function() { 4286 $(document).ready(function() { 4287 $('.tab-carousel').each(function() { 4288 initWidget(this); 4289 }); 4290 }); 4291 4292 function initWidget(widget) { 4293 var $widget = $(widget); 4294 var $nav = $widget.find('.tab-nav'); 4295 var $anchors = $nav.find('[data-tab]'); 4296 var $li = $nav.find('li'); 4297 var $contentContainer = $widget.find('.tab-carousel-content'); 4298 var $tabs = $contentContainer.find('[data-tab]'); 4299 var $curTab = $($tabs[0]); // Current tab is first tab. 4300 var width = $widget.width(); 4301 4302 // Setup nav interactivity. 4303 $anchors.click(function(evt) { 4304 evt.preventDefault(); 4305 var query = '[data-tab=' + $(this).data('tab') + ']'; 4306 transitionWidget($tabs.filter(query)); 4307 }); 4308 4309 // Add highlight for navigation on first item. 4310 var $highlight = $('<div>').addClass('highlight') 4311 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'}) 4312 .appendTo($nav); 4313 4314 // Store height since we will change contents to absolute. 4315 $contentContainer.height($contentContainer.height()); 4316 4317 // Absolutely position tabs so they're ready for transition. 4318 $tabs.each(function(index) { 4319 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'}); 4320 }); 4321 4322 function transitionWidget($toTab) { 4323 if (!$curTab.is($toTab)) { 4324 var curIndex = $tabs.index($curTab[0]); 4325 var toIndex = $tabs.index($toTab[0]); 4326 var dir = toIndex > curIndex ? 1 : -1; 4327 4328 // Animate content sections. 4329 $toTab.css({left:(width * dir) + 'px'}); 4330 $curTab.animate({left:(width * -dir) + 'px'}); 4331 $toTab.animate({left:'0'}); 4332 4333 // Animate navigation highlight. 4334 $highlight.animate({left:$($li[toIndex]).position().left + 'px', 4335 width:$($li[toIndex]).outerWidth() + 'px'}) 4336 4337 // Store new current section. 4338 $curTab = $toTab; 4339 } 4340 } 4341 } 4342})(); 4343 4344(function($) { 4345 'use strict'; 4346 4347 /** 4348 * Toggle Floating Label state. 4349 * @param {HTMLElement} el - The DOM element. 4350 * @param options 4351 * @constructor 4352 */ 4353 function FloatingLabel(el, options) { 4354 this.el = $(el); 4355 this.options = $.extend({}, FloatingLabel.DEFAULTS_, options); 4356 this.group = this.el.closest('.dac-form-input-group'); 4357 this.input = this.group.find('.dac-form-input'); 4358 4359 this.checkValue_ = this.checkValue_.bind(this); 4360 this.checkValue_(); 4361 4362 this.input.on('focus', function() { 4363 this.group.addClass('dac-focused'); 4364 }.bind(this)); 4365 this.input.on('blur', function() { 4366 this.group.removeClass('dac-focused'); 4367 this.checkValue_(); 4368 }.bind(this)); 4369 this.input.on('keyup', this.checkValue_); 4370 } 4371 4372 /** 4373 * The label is moved out of the textbox when it has a value. 4374 */ 4375 FloatingLabel.prototype.checkValue_ = function() { 4376 if (this.input.val().length) { 4377 this.group.addClass('dac-has-value'); 4378 } else { 4379 this.group.removeClass('dac-has-value'); 4380 } 4381 }; 4382 4383 /** 4384 * jQuery plugin 4385 * @param {object} options - Override default options. 4386 */ 4387 $.fn.dacFloatingLabel = function(options) { 4388 return this.each(function() { 4389 new FloatingLabel(this, options); 4390 }); 4391 }; 4392 4393 $(document).on('ready.aranja', function() { 4394 $('.dac-form-floatlabel').each(function() { 4395 $(this).dacFloatingLabel($(this).data()); 4396 }); 4397 }); 4398})(jQuery); 4399 4400/* global toRoot, CAROUSEL_OVERRIDE */ 4401(function($) { 4402 // Ordering matters 4403 var TAG_MAP = [ 4404 {from: 'developerstory', to: 'Android Developer Story'}, 4405 {from: 'googleplay', to: 'Google Play'} 4406 ]; 4407 4408 function DacCarouselQuery(el) { 4409 this.el = $(el); 4410 4411 var opts = this.el.data(); 4412 opts.maxResults = parseInt(opts.maxResults || '100', 10); 4413 opts.query = opts.carouselQuery; 4414 var resources = $.queryResources(opts); 4415 4416 this.el.empty(); 4417 $(resources).map(function() { 4418 var resource = $.extend({}, this, CAROUSEL_OVERRIDE[this.url]); 4419 var slide = $('<article class="dac-expand dac-hero">'); 4420 var image = cleanUrl(resource.heroImage || resource.image); 4421 var fullBleed = image && !resource.heroColor; 4422 4423 // Configure background 4424 slide.css({ 4425 backgroundImage: fullBleed ? 'url(' + image + ')' : '', 4426 backgroundColor: resource.heroColor || '' 4427 }); 4428 4429 // Should copy be inverted 4430 slide.toggleClass('dac-invert', resource.heroInvert || fullBleed); 4431 slide.toggleClass('dac-darken', fullBleed); 4432 4433 var cols = $('<div class="cols dac-hero-content">'); 4434 4435 // inline image column 4436 var rightCol = $('<div class="col-1of2 col-push-1of2 dac-hero-figure">') 4437 .appendTo(cols); 4438 4439 if (!fullBleed && image) { 4440 rightCol.append($('<img>').attr('src', image)); 4441 } 4442 4443 // info column 4444 $('<div class="col-1of2 col-pull-1of2">') 4445 .append($('<div class="dac-hero-tag">').text(formatTag(resource))) 4446 .append($('<h1 class="dac-hero-title">').text(formatTitle(resource))) 4447 .append($('<p class="dac-hero-description">').text(resource.summary)) 4448 .append($('<a class="dac-hero-cta">') 4449 .text(formatCTA(resource)) 4450 .attr('href', cleanUrl(resource.url)) 4451 .prepend($('<span class="dac-sprite dac-auto-chevron">')) 4452 ) 4453 .appendTo(cols); 4454 4455 slide.append(cols.wrap('<div class="wrap">').parent()); 4456 return slide[0]; 4457 }).prependTo(this.el); 4458 4459 // Pagination element. 4460 this.el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>'); 4461 4462 this.el.dacCarousel(); 4463 } 4464 4465 function cleanUrl(url) { 4466 if (url && url.indexOf('//') === -1) { 4467 url = toRoot + url; 4468 } 4469 return url; 4470 } 4471 4472 function formatTag(resource) { 4473 // Hmm, need a better more scalable solution for this. 4474 for (var i = 0, mapping; mapping = TAG_MAP[i]; i++) { 4475 if (resource.tags.indexOf(mapping.from) > -1) { 4476 return mapping.to; 4477 } 4478 } 4479 return resource.type; 4480 } 4481 4482 function formatTitle(resource) { 4483 return resource.title.replace(/android developer story: /i, ''); 4484 } 4485 4486 function formatCTA(resource) { 4487 return resource.type === 'youtube' ? 'Watch the video' : 'Learn more'; 4488 } 4489 4490 // jQuery plugin 4491 $.fn.dacCarouselQuery = function() { 4492 return this.each(function() { 4493 var el = $(this); 4494 var data = el.data('dac.carouselQuery'); 4495 4496 if (!data) { el.data('dac.carouselQuery', (data = new DacCarouselQuery(el))); } 4497 }); 4498 }; 4499 4500 // Data API 4501 $(function() { 4502 $('[data-carousel-query]').dacCarouselQuery(); 4503 }); 4504})(jQuery); 4505 4506(function($) { 4507 /** 4508 * A CSS based carousel, inspired by SequenceJS. 4509 * @param {jQuery} el 4510 * @param {object} options 4511 * @constructor 4512 */ 4513 function DacCarousel(el, options) { 4514 this.el = $(el); 4515 this.options = options = $.extend({}, DacCarousel.OPTIONS, this.el.data(), options || {}); 4516 this.frames = this.el.find(options.frameSelector); 4517 this.count = this.frames.size(); 4518 this.current = options.start; 4519 4520 this.initPagination(); 4521 this.initEvents(); 4522 this.initFrame(); 4523 } 4524 4525 DacCarousel.OPTIONS = { 4526 auto: true, 4527 autoTime: 10000, 4528 autoMinTime: 5000, 4529 btnPrev: '[data-carousel-prev]', 4530 btnNext: '[data-carousel-next]', 4531 frameSelector: 'article', 4532 loop: true, 4533 start: 0, 4534 pagination: '[data-carousel-pagination]' 4535 }; 4536 4537 DacCarousel.prototype.initPagination = function() { 4538 this.pagination = $([]); 4539 if (!this.options.pagination) { return; } 4540 4541 var pagination = $('<ul class="dac-pagination">'); 4542 var parent = this.el; 4543 if (typeof this.options.pagination === 'string') { parent = this.el.find(this.options.pagination); } 4544 4545 if (this.count > 1) { 4546 for (var i = 0; i < this.count; i++) { 4547 var li = $('<li class="dac-pagination-item">').text(i); 4548 if (i === this.options.start) { li.addClass('active'); } 4549 li.click(this.go.bind(this, i)); 4550 4551 pagination.append(li); 4552 } 4553 this.pagination = pagination.children(); 4554 parent.append(pagination); 4555 } 4556 }; 4557 4558 DacCarousel.prototype.initEvents = function() { 4559 var that = this; 4560 4561 this.el.hover(function() { 4562 that.pauseRotateTimer(); 4563 }, function() { 4564 that.startRotateTimer(); 4565 }); 4566 4567 $(this.options.btnPrev).click(function(e) { 4568 e.preventDefault(); 4569 that.prev(); 4570 }); 4571 4572 $(this.options.btnNext).click(function(e) { 4573 e.preventDefault(); 4574 that.next(); 4575 }); 4576 }; 4577 4578 DacCarousel.prototype.initFrame = function() { 4579 this.frames.removeClass('active').eq(this.options.start).addClass('active'); 4580 }; 4581 4582 DacCarousel.prototype.startRotateTimer = function() { 4583 if (!this.options.auto || this.rotateTimer) { return; } 4584 this.rotateTimer = setTimeout(this.next.bind(this), this.options.autoTime); 4585 }; 4586 4587 DacCarousel.prototype.pauseRotateTimer = function() { 4588 clearTimeout(this.rotateTimer); 4589 this.rotateTimer = null; 4590 }; 4591 4592 DacCarousel.prototype.prev = function() { 4593 this.go(this.current - 1); 4594 }; 4595 4596 DacCarousel.prototype.next = function() { 4597 this.go(this.current + 1); 4598 }; 4599 4600 DacCarousel.prototype.go = function(next) { 4601 // Figure out what the next slide is. 4602 while (this.count > 0 && next >= this.count) { next -= this.count; } 4603 while (next < 0) { next += this.count; } 4604 4605 // Cancel if we're already on that slide. 4606 if (next === this.current) { return; } 4607 4608 // Prepare next slide. 4609 this.frames.eq(next).removeClass('out'); 4610 4611 // Recalculate styles before starting slide transition. 4612 var that = this; 4613 resolveStyles(this.el[0], function() { 4614 // Update pagination 4615 that.pagination.removeClass('active').eq(next).addClass('active'); 4616 4617 // Transition out current frame 4618 that.frames.eq(that.current).toggleClass('active out'); 4619 4620 // Transition in a new frame 4621 that.frames.eq(next).toggleClass('active'); 4622 4623 that.current = next; 4624 }); 4625 }; 4626 4627 // Helper 4628 function resolveStyles(el, callback) { 4629 /*jshint expr:true*/ 4630 el.offsetTop; 4631 callback(); 4632 } 4633 4634 // jQuery plugin 4635 $.fn.dacCarousel = function() { 4636 this.each(function() { 4637 var $el = $(this); 4638 $el.data('dac-carousel', new DacCarousel(this)); 4639 }); 4640 return this; 4641 }; 4642 4643 // Data API 4644 $(function() { 4645 $('[data-carousel]').dacCarousel(); 4646 }); 4647})(jQuery); 4648 4649(function($) { 4650 'use strict'; 4651 4652 /** 4653 * Toggle the visabilty of the mobile navigation. 4654 * @param {HTMLElement} el - The DOM element. 4655 * @param options 4656 * @constructor 4657 */ 4658 function ToggleModal(el, options) { 4659 this.el = $(el); 4660 this.options = $.extend({}, ToggleModal.DEFAULTS_, options); 4661 this.el.on('click', this.clickHandler_.bind(this)); 4662 } 4663 4664 ToggleModal.DEFAULTS_ = { 4665 toggleClass: 'dac-modal-open' 4666 }; 4667 4668 /** 4669 * The actual toggle logic. 4670 * @param event 4671 * @private 4672 */ 4673 ToggleModal.prototype.clickHandler_ = function(event) { 4674 event.preventDefault(); 4675 //TODO: Toggle a class on the modal itself 4676 $('body').toggleClass(this.options.toggleClass); 4677 $('.dac-modal-dimmer').toggleClass('dac-active'); 4678 $('.dac-modal-window').toggleClass('dac-active'); 4679 }; 4680 4681 /** 4682 * jQuery plugin 4683 * @param {object} options - Override default options. 4684 */ 4685 $.fn.dacToggleModal = function(options) { 4686 return this.each(function() { 4687 new ToggleModal(this, options); 4688 }); 4689 }; 4690 4691 /** 4692 * Data Attribute API 4693 */ 4694 $(document).on('ready.aranja', function() { 4695 $('[data-modal-toogle]').each(function() { 4696 $(this).dacToggleModal($(this).data()); 4697 }); 4698 }); 4699})(jQuery); 4700 4701(function($) { 4702 'use strict'; 4703 4704 /** 4705 * Toggle the visabilty of the mobile navigation. 4706 * @param {HTMLElement} el - The DOM element. 4707 * @param options 4708 * @constructor 4709 */ 4710 function ToggleNav(el, options) { 4711 this.el = $(el); 4712 this.options = $.extend({}, ToggleNav.DEFAULTS_, options); 4713 this.options.target = [this.options.navigation]; 4714 4715 if (this.options.body) {this.options.target.push('body')} 4716 if (this.options.dimmer) {this.options.target.push(this.options.dimmer)} 4717 4718 this.el.on('click', this.clickHandler_.bind(this)); 4719 } 4720 4721 /** 4722 * ToggleNav Default Settings 4723 * @type {{body: boolean, dimmer: string, navigation: string, toggleClass: string}} 4724 * @private 4725 */ 4726 ToggleNav.DEFAULTS_ = { 4727 body: true, 4728 dimmer: '.dac-nav-dimmer', 4729 navigation: '[data-dac-nav]', 4730 toggleClass: 'dac-nav-open' 4731 }; 4732 4733 /** 4734 * The actual toggle logic. 4735 * @param event 4736 * @private 4737 */ 4738 ToggleNav.prototype.clickHandler_ = function(event) { 4739 event.preventDefault(); 4740 $(this.options.target.join(', ')).toggleClass(this.options.toggleClass); 4741 }; 4742 4743 /** 4744 * jQuery plugin 4745 * @param {object} options - Override default options. 4746 */ 4747 $.fn.dacToggleMobileNav = function(options) { 4748 return this.each(function() { 4749 new ToggleNav(this, options); 4750 }); 4751 }; 4752 4753 /** 4754 * Data Attribute API 4755 */ 4756 $(window).on('load.aranja', function() { 4757 $('[data-dac-toggle-nav]').each(function() { 4758 $(this).dacToggleMobileNav($(this).data()); 4759 }); 4760 }); 4761})(jQuery); 4762 4763(function($) { 4764 'use strict'; 4765 4766 /** 4767 * Submit the newsletter form to a Google Form. 4768 * @param {HTMLElement} el - The Form DOM element. 4769 * @constructor 4770 */ 4771 function NewsletterForm(el) { 4772 this.el = $(el); 4773 this.url = this.el.attr('action'); 4774 this.el.on('submit', this.submitHandler_.bind(this)); 4775 } 4776 4777 /** 4778 * Close the modal when the form is sent. 4779 * @private 4780 */ 4781 NewsletterForm.prototype.submitHandler_ = function() { 4782 //TODO: Close the modal with an event and let modal.js handle this 4783 $('body').removeClass('dac-modal-open'); 4784 $('.dac-modal-dimmer').removeClass('dac-active'); 4785 $('.dac-modal-window').removeClass('dac-active'); 4786 }; 4787 4788 /** 4789 * jQuery plugin 4790 * @param {object} options - Override default options. 4791 */ 4792 $.fn.dacNewsletterForm = function(options) { 4793 return this.each(function() { 4794 new NewsletterForm(this, options); 4795 }); 4796 }; 4797 4798 /** 4799 * Data Attribute API 4800 */ 4801 $(document).on('ready.aranja', function() { 4802 $('[data-newsletter-form]').each(function() { 4803 $(this).dacNewsletterForm(); 4804 }); 4805 }); 4806})(jQuery); 4807