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