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