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