docs.js revision ca1230c9a251fd0eaf68665c424a69442f717df1
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 22var navBarIsFixed = false; 23$(document).ready(function() { 24 25 // load json file for JD doc search suggestions 26 $.getScript(toRoot + 'jd_lists_unified.js'); 27 // load json file for Android API search suggestions 28 $.getScript(toRoot + 'reference/lists.js'); 29 // load json files for Google services API suggestions 30 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) { 31 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data 32 if(jqxhr.status === 200) { 33 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) { 34 if(jqxhr.status === 200) { 35 // combine GCM and GMS data 36 GOOGLE_DATA = GMS_DATA; 37 var start = GOOGLE_DATA.length; 38 for (var i=0; i<GCM_DATA.length; i++) { 39 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label, 40 link:GCM_DATA[i].link, type:GCM_DATA[i].type}); 41 } 42 } 43 }); 44 } 45 }); 46 47 // setup keyboard listener for search shortcut 48 $('body').keyup(function(event) { 49 if (event.which == 191) { 50 $('#search_autocomplete').focus(); 51 } 52 }); 53 54 // init the fullscreen toggle click event 55 $('#nav-swap .fullscreen').click(function(){ 56 if ($(this).hasClass('disabled')) { 57 toggleFullscreen(true); 58 } else { 59 toggleFullscreen(false); 60 } 61 }); 62 63 // initialize the divs with custom scrollbars 64 $('.scroll-pane').jScrollPane( {verticalGutter:0} ); 65 66 // add HRs below all H2s (except for a few other h2 variants) 67 $('h2').not('#qv h2').not('#tb h2').not('.sidebox h2').not('#devdoc-nav h2').not('h2.norule').css({marginBottom:0}).after('<hr/>'); 68 69 // set up the search close button 70 $('.search .close').click(function() { 71 $searchInput = $('#search_autocomplete'); 72 $searchInput.attr('value', ''); 73 $(this).addClass("hide"); 74 $("#search-container").removeClass('active'); 75 $("#search_autocomplete").blur(); 76 search_focus_changed($searchInput.get(), false); 77 hideResults(); 78 }); 79 80 // Set up quicknav 81 var quicknav_open = false; 82 $("#btn-quicknav").click(function() { 83 if (quicknav_open) { 84 $(this).removeClass('active'); 85 quicknav_open = false; 86 collapse(); 87 } else { 88 $(this).addClass('active'); 89 quicknav_open = true; 90 expand(); 91 } 92 }) 93 94 var expand = function() { 95 $('#header-wrap').addClass('quicknav'); 96 $('#quicknav').stop().show().animate({opacity:'1'}); 97 } 98 99 var collapse = function() { 100 $('#quicknav').stop().animate({opacity:'0'}, 100, function() { 101 $(this).hide(); 102 $('#header-wrap').removeClass('quicknav'); 103 }); 104 } 105 106 107 //Set up search 108 $("#search_autocomplete").focus(function() { 109 $("#search-container").addClass('active'); 110 }) 111 $("#search-container").mouseover(function() { 112 $("#search-container").addClass('active'); 113 $("#search_autocomplete").focus(); 114 }) 115 $("#search-container").mouseout(function() { 116 if ($("#search_autocomplete").is(":focus")) return; 117 if ($("#search_autocomplete").val() == '') { 118 setTimeout(function(){ 119 $("#search-container").removeClass('active'); 120 $("#search_autocomplete").blur(); 121 },250); 122 } 123 }) 124 $("#search_autocomplete").blur(function() { 125 if ($("#search_autocomplete").val() == '') { 126 $("#search-container").removeClass('active'); 127 } 128 }) 129 130 131 // prep nav expandos 132 var pagePath = document.location.pathname; 133 // account for intl docs by removing the intl/*/ path 134 if (pagePath.indexOf("/intl/") == 0) { 135 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last / 136 } 137 138 if (pagePath.indexOf(SITE_ROOT) == 0) { 139 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') { 140 pagePath += 'index.html'; 141 } 142 } 143 144 // Need a copy of the pagePath before it gets changed in the next block; 145 // it's needed to perform proper tab highlighting in offline docs (see rootDir below) 146 var pagePathOriginal = pagePath; 147 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') { 148 // If running locally, SITE_ROOT will be a relative path, so account for that by 149 // finding the relative URL to this page. This will allow us to find links on the page 150 // leading back to this page. 151 var pathParts = pagePath.split('/'); 152 var relativePagePathParts = []; 153 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3; 154 for (var i = 0; i < upDirs; i++) { 155 relativePagePathParts.push('..'); 156 } 157 for (var i = 0; i < upDirs; i++) { 158 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]); 159 } 160 relativePagePathParts.push(pathParts[pathParts.length - 1]); 161 pagePath = relativePagePathParts.join('/'); 162 } else { 163 // Otherwise the page path is already an absolute URL 164 } 165 166 // Highlight the header tabs... 167 // highlight Design tab 168 if ($("body").hasClass("design")) { 169 $("#header li.design a").addClass("selected"); 170 $("#sticky-header").addClass("design"); 171 172 // highlight Develop tab 173 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) { 174 $("#header li.develop a").addClass("selected"); 175 $("#sticky-header").addClass("develop"); 176 // In Develop docs, also highlight appropriate sub-tab 177 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1)); 178 if (rootDir == "training") { 179 $("#nav-x li.training a").addClass("selected"); 180 } else if (rootDir == "guide") { 181 $("#nav-x li.guide a").addClass("selected"); 182 } else if (rootDir == "reference") { 183 // If the root is reference, but page is also part of Google Services, select Google 184 if ($("body").hasClass("google")) { 185 $("#nav-x li.google a").addClass("selected"); 186 } else { 187 $("#nav-x li.reference a").addClass("selected"); 188 } 189 } else if ((rootDir == "tools") || (rootDir == "sdk")) { 190 $("#nav-x li.tools a").addClass("selected"); 191 } else if ($("body").hasClass("google")) { 192 $("#nav-x li.google a").addClass("selected"); 193 } else if ($("body").hasClass("samples")) { 194 $("#nav-x li.samples a").addClass("selected"); 195 } 196 197 // highlight Distribute tab 198 } else if ($("body").hasClass("distribute")) { 199 $("#header li.distribute a").addClass("selected"); 200 $("#sticky-header").addClass("distribute"); 201 202 var baseFrag = pagePathOriginal.indexOf('/', 1) + 1; 203 var secondFrag = pagePathOriginal.substring(baseFrag, pagePathOriginal.indexOf('/', baseFrag)); 204 if (secondFrag == "users") { 205 $("#nav-x li.users a").addClass("selected"); 206 } else if (secondFrag == "engage") { 207 $("#nav-x li.engage a").addClass("selected"); 208 } else if (secondFrag == "monetize") { 209 $("#nav-x li.monetize a").addClass("selected"); 210 } else if (secondFrag == "tools") { 211 $("#nav-x li.disttools a").addClass("selected"); 212 } else if (secondFrag == "stories") { 213 $("#nav-x li.stories a").addClass("selected"); 214 } else if (secondFrag == "essentials") { 215 $("#nav-x li.essentials a").addClass("selected"); 216 } else if (secondFrag == "googleplay") { 217 $("#nav-x li.googleplay a").addClass("selected"); 218 } 219 } else if ($("body").hasClass("about")) { 220 $("#sticky-header").addClass("about"); 221 } 222 223 // set global variable so we can highlight the sidenav a bit later (such as for google reference) 224 // and highlight the sidenav 225 mPagePath = pagePath; 226 highlightSidenav(); 227 buildBreadcrumbs(); 228 229 // set up prev/next links if they exist 230 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]'); 231 var $selListItem; 232 if ($selNavLink.length) { 233 $selListItem = $selNavLink.closest('li'); 234 235 // set up prev links 236 var $prevLink = []; 237 var $prevListItem = $selListItem.prev('li'); 238 239 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true : 240false; // navigate across topic boundaries only in design docs 241 if ($prevListItem.length) { 242 if ($prevListItem.hasClass('nav-section')) { 243 // jump to last topic of previous section 244 $prevLink = $prevListItem.find('a:last'); 245 } else if (!$selListItem.hasClass('nav-section')) { 246 // jump to previous topic in this section 247 $prevLink = $prevListItem.find('a:eq(0)'); 248 } 249 } else { 250 // jump to this section's index page (if it exists) 251 var $parentListItem = $selListItem.parents('li'); 252 $prevLink = $selListItem.parents('li').find('a'); 253 254 // except if cross boundaries aren't allowed, and we're at the top of a section already 255 // (and there's another parent) 256 if (!crossBoundaries && $parentListItem.hasClass('nav-section') 257 && $selListItem.hasClass('nav-section')) { 258 $prevLink = []; 259 } 260 } 261 262 // set up next links 263 var $nextLink = []; 264 var startClass = false; 265 var training = $(".next-class-link").length; // decides whether to provide "next class" link 266 var isCrossingBoundary = false; 267 268 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) { 269 // we're on an index page, jump to the first topic 270 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)'); 271 272 // if there aren't any children, go to the next section (required for About pages) 273 if($nextLink.length == 0) { 274 $nextLink = $selListItem.next('li').find('a'); 275 } else if ($('.topic-start-link').length) { 276 // as long as there's a child link and there is a "topic start link" (we're on a landing) 277 // then set the landing page "start link" text to be the first doc title 278 $('.topic-start-link').text($nextLink.text().toUpperCase()); 279 } 280 281 // If the selected page has a description, then it's a class or article homepage 282 if ($selListItem.find('a[description]').length) { 283 // this means we're on a class landing page 284 startClass = true; 285 } 286 } else { 287 // jump to the next topic in this section (if it exists) 288 $nextLink = $selListItem.next('li').find('a:eq(0)'); 289 if ($nextLink.length == 0) { 290 isCrossingBoundary = true; 291 // no more topics in this section, jump to the first topic in the next section 292 $nextLink = $selListItem.parents('li:eq(0)').next('li.nav-section').find('a:eq(0)'); 293 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course) 294 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)'); 295 if ($nextLink.length == 0) { 296 // if that doesn't work, we're at the end of the list, so disable NEXT link 297 $('.next-page-link').attr('href','').addClass("disabled") 298 .click(function() { return false; }); 299 } 300 } 301 } 302 } 303 304 if (startClass) { 305 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide"); 306 307 // if there's no training bar (below the start button), 308 // then we need to add a bottom border to button 309 if (!$("#tb").length) { 310 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'}); 311 } 312 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries 313 $('.content-footer.next-class').show(); 314 $('.next-page-link').attr('href','') 315 .removeClass("hide").addClass("disabled") 316 .click(function() { return false; }); 317 if ($nextLink.length) { 318 $('.next-class-link').attr('href',$nextLink.attr('href')) 319 .removeClass("hide").append($nextLink.html()); 320 $('.next-class-link').find('.new').empty(); 321 } 322 } else { 323 $('.next-page-link').attr('href', $nextLink.attr('href')).removeClass("hide"); 324 } 325 326 if (!startClass && $prevLink.length) { 327 var prevHref = $prevLink.attr('href'); 328 if (prevHref == SITE_ROOT + 'index.html') { 329 // Don't show Previous when it leads to the homepage 330 } else { 331 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide"); 332 } 333 } 334 335 // If this is a training 'article', there should be no prev/next nav 336 // ... if the grandparent is the "nav" ... and it has no child list items... 337 if (training && $selListItem.parents('ul').eq(1).is('[id="nav"]') && 338 !$selListItem.find('li').length) { 339 $('.next-page-link,.prev-page-link').attr('href','').addClass("disabled") 340 .click(function() { return false; }); 341 } 342 343 } 344 345 346 347 // Set up the course landing pages for Training with class names and descriptions 348 if ($('body.trainingcourse').length) { 349 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a'); 350 var $classDescriptions = $classLinks.attr('description'); 351 352 var $olClasses = $('<ol class="class-list"></ol>'); 353 var $liClass; 354 var $imgIcon; 355 var $h2Title; 356 var $pSummary; 357 var $olLessons; 358 var $liLesson; 359 $classLinks.each(function(index) { 360 $liClass = $('<li></li>'); 361 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>'); 362 $pSummary = $('<p class="description">' + $(this).attr('description') + '</p>'); 363 364 $olLessons = $('<ol class="lesson-list"></ol>'); 365 366 $lessons = $(this).closest('li').find('ul li a'); 367 368 if ($lessons.length) { 369 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-tutorial.png" ' 370 + ' width="64" height="64" alt=""/>'); 371 $lessons.each(function(index) { 372 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>'); 373 }); 374 } else { 375 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-article.png" ' 376 + ' width="64" height="64" alt=""/>'); 377 $pSummary.addClass('article'); 378 } 379 380 $liClass.append($h2Title).append($imgIcon).append($pSummary).append($olLessons); 381 $olClasses.append($liClass); 382 }); 383 $('.jd-descr').append($olClasses); 384 } 385 386 // Set up expand/collapse behavior 387 initExpandableNavItems("#nav"); 388 389 390 $(".scroll-pane").scroll(function(event) { 391 event.preventDefault(); 392 return false; 393 }); 394 395 /* Resize nav height when window height changes */ 396 $(window).resize(function() { 397 if ($('#side-nav').length == 0) return; 398 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]'); 399 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed 400 // make sidenav behave when resizing the window and side-scolling is a concern 401 if (navBarIsFixed) { 402 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) { 403 updateSideNavPosition(); 404 } else { 405 updateSidenavFullscreenWidth(); 406 } 407 } 408 resizeNav(); 409 }); 410 411 412 var navBarLeftPos; 413 if ($('#devdoc-nav').length) { 414 setNavBarLeftPos(); 415 } 416 417 418 // Set up play-on-hover <video> tags. 419 $('video.play-on-hover').bind('click', function(){ 420 $(this).get(0).load(); // in case the video isn't seekable 421 $(this).get(0).play(); 422 }); 423 424 // Set up tooltips 425 var TOOLTIP_MARGIN = 10; 426 $('acronym,.tooltip-link').each(function() { 427 var $target = $(this); 428 var $tooltip = $('<div>') 429 .addClass('tooltip-box') 430 .append($target.attr('title')) 431 .hide() 432 .appendTo('body'); 433 $target.removeAttr('title'); 434 435 $target.hover(function() { 436 // in 437 var targetRect = $target.offset(); 438 targetRect.width = $target.width(); 439 targetRect.height = $target.height(); 440 441 $tooltip.css({ 442 left: targetRect.left, 443 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN 444 }); 445 $tooltip.addClass('below'); 446 $tooltip.show(); 447 }, function() { 448 // out 449 $tooltip.hide(); 450 }); 451 }); 452 453 // Set up <h2> deeplinks 454 $('h2').click(function() { 455 var id = $(this).attr('id'); 456 if (id) { 457 document.location.hash = id; 458 } 459 }); 460 461 //Loads the +1 button 462 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true; 463 po.src = 'https://apis.google.com/js/plusone.js'; 464 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s); 465 466 467 // Revise the sidenav widths to make room for the scrollbar 468 // which avoids the visible width from changing each time the bar appears 469 var $sidenav = $("#side-nav"); 470 var sidenav_width = parseInt($sidenav.innerWidth()); 471 472 $("#devdoc-nav #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width 473 474 475 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller 476 477 if ($(".scroll-pane").length > 1) { 478 // Check if there's a user preference for the panel heights 479 var cookieHeight = readCookie("reference_height"); 480 if (cookieHeight) { 481 restoreHeight(cookieHeight); 482 } 483 } 484 485 resizeNav(); 486 487 /* init the language selector based on user cookie for lang */ 488 loadLangPref(); 489 changeNavLang(getLangPref()); 490 491 /* setup event handlers to ensure the overflow menu is visible while picking lang */ 492 $("#language select") 493 .mousedown(function() { 494 $("div.morehover").addClass("hover"); }) 495 .blur(function() { 496 $("div.morehover").removeClass("hover"); }); 497 498 /* some global variable setup */ 499 resizePackagesNav = $("#resize-packages-nav"); 500 classesNav = $("#classes-nav"); 501 devdocNav = $("#devdoc-nav"); 502 503 var cookiePath = ""; 504 if (location.href.indexOf("/reference/") != -1) { 505 cookiePath = "reference_"; 506 } else if (location.href.indexOf("/guide/") != -1) { 507 cookiePath = "guide_"; 508 } else if (location.href.indexOf("/tools/") != -1) { 509 cookiePath = "tools_"; 510 } else if (location.href.indexOf("/training/") != -1) { 511 cookiePath = "training_"; 512 } else if (location.href.indexOf("/design/") != -1) { 513 cookiePath = "design_"; 514 } else if (location.href.indexOf("/distribute/") != -1) { 515 cookiePath = "distribute_"; 516 } 517 518}); 519// END of the onload event 520 521 522function initExpandableNavItems(rootTag) { 523 $(rootTag + ' li.nav-section .nav-section-header').click(function() { 524 var section = $(this).closest('li.nav-section'); 525 if (section.hasClass('expanded')) { 526 /* hide me and descendants */ 527 section.find('ul').slideUp(250, function() { 528 // remove 'expanded' class from my section and any children 529 section.closest('li').removeClass('expanded'); 530 $('li.nav-section', section).removeClass('expanded'); 531 resizeNav(); 532 }); 533 } else { 534 /* show me */ 535 // first hide all other siblings 536 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky'); 537 $others.removeClass('expanded').children('ul').slideUp(250); 538 539 // now expand me 540 section.closest('li').addClass('expanded'); 541 section.children('ul').slideDown(250, function() { 542 resizeNav(); 543 }); 544 } 545 }); 546 547 // Stop expand/collapse behavior when clicking on nav section links 548 // (since we're navigating away from the page) 549 // This selector captures the first instance of <a>, but not those with "#" as the href. 550 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) { 551 window.location.href = $(this).attr('href'); 552 return false; 553 }); 554} 555 556 557/** Create the list of breadcrumb links in the sticky header */ 558function buildBreadcrumbs() { 559 var $breadcrumbUl = $("#sticky-header ul.breadcrumb"); 560 // Add the secondary horizontal nav item, if provided 561 var $selectedSecondNav = $("div#nav-x ul.nav-x a.selected").clone().removeClass("selected"); 562 if ($selectedSecondNav.length) { 563 $breadcrumbUl.prepend($("<li>").append($selectedSecondNav)) 564 } 565 // Add the primary horizontal nav 566 var $selectedFirstNav = $("div#header-wrap ul.nav-x a.selected").clone().removeClass("selected"); 567 // If there's no header nav item, use the logo link and title from alt text 568 if ($selectedFirstNav.length < 1) { 569 $selectedFirstNav = $("<a>") 570 .attr('href', $("div#header .logo a").attr('href')) 571 .text($("div#header .logo img").attr('alt')); 572 } 573 $breadcrumbUl.prepend($("<li>").append($selectedFirstNav)); 574} 575 576 577 578/** Highlight the current page in sidenav, expanding children as appropriate */ 579function highlightSidenav() { 580 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index) 581 if ($("ul#nav li.selected").length) { 582 unHighlightSidenav(); 583 } 584 // look for URL in sidenav, including the hash 585 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]'); 586 587 // If the selNavLink is still empty, look for it without the hash 588 if ($selNavLink.length == 0) { 589 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]'); 590 } 591 592 var $selListItem; 593 if ($selNavLink.length) { 594 // Find this page's <li> in sidenav and set selected 595 $selListItem = $selNavLink.closest('li'); 596 $selListItem.addClass('selected'); 597 598 // Traverse up the tree and expand all parent nav-sections 599 $selNavLink.parents('li.nav-section').each(function() { 600 $(this).addClass('expanded'); 601 $(this).children('ul').show(); 602 }); 603 } 604} 605 606function unHighlightSidenav() { 607 $("ul#nav li.selected").removeClass("selected"); 608 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide(); 609} 610 611function toggleFullscreen(enable) { 612 var delay = 20; 613 var enabled = true; 614 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]'); 615 if (enable) { 616 // Currently NOT USING fullscreen; enable fullscreen 617 stylesheet.removeAttr('disabled'); 618 $('#nav-swap .fullscreen').removeClass('disabled'); 619 $('#devdoc-nav').css({left:''}); 620 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch 621 enabled = true; 622 } else { 623 // Currently USING fullscreen; disable fullscreen 624 stylesheet.attr('disabled', 'disabled'); 625 $('#nav-swap .fullscreen').addClass('disabled'); 626 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch 627 enabled = false; 628 } 629 writeCookie("fullscreen", enabled, null, null); 630 setNavBarLeftPos(); 631 resizeNav(delay); 632 updateSideNavPosition(); 633 setTimeout(initSidenavHeightResize,delay); 634} 635 636 637function setNavBarLeftPos() { 638 navBarLeftPos = $('#body-content').offset().left; 639} 640 641 642function updateSideNavPosition() { 643 var newLeft = $(window).scrollLeft() - navBarLeftPos; 644 $('#devdoc-nav').css({left: -newLeft}); 645 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))}); 646} 647 648// TODO: use $(document).ready instead 649function addLoadEvent(newfun) { 650 var current = window.onload; 651 if (typeof window.onload != 'function') { 652 window.onload = newfun; 653 } else { 654 window.onload = function() { 655 current(); 656 newfun(); 657 } 658 } 659} 660 661var agent = navigator['userAgent'].toLowerCase(); 662// If a mobile phone, set flag and do mobile setup 663if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod 664 (agent.indexOf("blackberry") != -1) || 665 (agent.indexOf("webos") != -1) || 666 (agent.indexOf("mini") != -1)) { // opera mini browsers 667 isMobile = true; 668} 669 670 671$(document).ready(function() { 672 $("pre:not(.no-pretty-print)").addClass("prettyprint"); 673 prettyPrint(); 674}); 675 676 677 678 679/* ######### RESIZE THE SIDENAV HEIGHT ########## */ 680 681function resizeNav(delay) { 682 var $nav = $("#devdoc-nav"); 683 var $window = $(window); 684 var navHeight; 685 686 // Get the height of entire window and the total header height. 687 // Then figure out based on scroll position whether the header is visible 688 var windowHeight = $window.height(); 689 var scrollTop = $window.scrollTop(); 690 var headerHeight = $('#header-wrapper').outerHeight(); 691 var headerVisible = scrollTop < stickyTop; 692 693 // get the height of space between nav and top of window. 694 // Could be either margin or top position, depending on whether the nav is fixed. 695 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1; 696 // add 1 for the #side-nav bottom margin 697 698 // Depending on whether the header is visible, set the side nav's height. 699 if (headerVisible) { 700 // The sidenav height grows as the header goes off screen 701 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin; 702 } else { 703 // Once header is off screen, the nav height is almost full window height 704 navHeight = windowHeight - topMargin; 705 } 706 707 708 709 $scrollPanes = $(".scroll-pane"); 710 if ($scrollPanes.length > 1) { 711 // subtract the height of the api level widget and nav swapper from the available nav height 712 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true)); 713 714 $("#swapper").css({height:navHeight + "px"}); 715 if ($("#nav-tree").is(":visible")) { 716 $("#nav-tree").css({height:navHeight}); 717 } 718 719 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px"; 720 //subtract 10px to account for drag bar 721 722 // if the window becomes small enough to make the class panel height 0, 723 // then the package panel should begin to shrink 724 if (parseInt(classesHeight) <= 0) { 725 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar 726 $("#packages-nav").css({height:navHeight - 10}); 727 } 728 729 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'}); 730 $("#classes-nav .jspContainer").css({height:classesHeight}); 731 732 733 } else { 734 $nav.height(navHeight); 735 } 736 737 if (delay) { 738 updateFromResize = true; 739 delayedReInitScrollbars(delay); 740 } else { 741 reInitScrollbars(); 742 } 743 744} 745 746var updateScrollbars = false; 747var updateFromResize = false; 748 749/* Re-initialize the scrollbars to account for changed nav size. 750 * This method postpones the actual update by a 1/4 second in order to optimize the 751 * scroll performance while the header is still visible, because re-initializing the 752 * scroll panes is an intensive process. 753 */ 754function delayedReInitScrollbars(delay) { 755 // If we're scheduled for an update, but have received another resize request 756 // before the scheduled resize has occured, just ignore the new request 757 // (and wait for the scheduled one). 758 if (updateScrollbars && updateFromResize) { 759 updateFromResize = false; 760 return; 761 } 762 763 // We're scheduled for an update and the update request came from this method's setTimeout 764 if (updateScrollbars && !updateFromResize) { 765 reInitScrollbars(); 766 updateScrollbars = false; 767 } else { 768 updateScrollbars = true; 769 updateFromResize = false; 770 setTimeout('delayedReInitScrollbars()',delay); 771 } 772} 773 774/* Re-initialize the scrollbars to account for changed nav size. */ 775function reInitScrollbars() { 776 var pane = $(".scroll-pane").each(function(){ 777 var api = $(this).data('jsp'); 778 if (!api) { setTimeout(reInitScrollbars,300); return;} 779 api.reinitialise( {verticalGutter:0} ); 780 }); 781 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller 782} 783 784 785/* Resize the height of the nav panels in the reference, 786 * and save the new size to a cookie */ 787function saveNavPanels() { 788 var basePath = getBaseUri(location.pathname); 789 var section = basePath.substring(1,basePath.indexOf("/",1)); 790 writeCookie("height", resizePackagesNav.css("height"), section, null); 791} 792 793 794 795function restoreHeight(packageHeight) { 796 $("#resize-packages-nav").height(packageHeight); 797 $("#packages-nav").height(packageHeight); 798 // var classesHeight = navHeight - packageHeight; 799 // $("#classes-nav").css({height:classesHeight}); 800 // $("#classes-nav .jspContainer").css({height:classesHeight}); 801} 802 803 804 805/* ######### END RESIZE THE SIDENAV HEIGHT ########## */ 806 807 808 809 810 811/** Scroll the jScrollPane to make the currently selected item visible 812 This is called when the page finished loading. */ 813function scrollIntoView(nav) { 814 var $nav = $("#"+nav); 815 var element = $nav.jScrollPane({/* ...settings... */}); 816 var api = element.data('jsp'); 817 818 if ($nav.is(':visible')) { 819 var $selected = $(".selected", $nav); 820 if ($selected.length == 0) { 821 // If no selected item found, exit 822 return; 823 } 824 // get the selected item's offset from its container nav by measuring the item's offset 825 // relative to the document then subtract the container nav's offset relative to the document 826 var selectedOffset = $selected.offset().top - $nav.offset().top; 827 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item 828 // if it's more than 80% down the nav 829 // scroll the item up by an amount equal to 80% the container nav's height 830 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false); 831 } 832 } 833} 834 835 836 837 838 839 840/* Show popup dialogs */ 841function showDialog(id) { 842 $dialog = $("#"+id); 843 $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>'); 844 $dialog.wrapInner('<div/>'); 845 $dialog.removeClass("hide"); 846} 847 848 849 850 851 852/* ######### COOKIES! ########## */ 853 854function readCookie(cookie) { 855 var myCookie = cookie_namespace+"_"+cookie+"="; 856 if (document.cookie) { 857 var index = document.cookie.indexOf(myCookie); 858 if (index != -1) { 859 var valStart = index + myCookie.length; 860 var valEnd = document.cookie.indexOf(";", valStart); 861 if (valEnd == -1) { 862 valEnd = document.cookie.length; 863 } 864 var val = document.cookie.substring(valStart, valEnd); 865 return val; 866 } 867 } 868 return 0; 869} 870 871function writeCookie(cookie, val, section, expiration) { 872 if (val==undefined) return; 873 section = section == null ? "_" : "_"+section+"_"; 874 if (expiration == null) { 875 var date = new Date(); 876 date.setTime(date.getTime()+(10*365*24*60*60*1000)); // default expiration is one week 877 expiration = date.toGMTString(); 878 } 879 var cookieValue = cookie_namespace + section + cookie + "=" + val 880 + "; expires=" + expiration+"; path=/"; 881 document.cookie = cookieValue; 882} 883 884/* ######### END COOKIES! ########## */ 885 886 887var sticky = false; 888var stickyTop; 889var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll 890/* Sets the vertical scoll position at which the sticky bar should appear. 891 This method is called to reset the position when search results appear or hide */ 892function setStickyTop() { 893 stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight(); 894} 895 896/* 897 * Displays sticky nav bar on pages when dac header scrolls out of view 898 */ 899$(window).scroll(function(event) { 900 901 setStickyTop(); 902 var hiding = false; 903 var $stickyEl = $('#sticky-header'); 904 var $menuEl = $('.menu-container'); 905 // Exit if there's no sidenav 906 if ($('#side-nav').length == 0) return; 907 // Exit if the mouse target is a DIV, because that means the event is coming 908 // from a scrollable div and so there's no need to make adjustments to our layout 909 if ($(event.target).nodeName == "DIV") { 910 return; 911 } 912 913 var top = $(window).scrollTop(); 914 // we set the navbar fixed when the scroll position is beyond the height of the site header... 915 var shouldBeSticky = top >= stickyTop; 916 // ... except if the document content is shorter than the sidenav height. 917 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing) 918 if ($("#doc-col").height() < $("#side-nav").height()) { 919 shouldBeSticky = false; 920 } 921 922 // Don't continue if the header is sufficently far away 923 // (to avoid intensive resizing that slows scrolling) 924 if (sticky == shouldBeSticky) { 925 return; 926 } 927 // Account for horizontal scroll 928 var scrollLeft = $(window).scrollLeft(); 929 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match 930 if (navBarIsFixed && (scrollLeft != prevScrollLeft)) { 931 updateSideNavPosition(); 932 prevScrollLeft = scrollLeft; 933 } 934 935 // If sticky header visible and position is now near top, hide sticky 936 if (sticky && !shouldBeSticky) { 937 sticky = false; 938 hiding = true; 939 // make the sidenav static again 940 $('#devdoc-nav') 941 .removeClass('fixed') 942 .css({'width':'auto','margin':''}) 943 .prependTo('#side-nav'); 944 // delay hide the sticky 945 $menuEl.removeClass('sticky-menu'); 946 $stickyEl.fadeOut(250); 947 hiding = false; 948 949 // update the sidenaav position for side scrolling 950 updateSideNavPosition(); 951 } else if (!sticky && shouldBeSticky) { 952 sticky = true; 953 $stickyEl.fadeIn(10); 954 $menuEl.addClass('sticky-menu'); 955 956 // make the sidenav fixed 957 var width = $('#devdoc-nav').width(); 958 $('#devdoc-nav') 959 .addClass('fixed') 960 .css({'width':width+'px'}) 961 .prependTo('#body-content'); 962 963 // update the sidenaav position for side scrolling 964 updateSideNavPosition(); 965 966 } else if (hiding && top < 15) { 967 $menuEl.removeClass('sticky-menu'); 968 $stickyEl.hide(); 969 hiding = false; 970 } 971 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance 972}); 973 974/* 975 * Manages secion card states and nav resize to conclude loading 976 */ 977(function() { 978 $(document).ready(function() { 979 980 // Stack hover states 981 $('.section-card-menu').each(function(index, el) { 982 var height = $(el).height(); 983 $(el).css({height:height+'px', position:'relative'}); 984 var $cardInfo = $(el).find('.card-info'); 985 986 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'}); 987 }); 988 989 resizeNav(); // must resize once loading is finished 990 }); 991 992})(); 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007/* MISC LIBRARY FUNCTIONS */ 1008 1009 1010 1011 1012 1013function toggle(obj, slide) { 1014 var ul = $("ul:first", obj); 1015 var li = ul.parent(); 1016 if (li.hasClass("closed")) { 1017 if (slide) { 1018 ul.slideDown("fast"); 1019 } else { 1020 ul.show(); 1021 } 1022 li.removeClass("closed"); 1023 li.addClass("open"); 1024 $(".toggle-img", li).attr("title", "hide pages"); 1025 } else { 1026 ul.slideUp("fast"); 1027 li.removeClass("open"); 1028 li.addClass("closed"); 1029 $(".toggle-img", li).attr("title", "show pages"); 1030 } 1031} 1032 1033 1034function buildToggleLists() { 1035 $(".toggle-list").each( 1036 function(i) { 1037 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>"); 1038 $(this).addClass("closed"); 1039 }); 1040} 1041 1042 1043 1044function hideNestedItems(list, toggle) { 1045 $list = $(list); 1046 // hide nested lists 1047 if($list.hasClass('showing')) { 1048 $("li ol", $list).hide('fast'); 1049 $list.removeClass('showing'); 1050 // show nested lists 1051 } else { 1052 $("li ol", $list).show('fast'); 1053 $list.addClass('showing'); 1054 } 1055 $(".more,.less",$(toggle)).toggle(); 1056} 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085/* REFERENCE NAV SWAP */ 1086 1087 1088function getNavPref() { 1089 var v = readCookie('reference_nav'); 1090 if (v != NAV_PREF_TREE) { 1091 v = NAV_PREF_PANELS; 1092 } 1093 return v; 1094} 1095 1096function chooseDefaultNav() { 1097 nav_pref = getNavPref(); 1098 if (nav_pref == NAV_PREF_TREE) { 1099 $("#nav-panels").toggle(); 1100 $("#panel-link").toggle(); 1101 $("#nav-tree").toggle(); 1102 $("#tree-link").toggle(); 1103 } 1104} 1105 1106function swapNav() { 1107 if (nav_pref == NAV_PREF_TREE) { 1108 nav_pref = NAV_PREF_PANELS; 1109 } else { 1110 nav_pref = NAV_PREF_TREE; 1111 init_default_navtree(toRoot); 1112 } 1113 var date = new Date(); 1114 date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years 1115 writeCookie("nav", nav_pref, "reference", date.toGMTString()); 1116 1117 $("#nav-panels").toggle(); 1118 $("#panel-link").toggle(); 1119 $("#nav-tree").toggle(); 1120 $("#tree-link").toggle(); 1121 1122 resizeNav(); 1123 1124 // Gross nasty hack to make tree view show up upon first swap by setting height manually 1125 $("#nav-tree .jspContainer:visible") 1126 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'}); 1127 // Another nasty hack to make the scrollbar appear now that we have height 1128 resizeNav(); 1129 1130 if ($("#nav-tree").is(':visible')) { 1131 scrollIntoView("nav-tree"); 1132 } else { 1133 scrollIntoView("packages-nav"); 1134 scrollIntoView("classes-nav"); 1135 } 1136} 1137 1138 1139 1140/* ############################################ */ 1141/* ########## LOCALIZATION ############ */ 1142/* ############################################ */ 1143 1144function getBaseUri(uri) { 1145 var intlUrl = (uri.substring(0,6) == "/intl/"); 1146 if (intlUrl) { 1147 base = uri.substring(uri.indexOf('intl/')+5,uri.length); 1148 base = base.substring(base.indexOf('/')+1, base.length); 1149 //alert("intl, returning base url: /" + base); 1150 return ("/" + base); 1151 } else { 1152 //alert("not intl, returning uri as found."); 1153 return uri; 1154 } 1155} 1156 1157function requestAppendHL(uri) { 1158//append "?hl=<lang> to an outgoing request (such as to blog) 1159 var lang = getLangPref(); 1160 if (lang) { 1161 var q = 'hl=' + lang; 1162 uri += '?' + q; 1163 window.location = uri; 1164 return false; 1165 } else { 1166 return true; 1167 } 1168} 1169 1170 1171function changeNavLang(lang) { 1172 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]"); 1173 $links.each(function(i){ // for each link with a translation 1174 var $link = $(this); 1175 if (lang != "en") { // No need to worry about English, because a language change invokes new request 1176 // put the desired language from the attribute as the text 1177 $link.text($link.attr(lang+"-lang")) 1178 } 1179 }); 1180} 1181 1182function changeLangPref(lang, submit) { 1183 var date = new Date(); 1184 expires = date.toGMTString(date.setTime(date.getTime()+(10*365*24*60*60*1000))); 1185 // keep this for 50 years 1186 //alert("expires: " + expires) 1187 writeCookie("pref_lang", lang, null, expires); 1188 1189 // ####### TODO: Remove this condition once we're stable on devsite ####### 1190 // This condition is only needed if we still need to support legacy GAE server 1191 if (devsite) { 1192 // Switch language when on Devsite server 1193 if (submit) { 1194 $("#setlang").submit(); 1195 } 1196 } else { 1197 // Switch language when on legacy GAE server 1198 if (submit) { 1199 window.location = getBaseUri(location.pathname); 1200 } 1201 } 1202} 1203 1204function loadLangPref() { 1205 var lang = readCookie("pref_lang"); 1206 if (lang != 0) { 1207 $("#language").find("option[value='"+lang+"']").attr("selected",true); 1208 } 1209} 1210 1211function getLangPref() { 1212 var lang = $("#language").find(":selected").attr("value"); 1213 if (!lang) { 1214 lang = readCookie("pref_lang"); 1215 } 1216 return (lang != 0) ? lang : 'en'; 1217} 1218 1219/* ########## END LOCALIZATION ############ */ 1220 1221 1222 1223 1224 1225 1226/* Used to hide and reveal supplemental content, such as long code samples. 1227 See the companion CSS in android-developer-docs.css */ 1228function toggleContent(obj) { 1229 var div = $(obj).closest(".toggle-content"); 1230 var toggleMe = $(".toggle-content-toggleme:eq(0)",div); 1231 if (div.hasClass("closed")) { // if it's closed, open it 1232 toggleMe.slideDown(); 1233 $(".toggle-content-text:eq(0)", obj).toggle(); 1234 div.removeClass("closed").addClass("open"); 1235 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot 1236 + "assets/images/triangle-opened.png"); 1237 } else { // if it's open, close it 1238 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow 1239 $(".toggle-content-text:eq(0)", obj).toggle(); 1240 div.removeClass("open").addClass("closed"); 1241 div.find(".toggle-content").removeClass("open").addClass("closed") 1242 .find(".toggle-content-toggleme").hide(); 1243 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot 1244 + "assets/images/triangle-closed.png"); 1245 }); 1246 } 1247 return false; 1248} 1249 1250 1251/* New version of expandable content */ 1252function toggleExpandable(link,id) { 1253 if($(id).is(':visible')) { 1254 $(id).slideUp(); 1255 $(link).removeClass('expanded'); 1256 } else { 1257 $(id).slideDown(); 1258 $(link).addClass('expanded'); 1259 } 1260} 1261 1262function hideExpandable(ids) { 1263 $(ids).slideUp(); 1264 $(ids).prev('h4').find('a.expandable').removeClass('expanded'); 1265} 1266 1267 1268 1269 1270 1271/* 1272 * Slideshow 1.0 1273 * Used on /index.html and /develop/index.html for carousel 1274 * 1275 * Sample usage: 1276 * HTML - 1277 * <div class="slideshow-container"> 1278 * <a href="" class="slideshow-prev">Prev</a> 1279 * <a href="" class="slideshow-next">Next</a> 1280 * <ul> 1281 * <li class="item"><img src="images/marquee1.jpg"></li> 1282 * <li class="item"><img src="images/marquee2.jpg"></li> 1283 * <li class="item"><img src="images/marquee3.jpg"></li> 1284 * <li class="item"><img src="images/marquee4.jpg"></li> 1285 * </ul> 1286 * </div> 1287 * 1288 * <script type="text/javascript"> 1289 * $('.slideshow-container').dacSlideshow({ 1290 * auto: true, 1291 * btnPrev: '.slideshow-prev', 1292 * btnNext: '.slideshow-next' 1293 * }); 1294 * </script> 1295 * 1296 * Options: 1297 * btnPrev: optional identifier for previous button 1298 * btnNext: optional identifier for next button 1299 * btnPause: optional identifier for pause button 1300 * auto: whether or not to auto-proceed 1301 * speed: animation speed 1302 * autoTime: time between auto-rotation 1303 * easing: easing function for transition 1304 * start: item to select by default 1305 * scroll: direction to scroll in 1306 * pagination: whether or not to include dotted pagination 1307 * 1308 */ 1309 1310 (function($) { 1311 $.fn.dacSlideshow = function(o) { 1312 1313 //Options - see above 1314 o = $.extend({ 1315 btnPrev: null, 1316 btnNext: null, 1317 btnPause: null, 1318 auto: true, 1319 speed: 500, 1320 autoTime: 12000, 1321 easing: null, 1322 start: 0, 1323 scroll: 1, 1324 pagination: true 1325 1326 }, o || {}); 1327 1328 //Set up a carousel for each 1329 return this.each(function() { 1330 1331 var running = false; 1332 var animCss = o.vertical ? "top" : "left"; 1333 var sizeCss = o.vertical ? "height" : "width"; 1334 var div = $(this); 1335 var ul = $("ul", div); 1336 var tLi = $("li", ul); 1337 var tl = tLi.size(); 1338 var timer = null; 1339 1340 var li = $("li", ul); 1341 var itemLength = li.size(); 1342 var curr = o.start; 1343 1344 li.css({float: o.vertical ? "none" : "left"}); 1345 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"}); 1346 div.css({position: "relative", "z-index": "2", left: "0px"}); 1347 1348 var liSize = o.vertical ? height(li) : width(li); 1349 var ulSize = liSize * itemLength; 1350 var divSize = liSize; 1351 1352 li.css({width: li.width(), height: li.height()}); 1353 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize)); 1354 1355 div.css(sizeCss, divSize+"px"); 1356 1357 //Pagination 1358 if (o.pagination) { 1359 var pagination = $("<div class='pagination'></div>"); 1360 var pag_ul = $("<ul></ul>"); 1361 if (tl > 1) { 1362 for (var i=0;i<tl;i++) { 1363 var li = $("<li>"+i+"</li>"); 1364 pag_ul.append(li); 1365 if (i==o.start) li.addClass('active'); 1366 li.click(function() { 1367 go(parseInt($(this).text())); 1368 }) 1369 } 1370 pagination.append(pag_ul); 1371 div.append(pagination); 1372 } 1373 } 1374 1375 //Previous button 1376 if(o.btnPrev) 1377 $(o.btnPrev).click(function(e) { 1378 e.preventDefault(); 1379 return go(curr-o.scroll); 1380 }); 1381 1382 //Next button 1383 if(o.btnNext) 1384 $(o.btnNext).click(function(e) { 1385 e.preventDefault(); 1386 return go(curr+o.scroll); 1387 }); 1388 1389 //Pause button 1390 if(o.btnPause) 1391 $(o.btnPause).click(function(e) { 1392 e.preventDefault(); 1393 if ($(this).hasClass('paused')) { 1394 startRotateTimer(); 1395 } else { 1396 pauseRotateTimer(); 1397 } 1398 }); 1399 1400 //Auto rotation 1401 if(o.auto) startRotateTimer(); 1402 1403 function startRotateTimer() { 1404 clearInterval(timer); 1405 timer = setInterval(function() { 1406 if (curr == tl-1) { 1407 go(0); 1408 } else { 1409 go(curr+o.scroll); 1410 } 1411 }, o.autoTime); 1412 $(o.btnPause).removeClass('paused'); 1413 } 1414 1415 function pauseRotateTimer() { 1416 clearInterval(timer); 1417 $(o.btnPause).addClass('paused'); 1418 } 1419 1420 //Go to an item 1421 function go(to) { 1422 if(!running) { 1423 1424 if(to<0) { 1425 to = itemLength-1; 1426 } else if (to>itemLength-1) { 1427 to = 0; 1428 } 1429 curr = to; 1430 1431 running = true; 1432 1433 ul.animate( 1434 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing, 1435 function() { 1436 running = false; 1437 } 1438 ); 1439 1440 $(o.btnPrev + "," + o.btnNext).removeClass("disabled"); 1441 $( (curr-o.scroll<0 && o.btnPrev) 1442 || 1443 (curr+o.scroll > itemLength && o.btnNext) 1444 || 1445 [] 1446 ).addClass("disabled"); 1447 1448 1449 var nav_items = $('li', pagination); 1450 nav_items.removeClass('active'); 1451 nav_items.eq(to).addClass('active'); 1452 1453 1454 } 1455 if(o.auto) startRotateTimer(); 1456 return false; 1457 }; 1458 }); 1459 }; 1460 1461 function css(el, prop) { 1462 return parseInt($.css(el[0], prop)) || 0; 1463 }; 1464 function width(el) { 1465 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight'); 1466 }; 1467 function height(el) { 1468 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom'); 1469 }; 1470 1471 })(jQuery); 1472 1473 1474/* 1475 * dacSlideshow 1.0 1476 * Used on develop/index.html for side-sliding tabs 1477 * 1478 * Sample usage: 1479 * HTML - 1480 * <div class="slideshow-container"> 1481 * <a href="" class="slideshow-prev">Prev</a> 1482 * <a href="" class="slideshow-next">Next</a> 1483 * <ul> 1484 * <li class="item"><img src="images/marquee1.jpg"></li> 1485 * <li class="item"><img src="images/marquee2.jpg"></li> 1486 * <li class="item"><img src="images/marquee3.jpg"></li> 1487 * <li class="item"><img src="images/marquee4.jpg"></li> 1488 * </ul> 1489 * </div> 1490 * 1491 * <script type="text/javascript"> 1492 * $('.slideshow-container').dacSlideshow({ 1493 * auto: true, 1494 * btnPrev: '.slideshow-prev', 1495 * btnNext: '.slideshow-next' 1496 * }); 1497 * </script> 1498 * 1499 * Options: 1500 * btnPrev: optional identifier for previous button 1501 * btnNext: optional identifier for next button 1502 * auto: whether or not to auto-proceed 1503 * speed: animation speed 1504 * autoTime: time between auto-rotation 1505 * easing: easing function for transition 1506 * start: item to select by default 1507 * scroll: direction to scroll in 1508 * pagination: whether or not to include dotted pagination 1509 * 1510 */ 1511 (function($) { 1512 $.fn.dacTabbedList = function(o) { 1513 1514 //Options - see above 1515 o = $.extend({ 1516 speed : 250, 1517 easing: null, 1518 nav_id: null, 1519 frame_id: null 1520 }, o || {}); 1521 1522 //Set up a carousel for each 1523 return this.each(function() { 1524 1525 var curr = 0; 1526 var running = false; 1527 var animCss = "margin-left"; 1528 var sizeCss = "width"; 1529 var div = $(this); 1530 1531 var nav = $(o.nav_id, div); 1532 var nav_li = $("li", nav); 1533 var nav_size = nav_li.size(); 1534 var frame = div.find(o.frame_id); 1535 var content_width = $(frame).find('ul').width(); 1536 //Buttons 1537 $(nav_li).click(function(e) { 1538 go($(nav_li).index($(this))); 1539 }) 1540 1541 //Go to an item 1542 function go(to) { 1543 if(!running) { 1544 curr = to; 1545 running = true; 1546 1547 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing, 1548 function() { 1549 running = false; 1550 } 1551 ); 1552 1553 1554 nav_li.removeClass('active'); 1555 nav_li.eq(to).addClass('active'); 1556 1557 1558 } 1559 return false; 1560 }; 1561 }); 1562 }; 1563 1564 function css(el, prop) { 1565 return parseInt($.css(el[0], prop)) || 0; 1566 }; 1567 function width(el) { 1568 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight'); 1569 }; 1570 function height(el) { 1571 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom'); 1572 }; 1573 1574 })(jQuery); 1575 1576 1577 1578 1579 1580/* ######################################################## */ 1581/* ################ SEARCH SUGGESTIONS ################## */ 1582/* ######################################################## */ 1583 1584 1585 1586var gSelectedIndex = -1; // the index position of currently highlighted suggestion 1587var gSelectedColumn = -1; // which column of suggestion lists is currently focused 1588 1589var gMatches = new Array(); 1590var gLastText = ""; 1591var gInitialized = false; 1592var ROW_COUNT_FRAMEWORK = 20; // max number of results in list 1593var gListLength = 0; 1594 1595 1596var gGoogleMatches = new Array(); 1597var ROW_COUNT_GOOGLE = 15; // max number of results in list 1598var gGoogleListLength = 0; 1599 1600var gDocsMatches = new Array(); 1601var ROW_COUNT_DOCS = 100; // max number of results in list 1602var gDocsListLength = 0; 1603 1604function onSuggestionClick(link) { 1605 // When user clicks a suggested document, track it 1606 _gaq.push(['_trackEvent', 'Suggestion Click', 'clicked: ' + $(link).text(), 1607 'from: ' + $("#search_autocomplete").val()]); 1608} 1609 1610function set_item_selected($li, selected) 1611{ 1612 if (selected) { 1613 $li.attr('class','jd-autocomplete jd-selected'); 1614 } else { 1615 $li.attr('class','jd-autocomplete'); 1616 } 1617} 1618 1619function set_item_values(toroot, $li, match) 1620{ 1621 var $link = $('a',$li); 1622 $link.html(match.__hilabel || match.label); 1623 $link.attr('href',toroot + match.link); 1624} 1625 1626function set_item_values_jd(toroot, $li, match) 1627{ 1628 var $link = $('a',$li); 1629 $link.html(match.title); 1630 $link.attr('href',toroot + match.url); 1631} 1632 1633function new_suggestion($list) { 1634 var $li = $("<li class='jd-autocomplete'></li>"); 1635 $list.append($li); 1636 1637 $li.mousedown(function() { 1638 window.location = this.firstChild.getAttribute("href"); 1639 }); 1640 $li.mouseover(function() { 1641 $('.search_filtered_wrapper li').removeClass('jd-selected'); 1642 $(this).addClass('jd-selected'); 1643 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered')); 1644 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this); 1645 }); 1646 $li.append("<a onclick='onSuggestionClick(this)'></a>"); 1647 $li.attr('class','show-item'); 1648 return $li; 1649} 1650 1651function sync_selection_table(toroot) 1652{ 1653 var $li; //list item jquery object 1654 var i; //list item iterator 1655 1656 // if there are NO results at all, hide all columns 1657 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) { 1658 $('.suggest-card').hide(300); 1659 return; 1660 } 1661 1662 // if there are api results 1663 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) { 1664 // reveal suggestion list 1665 $('.suggest-card.dummy').show(); 1666 $('.suggest-card.reference').show(); 1667 var listIndex = 0; // list index position 1668 1669 // reset the lists 1670 $(".search_filtered_wrapper.reference li").remove(); 1671 1672 // ########### ANDROID RESULTS ############# 1673 if (gMatches.length > 0) { 1674 1675 // determine android results to show 1676 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ? 1677 gMatches.length : ROW_COUNT_FRAMEWORK; 1678 for (i=0; i<gListLength; i++) { 1679 var $li = new_suggestion($(".suggest-card.reference ul")); 1680 set_item_values(toroot, $li, gMatches[i]); 1681 set_item_selected($li, i == gSelectedIndex); 1682 } 1683 } 1684 1685 // ########### GOOGLE RESULTS ############# 1686 if (gGoogleMatches.length > 0) { 1687 // show header for list 1688 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>"); 1689 1690 // determine google results to show 1691 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE; 1692 for (i=0; i<gGoogleListLength; i++) { 1693 var $li = new_suggestion($(".suggest-card.reference ul")); 1694 set_item_values(toroot, $li, gGoogleMatches[i]); 1695 set_item_selected($li, i == gSelectedIndex); 1696 } 1697 } 1698 } else { 1699 $('.suggest-card.reference').hide(); 1700 $('.suggest-card.dummy').hide(); 1701 } 1702 1703 // ########### JD DOC RESULTS ############# 1704 if (gDocsMatches.length > 0) { 1705 // reset the lists 1706 $(".search_filtered_wrapper.docs li").remove(); 1707 1708 // determine google results to show 1709 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC: 1710 // The order must match the reverse order that each section appears as a card in 1711 // the suggestion UI... this may be only for the "develop" grouped items though. 1712 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS; 1713 for (i=0; i<gDocsListLength; i++) { 1714 var sugg = gDocsMatches[i]; 1715 var $li; 1716 if (sugg.type == "design") { 1717 $li = new_suggestion($(".suggest-card.design ul")); 1718 } else 1719 if (sugg.type == "distribute") { 1720 $li = new_suggestion($(".suggest-card.distribute ul")); 1721 } else 1722 if (sugg.type == "samples") { 1723 $li = new_suggestion($(".suggest-card.develop .child-card.samples")); 1724 } else 1725 if (sugg.type == "training") { 1726 $li = new_suggestion($(".suggest-card.develop .child-card.training")); 1727 } else 1728 if (sugg.type == "about"||"guide"||"tools"||"google") { 1729 $li = new_suggestion($(".suggest-card.develop .child-card.guides")); 1730 } else { 1731 continue; 1732 } 1733 1734 set_item_values_jd(toroot, $li, sugg); 1735 set_item_selected($li, i == gSelectedIndex); 1736 } 1737 1738 // add heading and show or hide card 1739 if ($(".suggest-card.design li").length > 0) { 1740 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>"); 1741 $(".suggest-card.design").show(300); 1742 } else { 1743 $('.suggest-card.design').hide(300); 1744 } 1745 if ($(".suggest-card.distribute li").length > 0) { 1746 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>"); 1747 $(".suggest-card.distribute").show(300); 1748 } else { 1749 $('.suggest-card.distribute').hide(300); 1750 } 1751 if ($(".child-card.guides li").length > 0) { 1752 $(".child-card.guides").prepend("<li class='header'>Guides:</li>"); 1753 $(".child-card.guides li").appendTo(".suggest-card.develop ul"); 1754 } 1755 if ($(".child-card.training li").length > 0) { 1756 $(".child-card.training").prepend("<li class='header'>Training:</li>"); 1757 $(".child-card.training li").appendTo(".suggest-card.develop ul"); 1758 } 1759 if ($(".child-card.samples li").length > 0) { 1760 $(".child-card.samples").prepend("<li class='header'>Samples:</li>"); 1761 $(".child-card.samples li").appendTo(".suggest-card.develop ul"); 1762 } 1763 1764 if ($(".suggest-card.develop li").length > 0) { 1765 $(".suggest-card.develop").show(300); 1766 } else { 1767 $('.suggest-card.develop').hide(300); 1768 } 1769 1770 } else { 1771 $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300); 1772 } 1773} 1774 1775/** Called by the search input's onkeydown and onkeyup events. 1776 * Handles navigation with keyboard arrows, Enter key to invoke search, 1777 * otherwise invokes search suggestions on key-up event. 1778 * @param e The JS event 1779 * @param kd True if the event is key-down 1780 * @param toroot A string for the site's root path 1781 * @returns True if the event should bubble up 1782 */ 1783function search_changed(e, kd, toroot) 1784{ 1785 var currentLang = getLangPref(); 1786 var search = document.getElementById("search_autocomplete"); 1787 var text = search.value.replace(/(^ +)|( +$)/g, ''); 1788 // get the ul hosting the currently selected item 1789 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0; 1790 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible"); 1791 var $selectedUl = $columns[gSelectedColumn]; 1792 1793 // show/hide the close button 1794 if (text != '') { 1795 $(".search .close").removeClass("hide"); 1796 } else { 1797 $(".search .close").addClass("hide"); 1798 } 1799 // 27 = esc 1800 if (e.keyCode == 27) { 1801 // close all search results 1802 if (kd) $('.search .close').trigger('click'); 1803 return true; 1804 } 1805 // 13 = enter 1806 else if (e.keyCode == 13) { 1807 if (gSelectedIndex < 0) { 1808 $('.suggest-card').hide(); 1809 if ($("#searchResults").is(":hidden") && (search.value != "")) { 1810 // if results aren't showing (and text not empty), return true to allow search to execute 1811 $('body,html').animate({scrollTop:0}, '500', 'swing'); 1812 return true; 1813 } else { 1814 // otherwise, results are already showing, so allow ajax to auto refresh the results 1815 // and ignore this Enter press to avoid the reload. 1816 return false; 1817 } 1818 } else if (kd && gSelectedIndex >= 0) { 1819 // click the link corresponding to selected item 1820 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click(); 1821 return false; 1822 } 1823 } 1824 // Stop here if Google results are showing 1825 else if ($("#searchResults").is(":visible")) { 1826 //If search_results is scrolled out of view, scroll to top on input 1827 if ((sticky ) && (search.value != "")) { 1828 $('body,html').animate({scrollTop:0}, '500', 'swing'); 1829 } 1830 // if results aren't showing (and text not empty), return true to allow search to execute 1831 return true; 1832 } 1833 // 38 UP ARROW 1834 else if (kd && (e.keyCode == 38)) { 1835 // if the next item is a header, skip it 1836 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) { 1837 gSelectedIndex--; 1838 } 1839 if (gSelectedIndex >= 0) { 1840 $('li', $selectedUl).removeClass('jd-selected'); 1841 gSelectedIndex--; 1842 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected'); 1843 // If user reaches top, reset selected column 1844 if (gSelectedIndex < 0) { 1845 gSelectedColumn = -1; 1846 } 1847 } 1848 return false; 1849 } 1850 // 40 DOWN ARROW 1851 else if (kd && (e.keyCode == 40)) { 1852 // if the next item is a header, skip it 1853 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) { 1854 gSelectedIndex++; 1855 } 1856 if ((gSelectedIndex < $("li", $selectedUl).length-1) || 1857 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) { 1858 $('li', $selectedUl).removeClass('jd-selected'); 1859 gSelectedIndex++; 1860 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected'); 1861 } 1862 return false; 1863 } 1864 // Consider left/right arrow navigation 1865 // NOTE: Order of suggest columns are reverse order (index position 0 is on right) 1866 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) { 1867 // 37 LEFT ARROW 1868 // go left only if current column is not left-most column (last column) 1869 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) { 1870 $('li', $selectedUl).removeClass('jd-selected'); 1871 gSelectedColumn++; 1872 $selectedUl = $columns[gSelectedColumn]; 1873 // keep or reset the selected item to last item as appropriate 1874 gSelectedIndex = gSelectedIndex > 1875 $("li", $selectedUl).length-1 ? 1876 $("li", $selectedUl).length-1 : gSelectedIndex; 1877 // if the corresponding item is a header, move down 1878 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) { 1879 gSelectedIndex++; 1880 } 1881 // set item selected 1882 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected'); 1883 return false; 1884 } 1885 // 39 RIGHT ARROW 1886 // go right only if current column is not the right-most column (first column) 1887 else if (e.keyCode == 39 && gSelectedColumn > 0) { 1888 $('li', $selectedUl).removeClass('jd-selected'); 1889 gSelectedColumn--; 1890 $selectedUl = $columns[gSelectedColumn]; 1891 // keep or reset the selected item to last item as appropriate 1892 gSelectedIndex = gSelectedIndex > 1893 $("li", $selectedUl).length-1 ? 1894 $("li", $selectedUl).length-1 : gSelectedIndex; 1895 // if the corresponding item is a header, move down 1896 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) { 1897 gSelectedIndex++; 1898 } 1899 // set item selected 1900 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected'); 1901 return false; 1902 } 1903 } 1904 1905 // if key-up event and not arrow down/up/left/right, 1906 // read the search query and add suggestions to gMatches 1907 else if (!kd && (e.keyCode != 40) 1908 && (e.keyCode != 38) 1909 && (e.keyCode != 37) 1910 && (e.keyCode != 39)) { 1911 gSelectedIndex = -1; 1912 gMatches = new Array(); 1913 matchedCount = 0; 1914 gGoogleMatches = new Array(); 1915 matchedCountGoogle = 0; 1916 gDocsMatches = new Array(); 1917 matchedCountDocs = 0; 1918 1919 // Search for Android matches 1920 for (var i=0; i<DATA.length; i++) { 1921 var s = DATA[i]; 1922 if (text.length != 0 && 1923 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) { 1924 gMatches[matchedCount] = s; 1925 matchedCount++; 1926 } 1927 } 1928 rank_autocomplete_api_results(text, gMatches); 1929 for (var i=0; i<gMatches.length; i++) { 1930 var s = gMatches[i]; 1931 } 1932 1933 1934 // Search for Google matches 1935 for (var i=0; i<GOOGLE_DATA.length; i++) { 1936 var s = GOOGLE_DATA[i]; 1937 if (text.length != 0 && 1938 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) { 1939 gGoogleMatches[matchedCountGoogle] = s; 1940 matchedCountGoogle++; 1941 } 1942 } 1943 rank_autocomplete_api_results(text, gGoogleMatches); 1944 for (var i=0; i<gGoogleMatches.length; i++) { 1945 var s = gGoogleMatches[i]; 1946 } 1947 1948 highlight_autocomplete_result_labels(text); 1949 1950 1951 1952 // Search for matching JD docs 1953 if (text.length >= 3) { 1954 // Regex to match only the beginning of a word 1955 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g"); 1956 1957 1958 // Search for Training classes 1959 for (var i=0; i<TRAINING_RESOURCES.length; i++) { 1960 // current search comparison, with counters for tag and title, 1961 // used later to improve ranking 1962 var s = TRAINING_RESOURCES[i]; 1963 s.matched_tag = 0; 1964 s.matched_title = 0; 1965 var matched = false; 1966 1967 // Check if query matches any tags; work backwards toward 1 to assist ranking 1968 for (var j = s.keywords.length - 1; j >= 0; j--) { 1969 // it matches a tag 1970 if (s.keywords[j].toLowerCase().match(textRegex)) { 1971 matched = true; 1972 s.matched_tag = j + 1; // add 1 to index position 1973 } 1974 } 1975 // Don't consider doc title for lessons (only for class landing pages), 1976 // unless the lesson has a tag that already matches 1977 if ((s.lang == currentLang) && 1978 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) { 1979 // it matches the doc title 1980 if (s.title.toLowerCase().match(textRegex)) { 1981 matched = true; 1982 s.matched_title = 1; 1983 } 1984 } 1985 if (matched) { 1986 gDocsMatches[matchedCountDocs] = s; 1987 matchedCountDocs++; 1988 } 1989 } 1990 1991 1992 // Search for API Guides 1993 for (var i=0; i<GUIDE_RESOURCES.length; i++) { 1994 // current search comparison, with counters for tag and title, 1995 // used later to improve ranking 1996 var s = GUIDE_RESOURCES[i]; 1997 s.matched_tag = 0; 1998 s.matched_title = 0; 1999 var matched = false; 2000 2001 // Check if query matches any tags; work backwards toward 1 to assist ranking 2002 for (var j = s.keywords.length - 1; j >= 0; j--) { 2003 // it matches a tag 2004 if (s.keywords[j].toLowerCase().match(textRegex)) { 2005 matched = true; 2006 s.matched_tag = j + 1; // add 1 to index position 2007 } 2008 } 2009 // Check if query matches the doc title, but only for current language 2010 if (s.lang == currentLang) { 2011 // if query matches the doc title 2012 if (s.title.toLowerCase().match(textRegex)) { 2013 matched = true; 2014 s.matched_title = 1; 2015 } 2016 } 2017 if (matched) { 2018 gDocsMatches[matchedCountDocs] = s; 2019 matchedCountDocs++; 2020 } 2021 } 2022 2023 2024 // Search for Tools Guides 2025 for (var i=0; i<TOOLS_RESOURCES.length; i++) { 2026 // current search comparison, with counters for tag and title, 2027 // used later to improve ranking 2028 var s = TOOLS_RESOURCES[i]; 2029 s.matched_tag = 0; 2030 s.matched_title = 0; 2031 var matched = false; 2032 2033 // Check if query matches any tags; work backwards toward 1 to assist ranking 2034 for (var j = s.keywords.length - 1; j >= 0; j--) { 2035 // it matches a tag 2036 if (s.keywords[j].toLowerCase().match(textRegex)) { 2037 matched = true; 2038 s.matched_tag = j + 1; // add 1 to index position 2039 } 2040 } 2041 // Check if query matches the doc title, but only for current language 2042 if (s.lang == currentLang) { 2043 // if query matches the doc title 2044 if (s.title.toLowerCase().match(textRegex)) { 2045 matched = true; 2046 s.matched_title = 1; 2047 } 2048 } 2049 if (matched) { 2050 gDocsMatches[matchedCountDocs] = s; 2051 matchedCountDocs++; 2052 } 2053 } 2054 2055 2056 // Search for About docs 2057 for (var i=0; i<ABOUT_RESOURCES.length; i++) { 2058 // current search comparison, with counters for tag and title, 2059 // used later to improve ranking 2060 var s = ABOUT_RESOURCES[i]; 2061 s.matched_tag = 0; 2062 s.matched_title = 0; 2063 var matched = false; 2064 2065 // Check if query matches any tags; work backwards toward 1 to assist ranking 2066 for (var j = s.keywords.length - 1; j >= 0; j--) { 2067 // it matches a tag 2068 if (s.keywords[j].toLowerCase().match(textRegex)) { 2069 matched = true; 2070 s.matched_tag = j + 1; // add 1 to index position 2071 } 2072 } 2073 // Check if query matches the doc title, but only for current language 2074 if (s.lang == currentLang) { 2075 // if query matches the doc title 2076 if (s.title.toLowerCase().match(textRegex)) { 2077 matched = true; 2078 s.matched_title = 1; 2079 } 2080 } 2081 if (matched) { 2082 gDocsMatches[matchedCountDocs] = s; 2083 matchedCountDocs++; 2084 } 2085 } 2086 2087 2088 // Search for Design guides 2089 for (var i=0; i<DESIGN_RESOURCES.length; i++) { 2090 // current search comparison, with counters for tag and title, 2091 // used later to improve ranking 2092 var s = DESIGN_RESOURCES[i]; 2093 s.matched_tag = 0; 2094 s.matched_title = 0; 2095 var matched = false; 2096 2097 // Check if query matches any tags; work backwards toward 1 to assist ranking 2098 for (var j = s.keywords.length - 1; j >= 0; j--) { 2099 // it matches a tag 2100 if (s.keywords[j].toLowerCase().match(textRegex)) { 2101 matched = true; 2102 s.matched_tag = j + 1; // add 1 to index position 2103 } 2104 } 2105 // Check if query matches the doc title, but only for current language 2106 if (s.lang == currentLang) { 2107 // if query matches the doc title 2108 if (s.title.toLowerCase().match(textRegex)) { 2109 matched = true; 2110 s.matched_title = 1; 2111 } 2112 } 2113 if (matched) { 2114 gDocsMatches[matchedCountDocs] = s; 2115 matchedCountDocs++; 2116 } 2117 } 2118 2119 2120 // Search for Distribute guides 2121 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) { 2122 // current search comparison, with counters for tag and title, 2123 // used later to improve ranking 2124 var s = DISTRIBUTE_RESOURCES[i]; 2125 s.matched_tag = 0; 2126 s.matched_title = 0; 2127 var matched = false; 2128 2129 // Check if query matches any tags; work backwards toward 1 to assist ranking 2130 for (var j = s.keywords.length - 1; j >= 0; j--) { 2131 // it matches a tag 2132 if (s.keywords[j].toLowerCase().match(textRegex)) { 2133 matched = true; 2134 s.matched_tag = j + 1; // add 1 to index position 2135 } 2136 } 2137 // Check if query matches the doc title, but only for current language 2138 if (s.lang == currentLang) { 2139 // if query matches the doc title 2140 if (s.title.toLowerCase().match(textRegex)) { 2141 matched = true; 2142 s.matched_title = 1; 2143 } 2144 } 2145 if (matched) { 2146 gDocsMatches[matchedCountDocs] = s; 2147 matchedCountDocs++; 2148 } 2149 } 2150 2151 2152 // Search for Google guides 2153 for (var i=0; i<GOOGLE_RESOURCES.length; i++) { 2154 // current search comparison, with counters for tag and title, 2155 // used later to improve ranking 2156 var s = GOOGLE_RESOURCES[i]; 2157 s.matched_tag = 0; 2158 s.matched_title = 0; 2159 var matched = false; 2160 2161 // Check if query matches any tags; work backwards toward 1 to assist ranking 2162 for (var j = s.keywords.length - 1; j >= 0; j--) { 2163 // it matches a tag 2164 if (s.keywords[j].toLowerCase().match(textRegex)) { 2165 matched = true; 2166 s.matched_tag = j + 1; // add 1 to index position 2167 } 2168 } 2169 // Check if query matches the doc title, but only for current language 2170 if (s.lang == currentLang) { 2171 // if query matches the doc title 2172 if (s.title.toLowerCase().match(textRegex)) { 2173 matched = true; 2174 s.matched_title = 1; 2175 } 2176 } 2177 if (matched) { 2178 gDocsMatches[matchedCountDocs] = s; 2179 matchedCountDocs++; 2180 } 2181 } 2182 2183 2184 // Search for Samples 2185 for (var i=0; i<SAMPLES_RESOURCES.length; i++) { 2186 // current search comparison, with counters for tag and title, 2187 // used later to improve ranking 2188 var s = SAMPLES_RESOURCES[i]; 2189 s.matched_tag = 0; 2190 s.matched_title = 0; 2191 var matched = false; 2192 // Check if query matches any tags; work backwards toward 1 to assist ranking 2193 for (var j = s.keywords.length - 1; j >= 0; j--) { 2194 // it matches a tag 2195 if (s.keywords[j].toLowerCase().match(textRegex)) { 2196 matched = true; 2197 s.matched_tag = j + 1; // add 1 to index position 2198 } 2199 } 2200 // Check if query matches the doc title, but only for current language 2201 if (s.lang == currentLang) { 2202 // if query matches the doc title.t 2203 if (s.title.toLowerCase().match(textRegex)) { 2204 matched = true; 2205 s.matched_title = 1; 2206 } 2207 } 2208 if (matched) { 2209 gDocsMatches[matchedCountDocs] = s; 2210 matchedCountDocs++; 2211 } 2212 } 2213 2214 // Rank/sort all the matched pages 2215 rank_autocomplete_doc_results(text, gDocsMatches); 2216 } 2217 2218 // draw the suggestions 2219 sync_selection_table(toroot); 2220 return true; // allow the event to bubble up to the search api 2221 } 2222} 2223 2224/* Order the jd doc result list based on match quality */ 2225function rank_autocomplete_doc_results(query, matches) { 2226 query = query || ''; 2227 if (!matches || !matches.length) 2228 return; 2229 2230 var _resultScoreFn = function(match) { 2231 var score = 1.0; 2232 2233 // if the query matched a tag 2234 if (match.matched_tag > 0) { 2235 // multiply score by factor relative to position in tags list (max of 3) 2236 score *= 3 / match.matched_tag; 2237 2238 // if it also matched the title 2239 if (match.matched_title > 0) { 2240 score *= 2; 2241 } 2242 } else if (match.matched_title > 0) { 2243 score *= 3; 2244 } 2245 2246 return score; 2247 }; 2248 2249 for (var i=0; i<matches.length; i++) { 2250 matches[i].__resultScore = _resultScoreFn(matches[i]); 2251 } 2252 2253 matches.sort(function(a,b){ 2254 var n = b.__resultScore - a.__resultScore; 2255 if (n == 0) // lexicographical sort if scores are the same 2256 n = (a.label < b.label) ? -1 : 1; 2257 return n; 2258 }); 2259} 2260 2261/* Order the result list based on match quality */ 2262function rank_autocomplete_api_results(query, matches) { 2263 query = query || ''; 2264 if (!matches || !matches.length) 2265 return; 2266 2267 // helper function that gets the last occurence index of the given regex 2268 // in the given string, or -1 if not found 2269 var _lastSearch = function(s, re) { 2270 if (s == '') 2271 return -1; 2272 var l = -1; 2273 var tmp; 2274 while ((tmp = s.search(re)) >= 0) { 2275 if (l < 0) l = 0; 2276 l += tmp; 2277 s = s.substr(tmp + 1); 2278 } 2279 return l; 2280 }; 2281 2282 // helper function that counts the occurrences of a given character in 2283 // a given string 2284 var _countChar = function(s, c) { 2285 var n = 0; 2286 for (var i=0; i<s.length; i++) 2287 if (s.charAt(i) == c) ++n; 2288 return n; 2289 }; 2290 2291 var queryLower = query.toLowerCase(); 2292 var queryAlnum = (queryLower.match(/\w+/) || [''])[0]; 2293 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum); 2294 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b'); 2295 2296 var _resultScoreFn = function(result) { 2297 // scores are calculated based on exact and prefix matches, 2298 // and then number of path separators (dots) from the last 2299 // match (i.e. favoring classes and deep package names) 2300 var score = 1.0; 2301 var labelLower = result.label.toLowerCase(); 2302 var t; 2303 t = _lastSearch(labelLower, partExactAlnumRE); 2304 if (t >= 0) { 2305 // exact part match 2306 var partsAfter = _countChar(labelLower.substr(t + 1), '.'); 2307 score *= 200 / (partsAfter + 1); 2308 } else { 2309 t = _lastSearch(labelLower, partPrefixAlnumRE); 2310 if (t >= 0) { 2311 // part prefix match 2312 var partsAfter = _countChar(labelLower.substr(t + 1), '.'); 2313 score *= 20 / (partsAfter + 1); 2314 } 2315 } 2316 2317 return score; 2318 }; 2319 2320 for (var i=0; i<matches.length; i++) { 2321 // if the API is deprecated, default score is 0; otherwise, perform scoring 2322 if (matches[i].deprecated == "true") { 2323 matches[i].__resultScore = 0; 2324 } else { 2325 matches[i].__resultScore = _resultScoreFn(matches[i]); 2326 } 2327 } 2328 2329 matches.sort(function(a,b){ 2330 var n = b.__resultScore - a.__resultScore; 2331 if (n == 0) // lexicographical sort if scores are the same 2332 n = (a.label < b.label) ? -1 : 1; 2333 return n; 2334 }); 2335} 2336 2337/* Add emphasis to part of string that matches query */ 2338function highlight_autocomplete_result_labels(query) { 2339 query = query || ''; 2340 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length)) 2341 return; 2342 2343 var queryLower = query.toLowerCase(); 2344 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0]; 2345 var queryRE = new RegExp( 2346 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig'); 2347 for (var i=0; i<gMatches.length; i++) { 2348 gMatches[i].__hilabel = gMatches[i].label.replace( 2349 queryRE, '<b>$1</b>'); 2350 } 2351 for (var i=0; i<gGoogleMatches.length; i++) { 2352 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace( 2353 queryRE, '<b>$1</b>'); 2354 } 2355} 2356 2357function search_focus_changed(obj, focused) 2358{ 2359 if (!focused) { 2360 if(obj.value == ""){ 2361 $(".search .close").addClass("hide"); 2362 } 2363 $(".suggest-card").hide(); 2364 } 2365} 2366 2367function submit_search() { 2368 var query = document.getElementById('search_autocomplete').value; 2369 location.hash = 'q=' + query; 2370 loadSearchResults(); 2371 $("#searchResults").slideDown('slow', setStickyTop); 2372 return false; 2373} 2374 2375 2376function hideResults() { 2377 $("#searchResults").slideUp('fast', setStickyTop); 2378 $(".search .close").addClass("hide"); 2379 location.hash = ''; 2380 2381 $("#search_autocomplete").val("").blur(); 2382 2383 // reset the ajax search callback to nothing, so results don't appear unless ENTER 2384 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {}); 2385 2386 // forcefully regain key-up event control (previously jacked by search api) 2387 $("#search_autocomplete").keyup(function(event) { 2388 return search_changed(event, false, toRoot); 2389 }); 2390 2391 return false; 2392} 2393 2394 2395 2396/* ########################################################## */ 2397/* ################ CUSTOM SEARCH ENGINE ################## */ 2398/* ########################################################## */ 2399 2400var searchControl; 2401google.load('search', '1', {"callback" : function() { 2402 searchControl = new google.search.SearchControl(); 2403 } }); 2404 2405function loadSearchResults() { 2406 document.getElementById("search_autocomplete").style.color = "#000"; 2407 2408 searchControl = new google.search.SearchControl(); 2409 2410 // use our existing search form and use tabs when multiple searchers are used 2411 drawOptions = new google.search.DrawOptions(); 2412 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED); 2413 drawOptions.setInput(document.getElementById("search_autocomplete")); 2414 2415 // configure search result options 2416 searchOptions = new google.search.SearcherOptions(); 2417 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN); 2418 2419 // configure each of the searchers, for each tab 2420 devSiteSearcher = new google.search.WebSearch(); 2421 devSiteSearcher.setUserDefinedLabel("All"); 2422 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u"); 2423 2424 designSearcher = new google.search.WebSearch(); 2425 designSearcher.setUserDefinedLabel("Design"); 2426 designSearcher.setSiteRestriction("http://developer.android.com/design/"); 2427 2428 trainingSearcher = new google.search.WebSearch(); 2429 trainingSearcher.setUserDefinedLabel("Training"); 2430 trainingSearcher.setSiteRestriction("http://developer.android.com/training/"); 2431 2432 guidesSearcher = new google.search.WebSearch(); 2433 guidesSearcher.setUserDefinedLabel("Guides"); 2434 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/"); 2435 2436 referenceSearcher = new google.search.WebSearch(); 2437 referenceSearcher.setUserDefinedLabel("Reference"); 2438 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/"); 2439 2440 googleSearcher = new google.search.WebSearch(); 2441 googleSearcher.setUserDefinedLabel("Google Services"); 2442 googleSearcher.setSiteRestriction("http://developer.android.com/google/"); 2443 2444 blogSearcher = new google.search.WebSearch(); 2445 blogSearcher.setUserDefinedLabel("Blog"); 2446 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com"); 2447 2448 // add each searcher to the search control 2449 searchControl.addSearcher(devSiteSearcher, searchOptions); 2450 searchControl.addSearcher(designSearcher, searchOptions); 2451 searchControl.addSearcher(trainingSearcher, searchOptions); 2452 searchControl.addSearcher(guidesSearcher, searchOptions); 2453 searchControl.addSearcher(referenceSearcher, searchOptions); 2454 searchControl.addSearcher(googleSearcher, searchOptions); 2455 searchControl.addSearcher(blogSearcher, searchOptions); 2456 2457 // configure result options 2458 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET); 2459 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF); 2460 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT); 2461 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING); 2462 2463 // upon ajax search, refresh the url and search title 2464 searchControl.setSearchStartingCallback(this, function(control, searcher, query) { 2465 updateResultTitle(query); 2466 var query = document.getElementById('search_autocomplete').value; 2467 location.hash = 'q=' + query; 2468 }); 2469 2470 // once search results load, set up click listeners 2471 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) { 2472 addResultClickListeners(); 2473 }); 2474 2475 // draw the search results box 2476 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions); 2477 2478 // get query and execute the search 2479 searchControl.execute(decodeURI(getQuery(location.hash))); 2480 2481 document.getElementById("search_autocomplete").focus(); 2482 addTabListeners(); 2483} 2484// End of loadSearchResults 2485 2486 2487google.setOnLoadCallback(function(){ 2488 if (location.hash.indexOf("q=") == -1) { 2489 // if there's no query in the url, don't search and make sure results are hidden 2490 $('#searchResults').hide(); 2491 return; 2492 } else { 2493 // first time loading search results for this page 2494 $('#searchResults').slideDown('slow', setStickyTop); 2495 $(".search .close").removeClass("hide"); 2496 loadSearchResults(); 2497 } 2498}, true); 2499 2500// when an event on the browser history occurs (back, forward, load) requery hash and do search 2501$(window).hashchange( function(){ 2502 // If the hash isn't a search query or there's an error in the query, 2503 // then adjust the scroll position to account for sticky header, then exit. 2504 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) { 2505 // If the results pane is open, close it. 2506 if (!$("#searchResults").is(":hidden")) { 2507 hideResults(); 2508 } 2509 return; 2510 } 2511 2512 // Otherwise, we have a search to do 2513 var query = decodeURI(getQuery(location.hash)); 2514 searchControl.execute(query); 2515 $('#searchResults').slideDown('slow', setStickyTop); 2516 $("#search_autocomplete").focus(); 2517 $(".search .close").removeClass("hide"); 2518 2519 updateResultTitle(query); 2520}); 2521 2522function updateResultTitle(query) { 2523 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>"); 2524} 2525 2526// forcefully regain key-up event control (previously jacked by search api) 2527$("#search_autocomplete").keyup(function(event) { 2528 return search_changed(event, false, toRoot); 2529}); 2530 2531// add event listeners to each tab so we can track the browser history 2532function addTabListeners() { 2533 var tabHeaders = $(".gsc-tabHeader"); 2534 for (var i = 0; i < tabHeaders.length; i++) { 2535 $(tabHeaders[i]).attr("id",i).click(function() { 2536 /* 2537 // make a copy of the page numbers for the search left pane 2538 setTimeout(function() { 2539 // remove any residual page numbers 2540 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove(); 2541 // move the page numbers to the left position; make a clone, 2542 // because the element is drawn to the DOM only once 2543 // and because we're going to remove it (previous line), 2544 // we need it to be available to move again as the user navigates 2545 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible') 2546 .clone().appendTo('#searchResults .gsc-tabsArea'); 2547 }, 200); 2548 */ 2549 }); 2550 } 2551 setTimeout(function(){$(tabHeaders[0]).click()},200); 2552} 2553 2554// add analytics tracking events to each result link 2555function addResultClickListeners() { 2556 $("#searchResults a.gs-title").each(function(index, link) { 2557 // When user clicks enter for Google search results, track it 2558 $(link).click(function() { 2559 _gaq.push(['_trackEvent', 'Google Click', 'clicked: ' + $(this).text(), 2560 'from: ' + $("#search_autocomplete").val()]); 2561 }); 2562 }); 2563} 2564 2565 2566function getQuery(hash) { 2567 var queryParts = hash.split('='); 2568 return queryParts[1]; 2569} 2570 2571/* returns the given string with all HTML brackets converted to entities 2572 TODO: move this to the site's JS library */ 2573function escapeHTML(string) { 2574 return string.replace(/</g,"<") 2575 .replace(/>/g,">"); 2576} 2577 2578 2579 2580 2581 2582 2583 2584/* ######################################################## */ 2585/* ################# JAVADOC REFERENCE ################### */ 2586/* ######################################################## */ 2587 2588/* Initialize some droiddoc stuff, but only if we're in the reference */ 2589if (location.pathname.indexOf("/reference") == 0) { 2590 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0) 2591 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0) 2592 && !(location.pathname.indexOf("/reference/com/google") == 0)) { 2593 $(document).ready(function() { 2594 // init available apis based on user pref 2595 changeApiLevel(); 2596 initSidenavHeightResize() 2597 }); 2598 } 2599} 2600 2601var API_LEVEL_COOKIE = "api_level"; 2602var minLevel = 1; 2603var maxLevel = 1; 2604 2605/******* SIDENAV DIMENSIONS ************/ 2606 2607 function initSidenavHeightResize() { 2608 // Change the drag bar size to nicely fit the scrollbar positions 2609 var $dragBar = $(".ui-resizable-s"); 2610 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"}); 2611 2612 $( "#resize-packages-nav" ).resizable({ 2613 containment: "#nav-panels", 2614 handles: "s", 2615 alsoResize: "#packages-nav", 2616 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */ 2617 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */ 2618 }); 2619 2620 } 2621 2622function updateSidenavFixedWidth() { 2623 if (!navBarIsFixed) return; 2624 $('#devdoc-nav').css({ 2625 'width' : $('#side-nav').css('width'), 2626 'margin' : $('#side-nav').css('margin') 2627 }); 2628 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'}); 2629 2630 initSidenavHeightResize(); 2631} 2632 2633function updateSidenavFullscreenWidth() { 2634 if (!navBarIsFixed) return; 2635 $('#devdoc-nav').css({ 2636 'width' : $('#side-nav').css('width'), 2637 'margin' : $('#side-nav').css('margin') 2638 }); 2639 $('#devdoc-nav .totop').css({'left': 'inherit'}); 2640 2641 initSidenavHeightResize(); 2642} 2643 2644function buildApiLevelSelector() { 2645 maxLevel = SINCE_DATA.length; 2646 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE)); 2647 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default 2648 2649 minLevel = parseInt($("#doc-api-level").attr("class")); 2650 // Handle provisional api levels; the provisional level will always be the highest possible level 2651 // Provisional api levels will also have a length; other stuff that's just missing a level won't, 2652 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class) 2653 if (isNaN(minLevel) && minLevel.length) { 2654 minLevel = maxLevel; 2655 } 2656 var select = $("#apiLevelSelector").html("").change(changeApiLevel); 2657 for (var i = maxLevel-1; i >= 0; i--) { 2658 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]); 2659 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames) 2660 select.append(option); 2661 } 2662 2663 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true) 2664 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0); 2665 selectedLevelItem.setAttribute('selected',true); 2666} 2667 2668function changeApiLevel() { 2669 maxLevel = SINCE_DATA.length; 2670 var selectedLevel = maxLevel; 2671 2672 selectedLevel = parseInt($("#apiLevelSelector option:selected").val()); 2673 toggleVisisbleApis(selectedLevel, "body"); 2674 2675 var date = new Date(); 2676 date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years 2677 var expiration = date.toGMTString(); 2678 writeCookie(API_LEVEL_COOKIE, selectedLevel, null, expiration); 2679 2680 if (selectedLevel < minLevel) { 2681 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class"; 2682 $("#naMessage").show().html("<div><p><strong>This " + thing 2683 + " requires API level " + minLevel + " or higher.</strong></p>" 2684 + "<p>This document is hidden because your selected API level for the documentation is " 2685 + selectedLevel + ". You can change the documentation API level with the selector " 2686 + "above the left navigation.</p>" 2687 + "<p>For more information about specifying the API level your app requires, " 2688 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'" 2689 + ">Supporting Different Platform Versions</a>.</p>" 2690 + "<input type='button' value='OK, make this page visible' " 2691 + "title='Change the API level to " + minLevel + "' " 2692 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />" 2693 + "</div>"); 2694 } else { 2695 $("#naMessage").hide(); 2696 } 2697} 2698 2699function toggleVisisbleApis(selectedLevel, context) { 2700 var apis = $(".api",context); 2701 apis.each(function(i) { 2702 var obj = $(this); 2703 var className = obj.attr("class"); 2704 var apiLevelIndex = className.lastIndexOf("-")+1; 2705 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex); 2706 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length; 2707 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex); 2708 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail 2709 return; 2710 } 2711 apiLevel = parseInt(apiLevel); 2712 2713 // Handle provisional api levels; if this item's level is the provisional one, set it to the max 2714 var selectedLevelNum = parseInt(selectedLevel) 2715 var apiLevelNum = parseInt(apiLevel); 2716 if (isNaN(apiLevelNum)) { 2717 apiLevelNum = maxLevel; 2718 } 2719 2720 // Grey things out that aren't available and give a tooltip title 2721 if (apiLevelNum > selectedLevelNum) { 2722 obj.addClass("absent").attr("title","Requires API Level \"" 2723 + apiLevel + "\" or higher. To reveal, change the target API level " 2724 + "above the left navigation."); 2725 } 2726 else obj.removeClass("absent").removeAttr("title"); 2727 }); 2728} 2729 2730 2731 2732 2733/* ################# SIDENAV TREE VIEW ################### */ 2734 2735function new_node(me, mom, text, link, children_data, api_level) 2736{ 2737 var node = new Object(); 2738 node.children = Array(); 2739 node.children_data = children_data; 2740 node.depth = mom.depth + 1; 2741 2742 node.li = document.createElement("li"); 2743 mom.get_children_ul().appendChild(node.li); 2744 2745 node.label_div = document.createElement("div"); 2746 node.label_div.className = "label"; 2747 if (api_level != null) { 2748 $(node.label_div).addClass("api"); 2749 $(node.label_div).addClass("api-level-"+api_level); 2750 } 2751 node.li.appendChild(node.label_div); 2752 2753 if (children_data != null) { 2754 node.expand_toggle = document.createElement("a"); 2755 node.expand_toggle.href = "javascript:void(0)"; 2756 node.expand_toggle.onclick = function() { 2757 if (node.expanded) { 2758 $(node.get_children_ul()).slideUp("fast"); 2759 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png"; 2760 node.expanded = false; 2761 } else { 2762 expand_node(me, node); 2763 } 2764 }; 2765 node.label_div.appendChild(node.expand_toggle); 2766 2767 node.plus_img = document.createElement("img"); 2768 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png"; 2769 node.plus_img.className = "plus"; 2770 node.plus_img.width = "8"; 2771 node.plus_img.border = "0"; 2772 node.expand_toggle.appendChild(node.plus_img); 2773 2774 node.expanded = false; 2775 } 2776 2777 var a = document.createElement("a"); 2778 node.label_div.appendChild(a); 2779 node.label = document.createTextNode(text); 2780 a.appendChild(node.label); 2781 if (link) { 2782 a.href = me.toroot + link; 2783 } else { 2784 if (children_data != null) { 2785 a.className = "nolink"; 2786 a.href = "javascript:void(0)"; 2787 a.onclick = node.expand_toggle.onclick; 2788 // This next line shouldn't be necessary. I'll buy a beer for the first 2789 // person who figures out how to remove this line and have the link 2790 // toggle shut on the first try. --joeo@android.com 2791 node.expanded = false; 2792 } 2793 } 2794 2795 2796 node.children_ul = null; 2797 node.get_children_ul = function() { 2798 if (!node.children_ul) { 2799 node.children_ul = document.createElement("ul"); 2800 node.children_ul.className = "children_ul"; 2801 node.children_ul.style.display = "none"; 2802 node.li.appendChild(node.children_ul); 2803 } 2804 return node.children_ul; 2805 }; 2806 2807 return node; 2808} 2809 2810 2811 2812 2813function expand_node(me, node) 2814{ 2815 if (node.children_data && !node.expanded) { 2816 if (node.children_visited) { 2817 $(node.get_children_ul()).slideDown("fast"); 2818 } else { 2819 get_node(me, node); 2820 if ($(node.label_div).hasClass("absent")) { 2821 $(node.get_children_ul()).addClass("absent"); 2822 } 2823 $(node.get_children_ul()).slideDown("fast"); 2824 } 2825 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png"; 2826 node.expanded = true; 2827 2828 // perform api level toggling because new nodes are new to the DOM 2829 var selectedLevel = $("#apiLevelSelector option:selected").val(); 2830 toggleVisisbleApis(selectedLevel, "#side-nav"); 2831 } 2832} 2833 2834function get_node(me, mom) 2835{ 2836 mom.children_visited = true; 2837 for (var i in mom.children_data) { 2838 var node_data = mom.children_data[i]; 2839 mom.children[i] = new_node(me, mom, node_data[0], node_data[1], 2840 node_data[2], node_data[3]); 2841 } 2842} 2843 2844function this_page_relative(toroot) 2845{ 2846 var full = document.location.pathname; 2847 var file = ""; 2848 if (toroot.substr(0, 1) == "/") { 2849 if (full.substr(0, toroot.length) == toroot) { 2850 return full.substr(toroot.length); 2851 } else { 2852 // the file isn't under toroot. Fail. 2853 return null; 2854 } 2855 } else { 2856 if (toroot != "./") { 2857 toroot = "./" + toroot; 2858 } 2859 do { 2860 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") { 2861 var pos = full.lastIndexOf("/"); 2862 file = full.substr(pos) + file; 2863 full = full.substr(0, pos); 2864 toroot = toroot.substr(0, toroot.length-3); 2865 } 2866 } while (toroot != "" && toroot != "/"); 2867 return file.substr(1); 2868 } 2869} 2870 2871function find_page(url, data) 2872{ 2873 var nodes = data; 2874 var result = null; 2875 for (var i in nodes) { 2876 var d = nodes[i]; 2877 if (d[1] == url) { 2878 return new Array(i); 2879 } 2880 else if (d[2] != null) { 2881 result = find_page(url, d[2]); 2882 if (result != null) { 2883 return (new Array(i).concat(result)); 2884 } 2885 } 2886 } 2887 return null; 2888} 2889 2890function init_default_navtree(toroot) { 2891 // load json file for navtree data 2892 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) { 2893 // when the file is loaded, initialize the tree 2894 if(jqxhr.status === 200) { 2895 init_navtree("tree-list", toroot, NAVTREE_DATA); 2896 } 2897 }); 2898 2899 // perform api level toggling because because the whole tree is new to the DOM 2900 var selectedLevel = $("#apiLevelSelector option:selected").val(); 2901 toggleVisisbleApis(selectedLevel, "#side-nav"); 2902} 2903 2904function init_navtree(navtree_id, toroot, root_nodes) 2905{ 2906 var me = new Object(); 2907 me.toroot = toroot; 2908 me.node = new Object(); 2909 2910 me.node.li = document.getElementById(navtree_id); 2911 me.node.children_data = root_nodes; 2912 me.node.children = new Array(); 2913 me.node.children_ul = document.createElement("ul"); 2914 me.node.get_children_ul = function() { return me.node.children_ul; }; 2915 //me.node.children_ul.className = "children_ul"; 2916 me.node.li.appendChild(me.node.children_ul); 2917 me.node.depth = 0; 2918 2919 get_node(me, me.node); 2920 2921 me.this_page = this_page_relative(toroot); 2922 me.breadcrumbs = find_page(me.this_page, root_nodes); 2923 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) { 2924 var mom = me.node; 2925 for (var i in me.breadcrumbs) { 2926 var j = me.breadcrumbs[i]; 2927 mom = mom.children[j]; 2928 expand_node(me, mom); 2929 } 2930 mom.label_div.className = mom.label_div.className + " selected"; 2931 addLoadEvent(function() { 2932 scrollIntoView("nav-tree"); 2933 }); 2934 } 2935} 2936 2937 2938 2939 2940 2941 2942 2943 2944/* TODO: eliminate redundancy with non-google functions */ 2945function init_google_navtree(navtree_id, toroot, root_nodes) 2946{ 2947 var me = new Object(); 2948 me.toroot = toroot; 2949 me.node = new Object(); 2950 2951 me.node.li = document.getElementById(navtree_id); 2952 me.node.children_data = root_nodes; 2953 me.node.children = new Array(); 2954 me.node.children_ul = document.createElement("ul"); 2955 me.node.get_children_ul = function() { return me.node.children_ul; }; 2956 //me.node.children_ul.className = "children_ul"; 2957 me.node.li.appendChild(me.node.children_ul); 2958 me.node.depth = 0; 2959 2960 get_google_node(me, me.node); 2961} 2962 2963function new_google_node(me, mom, text, link, children_data, api_level) 2964{ 2965 var node = new Object(); 2966 var child; 2967 node.children = Array(); 2968 node.children_data = children_data; 2969 node.depth = mom.depth + 1; 2970 node.get_children_ul = function() { 2971 if (!node.children_ul) { 2972 node.children_ul = document.createElement("ul"); 2973 node.children_ul.className = "tree-list-children"; 2974 node.li.appendChild(node.children_ul); 2975 } 2976 return node.children_ul; 2977 }; 2978 node.li = document.createElement("li"); 2979 2980 mom.get_children_ul().appendChild(node.li); 2981 2982 2983 if(link) { 2984 child = document.createElement("a"); 2985 2986 } 2987 else { 2988 child = document.createElement("span"); 2989 child.className = "tree-list-subtitle"; 2990 2991 } 2992 if (children_data != null) { 2993 node.li.className="nav-section"; 2994 node.label_div = document.createElement("div"); 2995 node.label_div.className = "nav-section-header-ref"; 2996 node.li.appendChild(node.label_div); 2997 get_google_node(me, node); 2998 node.label_div.appendChild(child); 2999 } 3000 else { 3001 node.li.appendChild(child); 3002 } 3003 if(link) { 3004 child.href = me.toroot + link; 3005 } 3006 node.label = document.createTextNode(text); 3007 child.appendChild(node.label); 3008 3009 node.children_ul = null; 3010 3011 return node; 3012} 3013 3014function get_google_node(me, mom) 3015{ 3016 mom.children_visited = true; 3017 var linkText; 3018 for (var i in mom.children_data) { 3019 var node_data = mom.children_data[i]; 3020 linkText = node_data[0]; 3021 3022 if(linkText.match("^"+"com.google.android")=="com.google.android"){ 3023 linkText = linkText.substr(19, linkText.length); 3024 } 3025 mom.children[i] = new_google_node(me, mom, linkText, node_data[1], 3026 node_data[2], node_data[3]); 3027 } 3028} 3029 3030 3031 3032 3033 3034 3035/****** NEW version of script to build google and sample navs dynamically ******/ 3036// TODO: update Google reference docs to tolerate this new implementation 3037 3038var NODE_NAME = 0; 3039var NODE_HREF = 1; 3040var NODE_GROUP = 2; 3041var NODE_TAGS = 3; 3042var NODE_CHILDREN = 4; 3043 3044function init_google_navtree2(navtree_id, data) 3045{ 3046 var $containerUl = $("#"+navtree_id); 3047 for (var i in data) { 3048 var node_data = data[i]; 3049 $containerUl.append(new_google_node2(node_data)); 3050 } 3051 3052 // Make all third-generation list items 'sticky' to prevent them from collapsing 3053 $containerUl.find('li li li.nav-section').addClass('sticky'); 3054 3055 initExpandableNavItems("#"+navtree_id); 3056} 3057 3058function new_google_node2(node_data) 3059{ 3060 var linkText = node_data[NODE_NAME]; 3061 if(linkText.match("^"+"com.google.android")=="com.google.android"){ 3062 linkText = linkText.substr(19, linkText.length); 3063 } 3064 var $li = $('<li>'); 3065 var $a; 3066 if (node_data[NODE_HREF] != null) { 3067 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >' 3068 + linkText + '</a>'); 3069 } else { 3070 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >' 3071 + linkText + '/</a>'); 3072 } 3073 var $childUl = $('<ul>'); 3074 if (node_data[NODE_CHILDREN] != null) { 3075 $li.addClass("nav-section"); 3076 $a = $('<div class="nav-section-header">').append($a); 3077 if (node_data[NODE_HREF] == null) $a.addClass('empty'); 3078 3079 for (var i in node_data[NODE_CHILDREN]) { 3080 var child_node_data = node_data[NODE_CHILDREN][i]; 3081 $childUl.append(new_google_node2(child_node_data)); 3082 } 3083 $li.append($childUl); 3084 } 3085 $li.prepend($a); 3086 3087 return $li; 3088} 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100function showGoogleRefTree() { 3101 init_default_google_navtree(toRoot); 3102 init_default_gcm_navtree(toRoot); 3103} 3104 3105function init_default_google_navtree(toroot) { 3106 // load json file for navtree data 3107 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) { 3108 // when the file is loaded, initialize the tree 3109 if(jqxhr.status === 200) { 3110 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA); 3111 highlightSidenav(); 3112 resizeNav(); 3113 } 3114 }); 3115} 3116 3117function init_default_gcm_navtree(toroot) { 3118 // load json file for navtree data 3119 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) { 3120 // when the file is loaded, initialize the tree 3121 if(jqxhr.status === 200) { 3122 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA); 3123 highlightSidenav(); 3124 resizeNav(); 3125 } 3126 }); 3127} 3128 3129function showSamplesRefTree() { 3130 init_default_samples_navtree(toRoot); 3131} 3132 3133function init_default_samples_navtree(toroot) { 3134 // load json file for navtree data 3135 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) { 3136 // when the file is loaded, initialize the tree 3137 if(jqxhr.status === 200) { 3138 // hack to remove the "about the samples" link then put it back in 3139 // after we nuke the list to remove the dummy static list of samples 3140 var $firstLi = $("#nav.samples-nav > li:first-child").clone(); 3141 $("#nav.samples-nav").empty(); 3142 $("#nav.samples-nav").append($firstLi); 3143 3144 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA); 3145 highlightSidenav(); 3146 resizeNav(); 3147 if ($("#jd-content #samples").length) { 3148 showSamples(); 3149 } 3150 } 3151 }); 3152} 3153 3154/* TOGGLE INHERITED MEMBERS */ 3155 3156/* Toggle an inherited class (arrow toggle) 3157 * @param linkObj The link that was clicked. 3158 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed. 3159 * 'null' to simply toggle. 3160 */ 3161function toggleInherited(linkObj, expand) { 3162 var base = linkObj.getAttribute("id"); 3163 var list = document.getElementById(base + "-list"); 3164 var summary = document.getElementById(base + "-summary"); 3165 var trigger = document.getElementById(base + "-trigger"); 3166 var a = $(linkObj); 3167 if ( (expand == null && a.hasClass("closed")) || expand ) { 3168 list.style.display = "none"; 3169 summary.style.display = "block"; 3170 trigger.src = toRoot + "assets/images/triangle-opened.png"; 3171 a.removeClass("closed"); 3172 a.addClass("opened"); 3173 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) { 3174 list.style.display = "block"; 3175 summary.style.display = "none"; 3176 trigger.src = toRoot + "assets/images/triangle-closed.png"; 3177 a.removeClass("opened"); 3178 a.addClass("closed"); 3179 } 3180 return false; 3181} 3182 3183/* Toggle all inherited classes in a single table (e.g. all inherited methods) 3184 * @param linkObj The link that was clicked. 3185 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed. 3186 * 'null' to simply toggle. 3187 */ 3188function toggleAllInherited(linkObj, expand) { 3189 var a = $(linkObj); 3190 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody 3191 var expandos = $(".jd-expando-trigger", table); 3192 if ( (expand == null && a.text() == "[Expand]") || expand ) { 3193 expandos.each(function(i) { 3194 toggleInherited(this, true); 3195 }); 3196 a.text("[Collapse]"); 3197 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) { 3198 expandos.each(function(i) { 3199 toggleInherited(this, false); 3200 }); 3201 a.text("[Expand]"); 3202 } 3203 return false; 3204} 3205 3206/* Toggle all inherited members in the class (link in the class title) 3207 */ 3208function toggleAllClassInherited() { 3209 var a = $("#toggleAllClassInherited"); // get toggle link from class title 3210 var toggles = $(".toggle-all", $("#body-content")); 3211 if (a.text() == "[Expand All]") { 3212 toggles.each(function(i) { 3213 toggleAllInherited(this, true); 3214 }); 3215 a.text("[Collapse All]"); 3216 } else { 3217 toggles.each(function(i) { 3218 toggleAllInherited(this, false); 3219 }); 3220 a.text("[Expand All]"); 3221 } 3222 return false; 3223} 3224 3225/* Expand all inherited members in the class. Used when initiating page search */ 3226function ensureAllInheritedExpanded() { 3227 var toggles = $(".toggle-all", $("#body-content")); 3228 toggles.each(function(i) { 3229 toggleAllInherited(this, true); 3230 }); 3231 $("#toggleAllClassInherited").text("[Collapse All]"); 3232} 3233 3234 3235/* HANDLE KEY EVENTS 3236 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search) 3237 */ 3238var agent = navigator['userAgent'].toLowerCase(); 3239var mac = agent.indexOf("macintosh") != -1; 3240 3241$(document).keydown( function(e) { 3242var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key 3243 if (control && e.which == 70) { // 70 is "F" 3244 ensureAllInheritedExpanded(); 3245 } 3246}); 3247 3248 3249 3250 3251 3252 3253/* On-demand functions */ 3254 3255/** Move sample code line numbers out of PRE block and into non-copyable column */ 3256function initCodeLineNumbers() { 3257 var numbers = $("#codesample-block a.number"); 3258 if (numbers.length) { 3259 $("#codesample-line-numbers").removeClass("hidden").append(numbers); 3260 } 3261 3262 $(document).ready(function() { 3263 // select entire line when clicked 3264 $("span.code-line").click(function() { 3265 if (!shifted) { 3266 selectText(this); 3267 } 3268 }); 3269 // invoke line link on double click 3270 $(".code-line").dblclick(function() { 3271 document.location.hash = $(this).attr('id'); 3272 }); 3273 // highlight the line when hovering on the number 3274 $("#codesample-line-numbers a.number").mouseover(function() { 3275 var id = $(this).attr('href'); 3276 $(id).css('background','#e7e7e7'); 3277 }); 3278 $("#codesample-line-numbers a.number").mouseout(function() { 3279 var id = $(this).attr('href'); 3280 $(id).css('background','none'); 3281 }); 3282 }); 3283} 3284 3285// create SHIFT key binder to avoid the selectText method when selecting multiple lines 3286var shifted = false; 3287$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} ); 3288 3289// courtesy of jasonedelman.com 3290function selectText(element) { 3291 var doc = document 3292 , range, selection 3293 ; 3294 if (doc.body.createTextRange) { //ms 3295 range = doc.body.createTextRange(); 3296 range.moveToElementText(element); 3297 range.select(); 3298 } else if (window.getSelection) { //all others 3299 selection = window.getSelection(); 3300 range = doc.createRange(); 3301 range.selectNodeContents(element); 3302 selection.removeAllRanges(); 3303 selection.addRange(range); 3304 } 3305} 3306 3307 3308 3309 3310/** Display links and other information about samples that match the 3311 group specified by the URL */ 3312function showSamples() { 3313 var group = $("#samples").attr('class'); 3314 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>"); 3315 3316 var $ul = $("<ul>"); 3317 $selectedLi = $("#nav li.selected"); 3318 3319 $selectedLi.children("ul").children("li").each(function() { 3320 var $li = $("<li>").append($(this).find("a").first().clone()); 3321 $ul.append($li); 3322 }); 3323 3324 $("#samples").append($ul); 3325 3326} 3327 3328 3329 3330/* ########################################################## */ 3331/* ################### RESOURCE CARDS ##################### */ 3332/* ########################################################## */ 3333 3334/** Handle resource queries, collections, and grids (sections). Requires 3335 jd_tag_helpers.js and the *_unified_data.js to be loaded. */ 3336 3337(function() { 3338 // Prevent the same resource from being loaded more than once per page. 3339 var addedPageResources = {}; 3340 3341 $(document).ready(function() { 3342 $('.resource-widget').each(function() { 3343 initResourceWidget(this); 3344 }); 3345 3346 /* Pass the line height to ellipsisfade() to adjust the height of the 3347 text container to show the max number of lines possible, without 3348 showing lines that are cut off. This works with the css ellipsis 3349 classes to fade last text line and apply an ellipsis char. */ 3350 3351 //card text currently uses 15px line height. 3352 var lineHeight = 15; 3353 $('.card-info .text').ellipsisfade(lineHeight); 3354 }); 3355 3356 /* 3357 Three types of resource layouts: 3358 Flow - Uses a fixed row-height flow using float left style. 3359 Carousel - Single card slideshow all same dimension absolute. 3360 Stack - Uses fixed columns and flexible element height. 3361 */ 3362 function initResourceWidget(widget) { 3363 var $widget = $(widget); 3364 var isFlow = $widget.hasClass('resource-flow-layout'), 3365 isCarousel = $widget.hasClass('resource-carousel-layout'), 3366 isStack = $widget.hasClass('resource-stack-layout'); 3367 3368 // find size of widget by pulling out its class name 3369 var sizeCols = 1; 3370 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/); 3371 if (m) { 3372 sizeCols = parseInt(m[1], 10); 3373 } 3374 3375 var opts = { 3376 cardSizes: ($widget.data('cardsizes') || '').split(','), 3377 maxResults: parseInt($widget.data('maxresults') || '100', 10), 3378 itemsPerPage: $widget.data('itemsperpage'), 3379 sortOrder: $widget.data('sortorder'), 3380 query: $widget.data('query'), 3381 section: $widget.data('section'), 3382 sizeCols: sizeCols 3383 }; 3384 3385 // run the search for the set of resources to show 3386 3387 var resources = buildResourceList(opts); 3388 3389 if (isFlow) { 3390 drawResourcesFlowWidget($widget, opts, resources); 3391 } else if (isCarousel) { 3392 drawResourcesCarouselWidget($widget, opts, resources); 3393 } else if (isStack) { 3394 var sections = buildSectionList(opts); 3395 opts['numStacks'] = $widget.data('numstacks'); 3396 drawResourcesStackWidget($widget, opts, resources, sections); 3397 } 3398 } 3399 3400 /* Initializes a Resource Carousel Widget */ 3401 function drawResourcesCarouselWidget($widget, opts, resources) { 3402 $widget.empty(); 3403 var plusone = true; //always show plusone on carousel 3404 3405 $widget.addClass('resource-card slideshow-container') 3406 .append($('<a>').addClass('slideshow-prev').text('Prev')) 3407 .append($('<a>').addClass('slideshow-next').text('Next')); 3408 3409 var css = { 'width': $widget.width() + 'px', 3410 'height': $widget.height() + 'px' }; 3411 3412 var $ul = $('<ul>'); 3413 3414 for (var i = 0; i < resources.length; ++i) { 3415 //keep url clean for matching and offline mode handling 3416 var urlPrefix = resources[i].url.indexOf("//") > -1 ? "" : toRoot; 3417 var $card = $('<a>') 3418 .attr('href', urlPrefix + resources[i].url) 3419 .decorateResourceCard(resources[i],plusone); 3420 3421 $('<li>').css(css) 3422 .append($card) 3423 .appendTo($ul); 3424 } 3425 3426 $('<div>').addClass('frame') 3427 .append($ul) 3428 .appendTo($widget); 3429 3430 $widget.dacSlideshow({ 3431 auto: true, 3432 btnPrev: '.slideshow-prev', 3433 btnNext: '.slideshow-next' 3434 }); 3435 }; 3436 3437 /* Initializes a Resource Card Stack Widget (column-based layout) */ 3438 function drawResourcesStackWidget($widget, opts, resources, sections) { 3439 // Don't empty widget, grab all items inside since they will be the first 3440 // items stacked, followed by the resource query 3441 var plusone = true; //by default show plusone on section cards 3442 var cards = $widget.find('.resource-card').detach().toArray(); 3443 var numStacks = opts.numStacks || 1; 3444 var $stacks = []; 3445 var urlString; 3446 3447 for (var i = 0; i < numStacks; ++i) { 3448 $stacks[i] = $('<div>').addClass('resource-card-stack') 3449 .appendTo($widget); 3450 } 3451 3452 var sectionResources = []; 3453 3454 // Extract any subsections that are actually resource cards 3455 for (var i = 0; i < sections.length; ++i) { 3456 if (!sections[i].sections || !sections[i].sections.length) { 3457 //keep url clean for matching and offline mode handling 3458 urlPrefix = sections[i].url.indexOf("//") > -1 ? "" : toRoot; 3459 // Render it as a resource card 3460 3461 sectionResources.push( 3462 $('<a>') 3463 .addClass('resource-card section-card') 3464 .attr('href', urlPrefix + sections[i].resource.url) 3465 .decorateResourceCard(sections[i].resource,plusone)[0] 3466 ); 3467 3468 } else { 3469 cards.push( 3470 $('<div>') 3471 .addClass('resource-card section-card-menu') 3472 .decorateResourceSection(sections[i],plusone)[0] 3473 ); 3474 } 3475 } 3476 3477 cards = cards.concat(sectionResources); 3478 3479 for (var i = 0; i < resources.length; ++i) { 3480 //keep url clean for matching and offline mode handling 3481 urlPrefix = resources[i].url.indexOf("//") > -1 ? "" : toRoot; 3482 var $card = $('<a>') 3483 .addClass('resource-card related-card') 3484 .attr('href', urlPrefix + resources[i].url) 3485 .decorateResourceCard(resources[i],plusone); 3486 3487 cards.push($card[0]); 3488 } 3489 3490 for (var i = 0; i < cards.length; ++i) { 3491 // Find the stack with the shortest height, but give preference to 3492 // left to right order. 3493 var minHeight = $stacks[0].height(); 3494 var minIndex = 0; 3495 3496 for (var j = 1; j < numStacks; ++j) { 3497 var height = $stacks[j].height(); 3498 if (height < minHeight - 45) { 3499 minHeight = height; 3500 minIndex = j; 3501 } 3502 } 3503 3504 $stacks[minIndex].append($(cards[i])); 3505 } 3506 3507 }; 3508 3509 /* Initializes a flow widget, see distribute.scss for generating accompanying css */ 3510 function drawResourcesFlowWidget($widget, opts, resources) { 3511 $widget.empty(); 3512 var cardSizes = opts.cardSizes || ['6x6']; 3513 var i = 0, j = 0; 3514 var plusone = true; // by default show plusone on resource cards 3515 3516 while (i < resources.length) { 3517 var cardSize = cardSizes[j++ % cardSizes.length]; 3518 cardSize = cardSize.replace(/^\s+|\s+$/,''); 3519 // Some card sizes do not get a plusone button, such as where space is constrained 3520 // or for cards commonly embedded in docs (to improve overall page speed). 3521 plusone = !((cardSize == "6x2") || (cardSize == "6x3") || 3522 (cardSize == "9x2") || (cardSize == "9x3") || 3523 (cardSize == "12x2") || (cardSize == "12x3")); 3524 3525 // A stack has a third dimension which is the number of stacked items 3526 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/); 3527 var stackCount = 0; 3528 var $stackDiv = null; 3529 3530 if (isStack) { 3531 // Create a stack container which should have the dimensions defined 3532 // by the product of the items inside. 3533 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1] 3534 + 'x' + isStack[2] * isStack[3]) .appendTo($widget); 3535 } 3536 3537 // Build each stack item or just a single item 3538 do { 3539 var resource = resources[i]; 3540 //keep url clean for matching and offline mode handling 3541 urlPrefix = resource.url.indexOf("//") > -1 ? "" : toRoot; 3542 var $card = $('<a>') 3543 .addClass('resource-card resource-card-' + cardSize + ' resource-card-' + resource.type) 3544 .attr('href', urlPrefix + resource.url); 3545 3546 if (isStack) { 3547 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]); 3548 if (++stackCount == parseInt(isStack[3])) { 3549 $card.addClass('resource-card-row-stack-last'); 3550 stackCount = 0; 3551 } 3552 } else { 3553 stackCount = 0; 3554 } 3555 3556 $card.decorateResourceCard(resource,plusone) 3557 .appendTo($stackDiv || $widget); 3558 3559 } while (++i < resources.length && stackCount > 0); 3560 } 3561 } 3562 3563 /* Build a site map of resources using a section as a root. */ 3564 function buildSectionList(opts) { 3565 if (opts.section && SECTION_BY_ID[opts.section]) { 3566 return SECTION_BY_ID[opts.section].sections || []; 3567 } 3568 return []; 3569 } 3570 3571 function buildResourceList(opts) { 3572 var maxResults = opts.maxResults || 100; 3573 3574 var query = opts.query || ''; 3575 var expressions = parseResourceQuery(query); 3576 var addedResourceIndices = {}; 3577 var results = []; 3578 3579 for (var i = 0; i < expressions.length; i++) { 3580 var clauses = expressions[i]; 3581 3582 // build initial set of resources from first clause 3583 var firstClause = clauses[0]; 3584 var resources = []; 3585 switch (firstClause.attr) { 3586 case 'type': 3587 resources = ALL_RESOURCES_BY_TYPE[firstClause.value]; 3588 break; 3589 case 'lang': 3590 resources = ALL_RESOURCES_BY_LANG[firstClause.value]; 3591 break; 3592 case 'tag': 3593 resources = ALL_RESOURCES_BY_TAG[firstClause.value]; 3594 break; 3595 case 'collection': 3596 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || []; 3597 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; }); 3598 break; 3599 case 'section': 3600 var urls = SITE_MAP[firstClause.value].sections || []; 3601 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; }); 3602 break; 3603 } 3604 // console.log(firstClause.attr + ':' + firstClause.value); 3605 resources = resources || []; 3606 3607 // use additional clauses to filter corpus 3608 if (clauses.length > 1) { 3609 var otherClauses = clauses.slice(1); 3610 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses)); 3611 } 3612 3613 // filter out resources already added 3614 if (i > 1) { 3615 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices)); 3616 } 3617 3618 // add to list of already added indices 3619 for (var j = 0; j < resources.length; j++) { 3620 // console.log(resources[j].title); 3621 addedResourceIndices[resources[j].index] = 1; 3622 } 3623 3624 // concat to final results list 3625 results = results.concat(resources); 3626 } 3627 3628 if (opts.sortOrder && results.length) { 3629 var attr = opts.sortOrder; 3630 3631 if (opts.sortOrder == 'random') { 3632 var i = results.length, j, temp; 3633 while (--i) { 3634 j = Math.floor(Math.random() * (i + 1)); 3635 temp = results[i]; 3636 results[i] = results[j]; 3637 results[j] = temp; 3638 } 3639 } else { 3640 var desc = attr.charAt(0) == '-'; 3641 if (desc) { 3642 attr = attr.substring(1); 3643 } 3644 results = results.sort(function(x,y) { 3645 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10)); 3646 }); 3647 } 3648 } 3649 3650 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources)); 3651 results = results.slice(0, maxResults); 3652 3653 for (var j = 0; j < results.length; ++j) { 3654 addedPageResources[results[j].index] = 1; 3655 } 3656 3657 return results; 3658 } 3659 3660 3661 function getResourceNotAlreadyAddedFilter(addedResourceIndices) { 3662 return function(resource) { 3663 return !addedResourceIndices[resource.index]; 3664 }; 3665 } 3666 3667 3668 function getResourceMatchesClausesFilter(clauses) { 3669 return function(resource) { 3670 return doesResourceMatchClauses(resource, clauses); 3671 }; 3672 } 3673 3674 3675 function doesResourceMatchClauses(resource, clauses) { 3676 for (var i = 0; i < clauses.length; i++) { 3677 var map; 3678 switch (clauses[i].attr) { 3679 case 'type': 3680 map = IS_RESOURCE_OF_TYPE[clauses[i].value]; 3681 break; 3682 case 'lang': 3683 map = IS_RESOURCE_IN_LANG[clauses[i].value]; 3684 break; 3685 case 'tag': 3686 map = IS_RESOURCE_TAGGED[clauses[i].value]; 3687 break; 3688 } 3689 3690 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) { 3691 return clauses[i].negative; 3692 } 3693 } 3694 return true; 3695 } 3696 3697 3698 function parseResourceQuery(query) { 3699 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video') 3700 var expressions = []; 3701 var expressionStrs = query.split(',') || []; 3702 for (var i = 0; i < expressionStrs.length; i++) { 3703 var expr = expressionStrs[i] || ''; 3704 3705 // Break expression into clauses (clause e.g. 'tag:foo') 3706 var clauses = []; 3707 var clauseStrs = expr.split(/(?=[\+\-])/); 3708 for (var j = 0; j < clauseStrs.length; j++) { 3709 var clauseStr = clauseStrs[j] || ''; 3710 3711 // Get attribute and value from clause (e.g. attribute='tag', value='foo') 3712 var parts = clauseStr.split(':'); 3713 var clause = {}; 3714 3715 clause.attr = parts[0].replace(/^\s+|\s+$/g,''); 3716 if (clause.attr) { 3717 if (clause.attr.charAt(0) == '+') { 3718 clause.attr = clause.attr.substring(1); 3719 } else if (clause.attr.charAt(0) == '-') { 3720 clause.negative = true; 3721 clause.attr = clause.attr.substring(1); 3722 } 3723 } 3724 3725 if (parts.length > 1) { 3726 clause.value = parts[1].replace(/^\s+|\s+$/g,''); 3727 } 3728 3729 clauses.push(clause); 3730 } 3731 3732 if (!clauses.length) { 3733 continue; 3734 } 3735 3736 expressions.push(clauses); 3737 } 3738 3739 return expressions; 3740 } 3741})(); 3742 3743(function($) { 3744 /* Simple jquery function to create dom for a standard resource card */ 3745 $.fn.decorateResourceCard = function(resource,plusone) { 3746 var section = resource.group || resource.type; 3747 var imgUrl; 3748 if (resource.image) { 3749 //keep url clean for matching and offline mode handling 3750 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot; 3751 imgUrl = urlPrefix + resource.image; 3752 } 3753 //add linkout logic here. check url or type, assign a class, map to css :after 3754 $('<div>') 3755 .addClass('card-bg') 3756 .css('background-image', 'url(' + (imgUrl || toRoot + 'assets/images/resource-card-default-android.jpg') + ')') 3757 .appendTo(this); 3758 if (!plusone) { 3759 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : '')) 3760 .append($('<div>').addClass('section').text(section)) 3761 .append($('<div>').addClass('title').html(resource.title)) 3762 .append($('<div>').addClass('description ellipsis') 3763 .append($('<div>').addClass('text').html(resource.summary)) 3764 .append($('<div>').addClass('util'))) 3765 .appendTo(this); 3766 } else { 3767 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : '')) 3768 .append($('<div>').addClass('section').text(section)) 3769 .append($('<div>').addClass('title').html(resource.title)) 3770 .append($('<div>').addClass('description ellipsis') 3771 .append($('<div>').addClass('text').html(resource.summary)) 3772 .append($('<div>').addClass('util') 3773 .append($('<div>').addClass('g-plusone') 3774 .attr('data-size', 'small') 3775 .attr('data-align', 'right') 3776 .attr('data-href', resource.url)))) 3777 .appendTo(this); 3778 } 3779 3780 return this; 3781 }; 3782 3783 /* Simple jquery function to create dom for a resource section card (menu) */ 3784 $.fn.decorateResourceSection = function(section,plusone) { 3785 var resource = section.resource; 3786 //keep url clean for matching and offline mode handling 3787 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot; 3788 var $base = $('<a>') 3789 .addClass('card-bg') 3790 .attr('href', resource.url) 3791 .append($('<div>').addClass('card-section-icon') 3792 .append($('<div>').addClass('icon')) 3793 .append($('<div>').addClass('section').html(resource.title))) 3794 .appendTo(this); 3795 3796 var $cardInfo = $('<div>').addClass('card-info').appendTo(this); 3797 3798 if (section.sections && section.sections.length) { 3799 // Recurse the section sub-tree to find a resource image. 3800 var stack = [section]; 3801 3802 while (stack.length) { 3803 if (stack[0].resource.image) { 3804 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')'); 3805 break; 3806 } 3807 3808 if (stack[0].sections) { 3809 stack = stack.concat(stack[0].sections); 3810 } 3811 3812 stack.shift(); 3813 } 3814 3815 var $ul = $('<ul>') 3816 .appendTo($cardInfo); 3817 3818 var max = section.sections.length > 3 ? 3 : section.sections.length; 3819 3820 for (var i = 0; i < max; ++i) { 3821 3822 var subResource = section.sections[i]; 3823 if (!plusone) { 3824 $('<li>') 3825 .append($('<a>').attr('href', subResource.url) 3826 .append($('<div>').addClass('title').html(subResource.title)) 3827 .append($('<div>').addClass('description ellipsis') 3828 .append($('<div>').addClass('text').html(subResource.summary)) 3829 .append($('<div>').addClass('util')))) 3830 .appendTo($ul); 3831 } else { 3832 $('<li>') 3833 .append($('<a>').attr('href', subResource.url) 3834 .append($('<div>').addClass('title').html(subResource.title)) 3835 .append($('<div>').addClass('description ellipsis') 3836 .append($('<div>').addClass('text').html(subResource.summary)) 3837 .append($('<div>').addClass('util') 3838 .append($('<div>').addClass('g-plusone') 3839 .attr('data-size', 'small') 3840 .attr('data-align', 'right') 3841 .attr('data-href', resource.url))))) 3842 .appendTo($ul); 3843 } 3844 } 3845 3846 // Add a more row 3847 if (max < section.sections.length) { 3848 $('<li>') 3849 .append($('<a>').attr('href', resource.url) 3850 .append($('<div>') 3851 .addClass('title') 3852 .text('More'))) 3853 .appendTo($ul); 3854 } 3855 } else { 3856 // No sub-resources, just render description? 3857 } 3858 3859 return this; 3860 }; 3861})(jQuery); 3862/* Calculate the vertical area remaining */ 3863(function($) { 3864 $.fn.ellipsisfade= function(lineHeight) { 3865 this.each(function() { 3866 // get element text 3867 var $this = $(this); 3868 var remainingHeight = $this.parent().parent().height(); 3869 $this.parent().siblings().each(function () 3870 { 3871 var h = $(this).height(); 3872 remainingHeight = remainingHeight - h; 3873 }); 3874 3875 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight 3876 $this.parent().css({'height': adjustedRemainingHeight}); 3877 $this.css({'height': "auto"}); 3878 }); 3879 3880 return this; 3881 }; 3882}) (jQuery); 3883