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