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