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