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