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