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