github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/make/tools/droiddoc/templates-ndk/assets/js/docs.js (about)

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