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