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