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