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