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  // Ignore if there's no search bar (some special pages have no header)
2542  if ($("#search-container").length < 1) return;
2543
2544  var hash = escape(location.hash.substr(1));
2545  var $matchingElement = $("#"+hash);
2546  // Sanity check that there's an element with that ID on the page
2547  if ($matchingElement.length) {
2548    // If the position of the target element is near the top of the page (<20px, where we expect it
2549    // to be because we need to move it down 60px to become in view), then move it down 60px
2550    if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2551      $(window).scrollTop($(window).scrollTop() - 60);
2552    }
2553  }
2554}
2555
2556// when an event on the browser history occurs (back, forward, load) requery hash and do search
2557$(window).hashchange( function(){
2558  // Ignore if there's no search bar (some special pages have no header)
2559  if ($("#search-container").length < 1) return;
2560
2561  // If the hash isn't a search query or there's an error in the query,
2562  // then adjust the scroll position to account for sticky header, then exit.
2563  if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2564    // If the results pane is open, close it.
2565    if (!$("#searchResults").is(":hidden")) {
2566      hideResults();
2567    }
2568    offsetScrollForSticky();
2569    return;
2570  }
2571
2572  // Otherwise, we have a search to do
2573  var query = decodeURI(getQuery(location.hash));
2574  searchControl.execute(query);
2575  $('#searchResults').slideDown('slow', setStickyTop);
2576  $("#search_autocomplete").focus();
2577  $(".search .close").removeClass("hide");
2578
2579  updateResultTitle(query);
2580});
2581
2582function updateResultTitle(query) {
2583  $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2584}
2585
2586// forcefully regain key-up event control (previously jacked by search api)
2587$("#search_autocomplete").keyup(function(event) {
2588  return search_changed(event, false, toRoot);
2589});
2590
2591// add event listeners to each tab so we can track the browser history
2592function addTabListeners() {
2593  var tabHeaders = $(".gsc-tabHeader");
2594  for (var i = 0; i < tabHeaders.length; i++) {
2595    $(tabHeaders[i]).attr("id",i).click(function() {
2596    /*
2597      // make a copy of the page numbers for the search left pane
2598      setTimeout(function() {
2599        // remove any residual page numbers
2600        $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
2601        // move the page numbers to the left position; make a clone,
2602        // because the element is drawn to the DOM only once
2603        // and because we're going to remove it (previous line),
2604        // we need it to be available to move again as the user navigates
2605        $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2606                        .clone().appendTo('#searchResults .gsc-tabsArea');
2607        }, 200);
2608      */
2609    });
2610  }
2611  setTimeout(function(){$(tabHeaders[0]).click()},200);
2612}
2613
2614// add analytics tracking events to each result link
2615function addResultClickListeners() {
2616  $("#searchResults a.gs-title").each(function(index, link) {
2617    // When user clicks enter for Google search results, track it
2618    $(link).click(function() {
2619      ga('send', 'event', 'Google Click', 'clicked: ' + $(this).text(),
2620                'from: ' + $("#search_autocomplete").val());
2621    });
2622  });
2623}
2624
2625
2626function getQuery(hash) {
2627  var queryParts = hash.split('=');
2628  return queryParts[1];
2629}
2630
2631/* returns the given string with all HTML brackets converted to entities
2632    TODO: move this to the site's JS library */
2633function escapeHTML(string) {
2634  return string.replace(/</g,"&lt;")
2635                .replace(/>/g,"&gt;");
2636}
2637
2638
2639
2640
2641
2642
2643
2644/* ######################################################## */
2645/* #################  JAVADOC REFERENCE ################### */
2646/* ######################################################## */
2647
2648/* Initialize some droiddoc stuff, but only if we're in the reference */
2649if (location.pathname.indexOf("/reference") == 0) {
2650  if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2651    && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2652    && !(location.pathname.indexOf("/reference/com/google") == 0)) {
2653    $(document).ready(function() {
2654      // init available apis based on user pref
2655      changeApiLevel();
2656      initSidenavHeightResize()
2657      });
2658  }
2659}
2660
2661var API_LEVEL_COOKIE = "api_level";
2662var minLevel = 1;
2663var maxLevel = 1;
2664
2665/******* SIDENAV DIMENSIONS ************/
2666
2667  function initSidenavHeightResize() {
2668    // Change the drag bar size to nicely fit the scrollbar positions
2669    var $dragBar = $(".ui-resizable-s");
2670    $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
2671
2672    $( "#resize-packages-nav" ).resizable({
2673      containment: "#nav-panels",
2674      handles: "s",
2675      alsoResize: "#packages-nav",
2676      resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2677      stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie  */
2678      });
2679
2680  }
2681
2682function updateSidenavFixedWidth() {
2683  if (!sticky) return;
2684  $('#devdoc-nav').css({
2685    'width' : $('#side-nav').css('width'),
2686    'margin' : $('#side-nav').css('margin')
2687  });
2688  $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
2689
2690  initSidenavHeightResize();
2691}
2692
2693function updateSidenavFullscreenWidth() {
2694  if (!sticky) return;
2695  $('#devdoc-nav').css({
2696    'width' : $('#side-nav').css('width'),
2697    'margin' : $('#side-nav').css('margin')
2698  });
2699  $('#devdoc-nav .totop').css({'left': 'inherit'});
2700
2701  initSidenavHeightResize();
2702}
2703
2704function buildApiLevelSelector() {
2705  maxLevel = SINCE_DATA.length;
2706  var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2707  userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2708
2709  minLevel = parseInt($("#doc-api-level").attr("class"));
2710  // Handle provisional api levels; the provisional level will always be the highest possible level
2711  // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2712  // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2713  if (isNaN(minLevel) && minLevel.length) {
2714    minLevel = maxLevel;
2715  }
2716  var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2717  for (var i = maxLevel-1; i >= 0; i--) {
2718    var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2719  //  if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2720    select.append(option);
2721  }
2722
2723  // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2724  var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2725  selectedLevelItem.setAttribute('selected',true);
2726}
2727
2728function changeApiLevel() {
2729  maxLevel = SINCE_DATA.length;
2730  var selectedLevel = maxLevel;
2731
2732  selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2733  toggleVisisbleApis(selectedLevel, "body");
2734
2735  var date = new Date();
2736  date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
2737  var expiration = date.toGMTString();
2738  writeCookie(API_LEVEL_COOKIE, selectedLevel, null, expiration);
2739
2740  if (selectedLevel < minLevel) {
2741    var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
2742    $("#naMessage").show().html("<div><p><strong>This " + thing
2743              + " requires API level " + minLevel + " or higher.</strong></p>"
2744              + "<p>This document is hidden because your selected API level for the documentation is "
2745              + selectedLevel + ". You can change the documentation API level with the selector "
2746              + "above the left navigation.</p>"
2747              + "<p>For more information about specifying the API level your app requires, "
2748              + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2749              + ">Supporting Different Platform Versions</a>.</p>"
2750              + "<input type='button' value='OK, make this page visible' "
2751              + "title='Change the API level to " + minLevel + "' "
2752              + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2753              + "</div>");
2754  } else {
2755    $("#naMessage").hide();
2756  }
2757}
2758
2759function toggleVisisbleApis(selectedLevel, context) {
2760  var apis = $(".api",context);
2761  apis.each(function(i) {
2762    var obj = $(this);
2763    var className = obj.attr("class");
2764    var apiLevelIndex = className.lastIndexOf("-")+1;
2765    var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2766    apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2767    var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2768    if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2769      return;
2770    }
2771    apiLevel = parseInt(apiLevel);
2772
2773    // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2774    var selectedLevelNum = parseInt(selectedLevel)
2775    var apiLevelNum = parseInt(apiLevel);
2776    if (isNaN(apiLevelNum)) {
2777        apiLevelNum = maxLevel;
2778    }
2779
2780    // Grey things out that aren't available and give a tooltip title
2781    if (apiLevelNum > selectedLevelNum) {
2782      obj.addClass("absent").attr("title","Requires API Level \""
2783            + apiLevel + "\" or higher. To reveal, change the target API level "
2784              + "above the left navigation.");
2785    }
2786    else obj.removeClass("absent").removeAttr("title");
2787  });
2788}
2789
2790
2791
2792
2793/* #################  SIDENAV TREE VIEW ################### */
2794
2795function new_node(me, mom, text, link, children_data, api_level)
2796{
2797  var node = new Object();
2798  node.children = Array();
2799  node.children_data = children_data;
2800  node.depth = mom.depth + 1;
2801
2802  node.li = document.createElement("li");
2803  mom.get_children_ul().appendChild(node.li);
2804
2805  node.label_div = document.createElement("div");
2806  node.label_div.className = "label";
2807  if (api_level != null) {
2808    $(node.label_div).addClass("api");
2809    $(node.label_div).addClass("api-level-"+api_level);
2810  }
2811  node.li.appendChild(node.label_div);
2812
2813  if (children_data != null) {
2814    node.expand_toggle = document.createElement("a");
2815    node.expand_toggle.href = "javascript:void(0)";
2816    node.expand_toggle.onclick = function() {
2817          if (node.expanded) {
2818            $(node.get_children_ul()).slideUp("fast");
2819            node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2820            node.expanded = false;
2821          } else {
2822            expand_node(me, node);
2823          }
2824       };
2825    node.label_div.appendChild(node.expand_toggle);
2826
2827    node.plus_img = document.createElement("img");
2828    node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2829    node.plus_img.className = "plus";
2830    node.plus_img.width = "8";
2831    node.plus_img.border = "0";
2832    node.expand_toggle.appendChild(node.plus_img);
2833
2834    node.expanded = false;
2835  }
2836
2837  var a = document.createElement("a");
2838  node.label_div.appendChild(a);
2839  node.label = document.createTextNode(text);
2840  a.appendChild(node.label);
2841  if (link) {
2842    a.href = me.toroot + link;
2843  } else {
2844    if (children_data != null) {
2845      a.className = "nolink";
2846      a.href = "javascript:void(0)";
2847      a.onclick = node.expand_toggle.onclick;
2848      // This next line shouldn't be necessary.  I'll buy a beer for the first
2849      // person who figures out how to remove this line and have the link
2850      // toggle shut on the first try. --joeo@android.com
2851      node.expanded = false;
2852    }
2853  }
2854
2855
2856  node.children_ul = null;
2857  node.get_children_ul = function() {
2858      if (!node.children_ul) {
2859        node.children_ul = document.createElement("ul");
2860        node.children_ul.className = "children_ul";
2861        node.children_ul.style.display = "none";
2862        node.li.appendChild(node.children_ul);
2863      }
2864      return node.children_ul;
2865    };
2866
2867  return node;
2868}
2869
2870
2871
2872
2873function expand_node(me, node)
2874{
2875  if (node.children_data && !node.expanded) {
2876    if (node.children_visited) {
2877      $(node.get_children_ul()).slideDown("fast");
2878    } else {
2879      get_node(me, node);
2880      if ($(node.label_div).hasClass("absent")) {
2881        $(node.get_children_ul()).addClass("absent");
2882      }
2883      $(node.get_children_ul()).slideDown("fast");
2884    }
2885    node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2886    node.expanded = true;
2887
2888    // perform api level toggling because new nodes are new to the DOM
2889    var selectedLevel = $("#apiLevelSelector option:selected").val();
2890    toggleVisisbleApis(selectedLevel, "#side-nav");
2891  }
2892}
2893
2894function get_node(me, mom)
2895{
2896  mom.children_visited = true;
2897  for (var i in mom.children_data) {
2898    var node_data = mom.children_data[i];
2899    mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2900        node_data[2], node_data[3]);
2901  }
2902}
2903
2904function this_page_relative(toroot)
2905{
2906  var full = document.location.pathname;
2907  var file = "";
2908  if (toroot.substr(0, 1) == "/") {
2909    if (full.substr(0, toroot.length) == toroot) {
2910      return full.substr(toroot.length);
2911    } else {
2912      // the file isn't under toroot.  Fail.
2913      return null;
2914    }
2915  } else {
2916    if (toroot != "./") {
2917      toroot = "./" + toroot;
2918    }
2919    do {
2920      if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
2921        var pos = full.lastIndexOf("/");
2922        file = full.substr(pos) + file;
2923        full = full.substr(0, pos);
2924        toroot = toroot.substr(0, toroot.length-3);
2925      }
2926    } while (toroot != "" && toroot != "/");
2927    return file.substr(1);
2928  }
2929}
2930
2931function find_page(url, data)
2932{
2933  var nodes = data;
2934  var result = null;
2935  for (var i in nodes) {
2936    var d = nodes[i];
2937    if (d[1] == url) {
2938      return new Array(i);
2939    }
2940    else if (d[2] != null) {
2941      result = find_page(url, d[2]);
2942      if (result != null) {
2943        return (new Array(i).concat(result));
2944      }
2945    }
2946  }
2947  return null;
2948}
2949
2950function init_default_navtree(toroot) {
2951  // load json file for navtree data
2952  $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
2953      // when the file is loaded, initialize the tree
2954      if(jqxhr.status === 200) {
2955          init_navtree("tree-list", toroot, NAVTREE_DATA);
2956      }
2957  });
2958
2959  // perform api level toggling because because the whole tree is new to the DOM
2960  var selectedLevel = $("#apiLevelSelector option:selected").val();
2961  toggleVisisbleApis(selectedLevel, "#side-nav");
2962}
2963
2964function init_navtree(navtree_id, toroot, root_nodes)
2965{
2966  var me = new Object();
2967  me.toroot = toroot;
2968  me.node = new Object();
2969
2970  me.node.li = document.getElementById(navtree_id);
2971  me.node.children_data = root_nodes;
2972  me.node.children = new Array();
2973  me.node.children_ul = document.createElement("ul");
2974  me.node.get_children_ul = function() { return me.node.children_ul; };
2975  //me.node.children_ul.className = "children_ul";
2976  me.node.li.appendChild(me.node.children_ul);
2977  me.node.depth = 0;
2978
2979  get_node(me, me.node);
2980
2981  me.this_page = this_page_relative(toroot);
2982  me.breadcrumbs = find_page(me.this_page, root_nodes);
2983  if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
2984    var mom = me.node;
2985    for (var i in me.breadcrumbs) {
2986      var j = me.breadcrumbs[i];
2987      mom = mom.children[j];
2988      expand_node(me, mom);
2989    }
2990    mom.label_div.className = mom.label_div.className + " selected";
2991    addLoadEvent(function() {
2992      scrollIntoView("nav-tree");
2993      });
2994  }
2995}
2996
2997
2998
2999
3000
3001
3002
3003
3004/* TODO: eliminate redundancy with non-google functions */
3005function init_google_navtree(navtree_id, toroot, root_nodes)
3006{
3007  var me = new Object();
3008  me.toroot = toroot;
3009  me.node = new Object();
3010
3011  me.node.li = document.getElementById(navtree_id);
3012  me.node.children_data = root_nodes;
3013  me.node.children = new Array();
3014  me.node.children_ul = document.createElement("ul");
3015  me.node.get_children_ul = function() { return me.node.children_ul; };
3016  //me.node.children_ul.className = "children_ul";
3017  me.node.li.appendChild(me.node.children_ul);
3018  me.node.depth = 0;
3019
3020  get_google_node(me, me.node);
3021}
3022
3023function new_google_node(me, mom, text, link, children_data, api_level)
3024{
3025  var node = new Object();
3026  var child;
3027  node.children = Array();
3028  node.children_data = children_data;
3029  node.depth = mom.depth + 1;
3030  node.get_children_ul = function() {
3031      if (!node.children_ul) {
3032        node.children_ul = document.createElement("ul");
3033        node.children_ul.className = "tree-list-children";
3034        node.li.appendChild(node.children_ul);
3035      }
3036      return node.children_ul;
3037    };
3038  node.li = document.createElement("li");
3039
3040  mom.get_children_ul().appendChild(node.li);
3041
3042
3043  if(link) {
3044    child = document.createElement("a");
3045
3046  }
3047  else {
3048    child = document.createElement("span");
3049    child.className = "tree-list-subtitle";
3050
3051  }
3052  if (children_data != null) {
3053    node.li.className="nav-section";
3054    node.label_div = document.createElement("div");
3055    node.label_div.className = "nav-section-header-ref";
3056    node.li.appendChild(node.label_div);
3057    get_google_node(me, node);
3058    node.label_div.appendChild(child);
3059  }
3060  else {
3061    node.li.appendChild(child);
3062  }
3063  if(link) {
3064    child.href = me.toroot + link;
3065  }
3066  node.label = document.createTextNode(text);
3067  child.appendChild(node.label);
3068
3069  node.children_ul = null;
3070
3071  return node;
3072}
3073
3074function get_google_node(me, mom)
3075{
3076  mom.children_visited = true;
3077  var linkText;
3078  for (var i in mom.children_data) {
3079    var node_data = mom.children_data[i];
3080    linkText = node_data[0];
3081
3082    if(linkText.match("^"+"com.google.android")=="com.google.android"){
3083      linkText = linkText.substr(19, linkText.length);
3084    }
3085      mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3086          node_data[2], node_data[3]);
3087  }
3088}
3089
3090
3091
3092
3093
3094
3095/****** NEW version of script to build google and sample navs dynamically ******/
3096// TODO: update Google reference docs to tolerate this new implementation
3097
3098var NODE_NAME = 0;
3099var NODE_HREF = 1;
3100var NODE_GROUP = 2;
3101var NODE_TAGS = 3;
3102var NODE_CHILDREN = 4;
3103
3104function init_google_navtree2(navtree_id, data)
3105{
3106  var $containerUl = $("#"+navtree_id);
3107  for (var i in data) {
3108    var node_data = data[i];
3109    $containerUl.append(new_google_node2(node_data));
3110  }
3111
3112  // Make all third-generation list items 'sticky' to prevent them from collapsing
3113  $containerUl.find('li li li.nav-section').addClass('sticky');
3114
3115  initExpandableNavItems("#"+navtree_id);
3116}
3117
3118function new_google_node2(node_data)
3119{
3120  var linkText = node_data[NODE_NAME];
3121  if(linkText.match("^"+"com.google.android")=="com.google.android"){
3122    linkText = linkText.substr(19, linkText.length);
3123  }
3124  var $li = $('<li>');
3125  var $a;
3126  if (node_data[NODE_HREF] != null) {
3127    $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3128        + linkText + '</a>');
3129  } else {
3130    $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3131        + linkText + '/</a>');
3132  }
3133  var $childUl = $('<ul>');
3134  if (node_data[NODE_CHILDREN] != null) {
3135    $li.addClass("nav-section");
3136    $a = $('<div class="nav-section-header">').append($a);
3137    if (node_data[NODE_HREF] == null) $a.addClass('empty');
3138
3139    for (var i in node_data[NODE_CHILDREN]) {
3140      var child_node_data = node_data[NODE_CHILDREN][i];
3141      $childUl.append(new_google_node2(child_node_data));
3142    }
3143    $li.append($childUl);
3144  }
3145  $li.prepend($a);
3146
3147  return $li;
3148}
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160function showGoogleRefTree() {
3161  init_default_google_navtree(toRoot);
3162  init_default_gcm_navtree(toRoot);
3163}
3164
3165function init_default_google_navtree(toroot) {
3166  // load json file for navtree data
3167  $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3168      // when the file is loaded, initialize the tree
3169      if(jqxhr.status === 200) {
3170          init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3171          highlightSidenav();
3172          resizeNav();
3173      }
3174  });
3175}
3176
3177function init_default_gcm_navtree(toroot) {
3178  // load json file for navtree data
3179  $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3180      // when the file is loaded, initialize the tree
3181      if(jqxhr.status === 200) {
3182          init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3183          highlightSidenav();
3184          resizeNav();
3185      }
3186  });
3187}
3188
3189function showSamplesRefTree() {
3190  init_default_samples_navtree(toRoot);
3191}
3192
3193function init_default_samples_navtree(toroot) {
3194  // load json file for navtree data
3195  $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3196      // when the file is loaded, initialize the tree
3197      if(jqxhr.status === 200) {
3198          // hack to remove the "about the samples" link then put it back in
3199          // after we nuke the list to remove the dummy static list of samples
3200          var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3201          $("#nav.samples-nav").empty();
3202          $("#nav.samples-nav").append($firstLi);
3203
3204          init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
3205          highlightSidenav();
3206          resizeNav();
3207          if ($("#jd-content #samples").length) {
3208            showSamples();
3209          }
3210      }
3211  });
3212}
3213
3214/* TOGGLE INHERITED MEMBERS */
3215
3216/* Toggle an inherited class (arrow toggle)
3217 * @param linkObj  The link that was clicked.
3218 * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
3219 *                'null' to simply toggle.
3220 */
3221function toggleInherited(linkObj, expand) {
3222    var base = linkObj.getAttribute("id");
3223    var list = document.getElementById(base + "-list");
3224    var summary = document.getElementById(base + "-summary");
3225    var trigger = document.getElementById(base + "-trigger");
3226    var a = $(linkObj);
3227    if ( (expand == null && a.hasClass("closed")) || expand ) {
3228        list.style.display = "none";
3229        summary.style.display = "block";
3230        trigger.src = toRoot + "assets/images/triangle-opened.png";
3231        a.removeClass("closed");
3232        a.addClass("opened");
3233    } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3234        list.style.display = "block";
3235        summary.style.display = "none";
3236        trigger.src = toRoot + "assets/images/triangle-closed.png";
3237        a.removeClass("opened");
3238        a.addClass("closed");
3239    }
3240    return false;
3241}
3242
3243/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3244 * @param linkObj  The link that was clicked.
3245 * @param expand  'true' to ensure it's expanded. 'false' to ensure it's closed.
3246 *                'null' to simply toggle.
3247 */
3248function toggleAllInherited(linkObj, expand) {
3249  var a = $(linkObj);
3250  var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3251  var expandos = $(".jd-expando-trigger", table);
3252  if ( (expand == null && a.text() == "[Expand]") || expand ) {
3253    expandos.each(function(i) {
3254      toggleInherited(this, true);
3255    });
3256    a.text("[Collapse]");
3257  } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3258    expandos.each(function(i) {
3259      toggleInherited(this, false);
3260    });
3261    a.text("[Expand]");
3262  }
3263  return false;
3264}
3265
3266/* Toggle all inherited members in the class (link in the class title)
3267 */
3268function toggleAllClassInherited() {
3269  var a = $("#toggleAllClassInherited"); // get toggle link from class title
3270  var toggles = $(".toggle-all", $("#body-content"));
3271  if (a.text() == "[Expand All]") {
3272    toggles.each(function(i) {
3273      toggleAllInherited(this, true);
3274    });
3275    a.text("[Collapse All]");
3276  } else {
3277    toggles.each(function(i) {
3278      toggleAllInherited(this, false);
3279    });
3280    a.text("[Expand All]");
3281  }
3282  return false;
3283}
3284
3285/* Expand all inherited members in the class. Used when initiating page search */
3286function ensureAllInheritedExpanded() {
3287  var toggles = $(".toggle-all", $("#body-content"));
3288  toggles.each(function(i) {
3289    toggleAllInherited(this, true);
3290  });
3291  $("#toggleAllClassInherited").text("[Collapse All]");
3292}
3293
3294
3295/* HANDLE KEY EVENTS
3296 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3297 */
3298var agent = navigator['userAgent'].toLowerCase();
3299var mac = agent.indexOf("macintosh") != -1;
3300
3301$(document).keydown( function(e) {
3302var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3303  if (control && e.which == 70) {  // 70 is "F"
3304    ensureAllInheritedExpanded();
3305  }
3306});
3307
3308
3309
3310
3311
3312
3313/* On-demand functions */
3314
3315/** Move sample code line numbers out of PRE block and into non-copyable column */
3316function initCodeLineNumbers() {
3317  var numbers = $("#codesample-block a.number");
3318  if (numbers.length) {
3319    $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3320  }
3321
3322  $(document).ready(function() {
3323    // select entire line when clicked
3324    $("span.code-line").click(function() {
3325      if (!shifted) {
3326        selectText(this);
3327      }
3328    });
3329    // invoke line link on double click
3330    $(".code-line").dblclick(function() {
3331      document.location.hash = $(this).attr('id');
3332    });
3333    // highlight the line when hovering on the number
3334    $("#codesample-line-numbers a.number").mouseover(function() {
3335      var id = $(this).attr('href');
3336      $(id).css('background','#e7e7e7');
3337    });
3338    $("#codesample-line-numbers a.number").mouseout(function() {
3339      var id = $(this).attr('href');
3340      $(id).css('background','none');
3341    });
3342  });
3343}
3344
3345// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3346var shifted = false;
3347$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3348
3349// courtesy of jasonedelman.com
3350function selectText(element) {
3351    var doc = document
3352        , range, selection
3353    ;
3354    if (doc.body.createTextRange) { //ms
3355        range = doc.body.createTextRange();
3356        range.moveToElementText(element);
3357        range.select();
3358    } else if (window.getSelection) { //all others
3359        selection = window.getSelection();
3360        range = doc.createRange();
3361        range.selectNodeContents(element);
3362        selection.removeAllRanges();
3363        selection.addRange(range);
3364    }
3365}
3366
3367
3368
3369
3370/** Display links and other information about samples that match the
3371    group specified by the URL */
3372function showSamples() {
3373  var group = $("#samples").attr('class');
3374  $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3375
3376  var $ul = $("<ul>");
3377  $selectedLi = $("#nav li.selected");
3378
3379  $selectedLi.children("ul").children("li").each(function() {
3380      var $li = $("<li>").append($(this).find("a").first().clone());
3381      $ul.append($li);
3382  });
3383
3384  $("#samples").append($ul);
3385
3386}
3387
3388
3389
3390/* ########################################################## */
3391/* ###################  RESOURCE CARDS  ##################### */
3392/* ########################################################## */
3393
3394/** Handle resource queries, collections, and grids (sections). Requires
3395    jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3396
3397(function() {
3398  // Prevent the same resource from being loaded more than once per page.
3399  var addedPageResources = {};
3400
3401  $(document).ready(function() {
3402    $('.resource-widget').each(function() {
3403      initResourceWidget(this);
3404    });
3405
3406    /* Pass the line height to ellipsisfade() to adjust the height of the
3407    text container to show the max number of lines possible, without
3408    showing lines that are cut off. This works with the css ellipsis
3409    classes to fade last text line and apply an ellipsis char. */
3410
3411    //card text currently uses 15px line height.
3412    var lineHeight = 15;
3413    $('.card-info .text').ellipsisfade(lineHeight);
3414  });
3415
3416  /*
3417    Three types of resource layouts:
3418    Flow - Uses a fixed row-height flow using float left style.
3419    Carousel - Single card slideshow all same dimension absolute.
3420    Stack - Uses fixed columns and flexible element height.
3421  */
3422  function initResourceWidget(widget) {
3423    var $widget = $(widget);
3424    var isFlow = $widget.hasClass('resource-flow-layout'),
3425        isCarousel = $widget.hasClass('resource-carousel-layout'),
3426        isStack = $widget.hasClass('resource-stack-layout');
3427
3428    // find size of widget by pulling out its class name
3429    var sizeCols = 1;
3430    var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
3431    if (m) {
3432      sizeCols = parseInt(m[1], 10);
3433    }
3434
3435    var opts = {
3436      cardSizes: ($widget.data('cardsizes') || '').split(','),
3437      maxResults: parseInt($widget.data('maxresults') || '100', 10),
3438      itemsPerPage: $widget.data('itemsperpage'),
3439      sortOrder: $widget.data('sortorder'),
3440      query: $widget.data('query'),
3441      section: $widget.data('section'),
3442      sizeCols: sizeCols,
3443      /* Added by LFL 6/6/14 */
3444      resourceStyle: $widget.data('resourcestyle') || 'card',
3445      stackSort: $widget.data('stacksort') || 'true'
3446    };
3447
3448    // run the search for the set of resources to show
3449
3450    var resources = buildResourceList(opts);
3451
3452    if (isFlow) {
3453      drawResourcesFlowWidget($widget, opts, resources);
3454    } else if (isCarousel) {
3455      drawResourcesCarouselWidget($widget, opts, resources);
3456    } else if (isStack) {
3457      /* Looks like this got removed and is not used, so repurposing for the
3458          homepage style layout.
3459          Modified by LFL 6/6/14
3460      */
3461      //var sections = buildSectionList(opts);
3462      opts['numStacks'] = $widget.data('numstacks');
3463      drawResourcesStackWidget($widget, opts, resources/*, sections*/);
3464    }
3465  }
3466
3467  /* Initializes a Resource Carousel Widget */
3468  function drawResourcesCarouselWidget($widget, opts, resources) {
3469    $widget.empty();
3470    var plusone = true; //always show plusone on carousel
3471
3472    $widget.addClass('resource-card slideshow-container')
3473      .append($('<a>').addClass('slideshow-prev').text('Prev'))
3474      .append($('<a>').addClass('slideshow-next').text('Next'));
3475
3476    var css = { 'width': $widget.width() + 'px',
3477                'height': $widget.height() + 'px' };
3478
3479    var $ul = $('<ul>');
3480
3481    for (var i = 0; i < resources.length; ++i) {
3482      var $card = $('<a>')
3483        .attr('href', cleanUrl(resources[i].url))
3484        .decorateResourceCard(resources[i],plusone);
3485
3486      $('<li>').css(css)
3487          .append($card)
3488          .appendTo($ul);
3489    }
3490
3491    $('<div>').addClass('frame')
3492      .append($ul)
3493      .appendTo($widget);
3494
3495    $widget.dacSlideshow({
3496      auto: true,
3497      btnPrev: '.slideshow-prev',
3498      btnNext: '.slideshow-next'
3499    });
3500  };
3501
3502  /* Initializes a Resource Card Stack Widget (column-based layout)
3503     Modified by LFL 6/6/14
3504   */
3505  function drawResourcesStackWidget($widget, opts, resources, sections) {
3506    // Don't empty widget, grab all items inside since they will be the first
3507    // items stacked, followed by the resource query
3508    var plusone = true; //by default show plusone on section cards
3509    var cards = $widget.find('.resource-card').detach().toArray();
3510    var numStacks = opts.numStacks || 1;
3511    var $stacks = [];
3512    var urlString;
3513
3514    for (var i = 0; i < numStacks; ++i) {
3515      $stacks[i] = $('<div>').addClass('resource-card-stack')
3516          .appendTo($widget);
3517    }
3518
3519    var sectionResources = [];
3520
3521    // Extract any subsections that are actually resource cards
3522    if (sections) {
3523      for (var i = 0; i < sections.length; ++i) {
3524        if (!sections[i].sections || !sections[i].sections.length) {
3525          // Render it as a resource card
3526          sectionResources.push(
3527            $('<a>')
3528              .addClass('resource-card section-card')
3529              .attr('href', cleanUrl(sections[i].resource.url))
3530              .decorateResourceCard(sections[i].resource,plusone)[0]
3531          );
3532
3533        } else {
3534          cards.push(
3535            $('<div>')
3536              .addClass('resource-card section-card-menu')
3537              .decorateResourceSection(sections[i],plusone)[0]
3538          );
3539        }
3540      }
3541    }
3542
3543    cards = cards.concat(sectionResources);
3544
3545    for (var i = 0; i < resources.length; ++i) {
3546      var $card = createResourceElement(resources[i], opts);
3547
3548      if (opts.resourceStyle.indexOf('related') > -1) {
3549        $card.addClass('related-card');
3550      }
3551
3552      cards.push($card[0]);
3553    }
3554
3555    if (opts.stackSort != 'false') {
3556      for (var i = 0; i < cards.length; ++i) {
3557        // Find the stack with the shortest height, but give preference to
3558        // left to right order.
3559        var minHeight = $stacks[0].height();
3560        var minIndex = 0;
3561
3562        for (var j = 1; j < numStacks; ++j) {
3563          var height = $stacks[j].height();
3564          if (height < minHeight - 45) {
3565            minHeight = height;
3566            minIndex = j;
3567          }
3568        }
3569
3570        $stacks[minIndex].append($(cards[i]));
3571      }
3572    }
3573
3574  };
3575
3576  /*
3577    Create a resource card using the given resource object and a list of html
3578     configured options. Returns a jquery object containing the element.
3579  */
3580  function createResourceElement(resource, opts, plusone) {
3581    var $el;
3582
3583    // The difference here is that generic cards are not entirely clickable
3584    // so its a div instead of an a tag, also the generic one is not given
3585    // the resource-card class so it appears with a transparent background
3586    // and can be styled in whatever way the css setup.
3587    if (opts.resourceStyle == 'generic') {
3588      $el = $('<div>')
3589        .addClass('resource')
3590        .attr('href', cleanUrl(resource.url))
3591        .decorateResource(resource, opts);
3592    } else {
3593      var cls = 'resource resource-card';
3594
3595      $el = $('<a>')
3596        .addClass(cls)
3597        .attr('href', cleanUrl(resource.url))
3598        .decorateResourceCard(resource, plusone);
3599    }
3600
3601    return $el;
3602  }
3603
3604  /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3605  function drawResourcesFlowWidget($widget, opts, resources) {
3606    $widget.empty();
3607    var cardSizes = opts.cardSizes || ['6x6'];
3608    var i = 0, j = 0;
3609    var plusone = true; // by default show plusone on resource cards
3610
3611    while (i < resources.length) {
3612      var cardSize = cardSizes[j++ % cardSizes.length];
3613      cardSize = cardSize.replace(/^\s+|\s+$/,'');
3614      // Some card sizes do not get a plusone button, such as where space is constrained
3615      // or for cards commonly embedded in docs (to improve overall page speed).
3616      plusone = !((cardSize == "6x2") || (cardSize == "6x3") ||
3617                  (cardSize == "9x2") || (cardSize == "9x3") ||
3618                  (cardSize == "12x2") || (cardSize == "12x3"));
3619
3620      // A stack has a third dimension which is the number of stacked items
3621      var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3622      var stackCount = 0;
3623      var $stackDiv = null;
3624
3625      if (isStack) {
3626        // Create a stack container which should have the dimensions defined
3627        // by the product of the items inside.
3628        $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
3629            + 'x' + isStack[2] * isStack[3]) .appendTo($widget);
3630      }
3631
3632      // Build each stack item or just a single item
3633      do {
3634        var resource = resources[i];
3635
3636        var $card = createResourceElement(resources[i], opts, plusone);
3637
3638        $card.addClass('resource-card-' + cardSize +
3639          ' resource-card-' + resource.type);
3640
3641        if (isStack) {
3642          $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3643          if (++stackCount == parseInt(isStack[3])) {
3644            $card.addClass('resource-card-row-stack-last');
3645            stackCount = 0;
3646          }
3647        } else {
3648          stackCount = 0;
3649        }
3650
3651        $card.appendTo($stackDiv || $widget);
3652
3653      } while (++i < resources.length && stackCount > 0);
3654    }
3655  }
3656
3657  /* Build a site map of resources using a section as a root. */
3658  function buildSectionList(opts) {
3659    if (opts.section && SECTION_BY_ID[opts.section]) {
3660      return SECTION_BY_ID[opts.section].sections || [];
3661    }
3662    return [];
3663  }
3664
3665  function buildResourceList(opts) {
3666    var maxResults = opts.maxResults || 100;
3667
3668    var query = opts.query || '';
3669    var expressions = parseResourceQuery(query);
3670    var addedResourceIndices = {};
3671    var results = [];
3672
3673    for (var i = 0; i < expressions.length; i++) {
3674      var clauses = expressions[i];
3675
3676      // build initial set of resources from first clause
3677      var firstClause = clauses[0];
3678      var resources = [];
3679      switch (firstClause.attr) {
3680        case 'type':
3681          resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3682          break;
3683        case 'lang':
3684          resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3685          break;
3686        case 'tag':
3687          resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3688          break;
3689        case 'collection':
3690          var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3691          resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3692          break;
3693        case 'section':
3694          var urls = SITE_MAP[firstClause.value].sections || [];
3695          resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3696          break;
3697      }
3698      // console.log(firstClause.attr + ':' + firstClause.value);
3699      resources = resources || [];
3700
3701      // use additional clauses to filter corpus
3702      if (clauses.length > 1) {
3703        var otherClauses = clauses.slice(1);
3704        resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3705      }
3706
3707      // filter out resources already added
3708      if (i > 1) {
3709        resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3710      }
3711
3712      // add to list of already added indices
3713      for (var j = 0; j < resources.length; j++) {
3714        // console.log(resources[j].title);
3715        addedResourceIndices[resources[j].index] = 1;
3716      }
3717
3718      // concat to final results list
3719      results = results.concat(resources);
3720    }
3721
3722    if (opts.sortOrder && results.length) {
3723      var attr = opts.sortOrder;
3724
3725      if (opts.sortOrder == 'random') {
3726        var i = results.length, j, temp;
3727        while (--i) {
3728          j = Math.floor(Math.random() * (i + 1));
3729          temp = results[i];
3730          results[i] = results[j];
3731          results[j] = temp;
3732        }
3733      } else {
3734        var desc = attr.charAt(0) == '-';
3735        if (desc) {
3736          attr = attr.substring(1);
3737        }
3738        results = results.sort(function(x,y) {
3739          return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3740        });
3741      }
3742    }
3743
3744    results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3745    results = results.slice(0, maxResults);
3746
3747    for (var j = 0; j < results.length; ++j) {
3748      addedPageResources[results[j].index] = 1;
3749    }
3750
3751    return results;
3752  }
3753
3754
3755  function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3756    return function(resource) {
3757      return !addedResourceIndices[resource.index];
3758    };
3759  }
3760
3761
3762  function getResourceMatchesClausesFilter(clauses) {
3763    return function(resource) {
3764      return doesResourceMatchClauses(resource, clauses);
3765    };
3766  }
3767
3768
3769  function doesResourceMatchClauses(resource, clauses) {
3770    for (var i = 0; i < clauses.length; i++) {
3771      var map;
3772      switch (clauses[i].attr) {
3773        case 'type':
3774          map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3775          break;
3776        case 'lang':
3777          map = IS_RESOURCE_IN_LANG[clauses[i].value];
3778          break;
3779        case 'tag':
3780          map = IS_RESOURCE_TAGGED[clauses[i].value];
3781          break;
3782      }
3783
3784      if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3785        return clauses[i].negative;
3786      }
3787    }
3788    return true;
3789  }
3790
3791  function cleanUrl(url)
3792  {
3793    if (url && url.indexOf('//') === -1) {
3794      url = toRoot + url;
3795    }
3796
3797    return url;
3798  }
3799
3800
3801  function parseResourceQuery(query) {
3802    // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3803    var expressions = [];
3804    var expressionStrs = query.split(',') || [];
3805    for (var i = 0; i < expressionStrs.length; i++) {
3806      var expr = expressionStrs[i] || '';
3807
3808      // Break expression into clauses (clause e.g. 'tag:foo')
3809      var clauses = [];
3810      var clauseStrs = expr.split(/(?=[\+\-])/);
3811      for (var j = 0; j < clauseStrs.length; j++) {
3812        var clauseStr = clauseStrs[j] || '';
3813
3814        // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3815        var parts = clauseStr.split(':');
3816        var clause = {};
3817
3818        clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3819        if (clause.attr) {
3820          if (clause.attr.charAt(0) == '+') {
3821            clause.attr = clause.attr.substring(1);
3822          } else if (clause.attr.charAt(0) == '-') {
3823            clause.negative = true;
3824            clause.attr = clause.attr.substring(1);
3825          }
3826        }
3827
3828        if (parts.length > 1) {
3829          clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3830        }
3831
3832        clauses.push(clause);
3833      }
3834
3835      if (!clauses.length) {
3836        continue;
3837      }
3838
3839      expressions.push(clauses);
3840    }
3841
3842    return expressions;
3843  }
3844})();
3845
3846(function($) {
3847
3848  /*
3849    Utility method for creating dom for the description area of a card.
3850    Used in decorateResourceCard and decorateResource.
3851  */
3852  function buildResourceCardDescription(resource, plusone) {
3853    var $description = $('<div>').addClass('description ellipsis');
3854
3855    $description.append($('<div>').addClass('text').html(resource.summary));
3856
3857    if (resource.cta) {
3858      $description.append($('<a>').addClass('cta').html(resource.cta));
3859    }
3860
3861    if (plusone) {
3862      var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
3863        "//developer.android.com/" + resource.url;
3864
3865      $description.append($('<div>').addClass('util')
3866        .append($('<div>').addClass('g-plusone')
3867          .attr('data-size', 'small')
3868          .attr('data-align', 'right')
3869          .attr('data-href', plusurl)));
3870    }
3871
3872    return $description;
3873  }
3874
3875
3876  /* Simple jquery function to create dom for a standard resource card */
3877  $.fn.decorateResourceCard = function(resource,plusone) {
3878    var section = resource.group || resource.type;
3879    var imgUrl = resource.image ||
3880      'assets/images/resource-card-default-android.jpg';
3881
3882    if (imgUrl.indexOf('//') === -1) {
3883      imgUrl = toRoot + imgUrl;
3884    }
3885
3886    $('<div>').addClass('card-bg')
3887      .css('background-image', 'url(' + (imgUrl || toRoot +
3888        'assets/images/resource-card-default-android.jpg') + ')')
3889      .appendTo(this);
3890
3891    $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
3892      .append($('<div>').addClass('section').text(section))
3893      .append($('<div>').addClass('title').html(resource.title))
3894      .append(buildResourceCardDescription(resource, plusone))
3895      .appendTo(this);
3896
3897    return this;
3898  };
3899
3900  /* Simple jquery function to create dom for a resource section card (menu) */
3901  $.fn.decorateResourceSection = function(section,plusone) {
3902    var resource = section.resource;
3903    //keep url clean for matching and offline mode handling
3904    var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
3905    var $base = $('<a>')
3906        .addClass('card-bg')
3907        .attr('href', resource.url)
3908        .append($('<div>').addClass('card-section-icon')
3909          .append($('<div>').addClass('icon'))
3910          .append($('<div>').addClass('section').html(resource.title)))
3911      .appendTo(this);
3912
3913    var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
3914
3915    if (section.sections && section.sections.length) {
3916      // Recurse the section sub-tree to find a resource image.
3917      var stack = [section];
3918
3919      while (stack.length) {
3920        if (stack[0].resource.image) {
3921          $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
3922          break;
3923        }
3924
3925        if (stack[0].sections) {
3926          stack = stack.concat(stack[0].sections);
3927        }
3928
3929        stack.shift();
3930      }
3931
3932      var $ul = $('<ul>')
3933        .appendTo($cardInfo);
3934
3935      var max = section.sections.length > 3 ? 3 : section.sections.length;
3936
3937      for (var i = 0; i < max; ++i) {
3938
3939        var subResource = section.sections[i];
3940        if (!plusone) {
3941          $('<li>')
3942            .append($('<a>').attr('href', subResource.url)
3943              .append($('<div>').addClass('title').html(subResource.title))
3944              .append($('<div>').addClass('description ellipsis')
3945                .append($('<div>').addClass('text').html(subResource.summary))
3946                .append($('<div>').addClass('util'))))
3947          .appendTo($ul);
3948        } else {
3949          $('<li>')
3950            .append($('<a>').attr('href', subResource.url)
3951              .append($('<div>').addClass('title').html(subResource.title))
3952              .append($('<div>').addClass('description ellipsis')
3953                .append($('<div>').addClass('text').html(subResource.summary))
3954                .append($('<div>').addClass('util')
3955                  .append($('<div>').addClass('g-plusone')
3956                    .attr('data-size', 'small')
3957                    .attr('data-align', 'right')
3958                    .attr('data-href', resource.url)))))
3959          .appendTo($ul);
3960        }
3961      }
3962
3963      // Add a more row
3964      if (max < section.sections.length) {
3965        $('<li>')
3966          .append($('<a>').attr('href', resource.url)
3967            .append($('<div>')
3968              .addClass('title')
3969              .text('More')))
3970        .appendTo($ul);
3971      }
3972    } else {
3973      // No sub-resources, just render description?
3974    }
3975
3976    return this;
3977  };
3978
3979
3980
3981
3982  /* Render other types of resource styles that are not cards. */
3983  $.fn.decorateResource = function(resource, opts) {
3984    var imgUrl = resource.image ||
3985      'assets/images/resource-card-default-android.jpg';
3986    var linkUrl = resource.url;
3987
3988    if (imgUrl.indexOf('//') === -1) {
3989      imgUrl = toRoot + imgUrl;
3990    }
3991
3992    if (linkUrl && linkUrl.indexOf('//') === -1) {
3993      linkUrl = toRoot + linkUrl;
3994    }
3995
3996    $(this).append(
3997      $('<div>').addClass('image')
3998        .css('background-image', 'url(' + imgUrl + ')'),
3999      $('<div>').addClass('info').append(
4000        $('<h4>').addClass('title').html(resource.title),
4001        $('<p>').addClass('summary').html(resource.summary),
4002        $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4003      )
4004    );
4005
4006    return this;
4007  };
4008})(jQuery);
4009
4010
4011/* Calculate the vertical area remaining */
4012(function($) {
4013    $.fn.ellipsisfade= function(lineHeight) {
4014        this.each(function() {
4015            // get element text
4016            var $this = $(this);
4017            var remainingHeight = $this.parent().parent().height();
4018            $this.parent().siblings().each(function ()
4019            {
4020              if ($(this).is(":visible")) {
4021                var h = $(this).height();
4022                remainingHeight = remainingHeight - h;
4023              }
4024            });
4025
4026            adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4027            $this.parent().css({'height': adjustedRemainingHeight});
4028            $this.css({'height': "auto"});
4029        });
4030
4031        return this;
4032    };
4033}) (jQuery);
4034
4035/*
4036  Fullscreen Carousel
4037
4038  The following allows for an area at the top of the page that takes over the
4039  entire browser height except for its top offset and an optional bottom
4040  padding specified as a data attribute.
4041
4042  HTML:
4043
4044  <div class="fullscreen-carousel">
4045    <div class="fullscreen-carousel-content">
4046      <!-- content here -->
4047    </div>
4048    <div class="fullscreen-carousel-content">
4049      <!-- content here -->
4050    </div>
4051
4052    etc ...
4053
4054  </div>
4055
4056  Control over how the carousel takes over the screen can mostly be defined in
4057  a css file. Setting min-height on the .fullscreen-carousel-content elements
4058  will prevent them from shrinking to far vertically when the browser is very
4059  short, and setting max-height on the .fullscreen-carousel itself will prevent
4060  the area from becoming to long in the case that the browser is stretched very
4061  tall.
4062
4063  There is limited functionality for having multiple sections since that request
4064  was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4065  scroll between multiple content areas.
4066*/
4067
4068(function() {
4069  $(document).ready(function() {
4070    $('.fullscreen-carousel').each(function() {
4071      initWidget(this);
4072    });
4073  });
4074
4075  function initWidget(widget) {
4076    var $widget = $(widget);
4077
4078    var topOffset = $widget.offset().top;
4079    var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4080    var maxHeight = 0;
4081    var minHeight = 0;
4082    var $content = $widget.find('.fullscreen-carousel-content');
4083    var $nextArrow = $widget.find('.next-arrow');
4084    var $prevArrow = $widget.find('.prev-arrow');
4085    var $curSection = $($content[0]);
4086
4087    if ($content.length <= 1) {
4088      $nextArrow.hide();
4089      $prevArrow.hide();
4090    } else {
4091      $nextArrow.click(function() {
4092        var index = ($content.index($curSection) + 1);
4093        $curSection.hide();
4094        $curSection = $($content[index >= $content.length ? 0 : index]);
4095        $curSection.show();
4096      });
4097
4098      $prevArrow.click(function() {
4099        var index = ($content.index($curSection) - 1);
4100        $curSection.hide();
4101        $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4102        $curSection.show();
4103      });
4104    }
4105
4106    // Just hide all content sections except first.
4107    $content.each(function(index) {
4108      if ($(this).height() > minHeight) minHeight = $(this).height();
4109      $(this).css({position: 'absolute',  display: index > 0 ? 'none' : ''});
4110    });
4111
4112    // Register for changes to window size, and trigger.
4113    $(window).resize(resizeWidget);
4114    resizeWidget();
4115
4116    function resizeWidget() {
4117      var height = $(window).height() - topOffset - padBottom;
4118      $widget.width($(window).width());
4119      $widget.height(height < minHeight ? minHeight :
4120        (maxHeight && height > maxHeight ? maxHeight : height));
4121    }
4122  }
4123})();
4124
4125
4126
4127
4128
4129/*
4130  Tab Carousel
4131
4132  The following allows tab widgets to be installed via the html below. Each
4133  tab content section should have a data-tab attribute matching one of the
4134  nav items'. Also each tab content section should have a width matching the
4135  tab carousel.
4136
4137  HTML:
4138
4139  <div class="tab-carousel">
4140    <ul class="tab-nav">
4141      <li><a href="#" data-tab="handsets">Handsets</a>
4142      <li><a href="#" data-tab="wearable">Wearable</a>
4143      <li><a href="#" data-tab="tv">TV</a>
4144    </ul>
4145
4146    <div class="tab-carousel-content">
4147      <div data-tab="handsets">
4148        <!--Full width content here-->
4149      </div>
4150
4151      <div data-tab="wearable">
4152        <!--Full width content here-->
4153      </div>
4154
4155      <div data-tab="tv">
4156        <!--Full width content here-->
4157      </div>
4158    </div>
4159  </div>
4160
4161*/
4162(function() {
4163  $(document).ready(function() {
4164    $('.tab-carousel').each(function() {
4165      initWidget(this);
4166    });
4167  });
4168
4169  function initWidget(widget) {
4170    var $widget = $(widget);
4171    var $nav = $widget.find('.tab-nav');
4172    var $anchors = $nav.find('[data-tab]');
4173    var $li = $nav.find('li');
4174    var $contentContainer = $widget.find('.tab-carousel-content');
4175    var $tabs = $contentContainer.find('[data-tab]');
4176    var $curTab = $($tabs[0]); // Current tab is first tab.
4177    var width = $widget.width();
4178
4179    // Setup nav interactivity.
4180    $anchors.click(function(evt) {
4181      evt.preventDefault();
4182      var query = '[data-tab=' + $(this).data('tab') + ']';
4183      transitionWidget($tabs.filter(query));
4184    });
4185
4186    // Add highlight for navigation on first item.
4187    var $highlight = $('<div>').addClass('highlight')
4188      .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4189      .appendTo($nav);
4190
4191    // Store height since we will change contents to absolute.
4192    $contentContainer.height($contentContainer.height());
4193
4194    // Absolutely position tabs so they're ready for transition.
4195    $tabs.each(function(index) {
4196      $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4197    });
4198
4199    function transitionWidget($toTab) {
4200      if (!$curTab.is($toTab)) {
4201        var curIndex = $tabs.index($curTab[0]);
4202        var toIndex = $tabs.index($toTab[0]);
4203        var dir = toIndex > curIndex ? 1 : -1;
4204
4205        // Animate content sections.
4206        $toTab.css({left:(width * dir) + 'px'});
4207        $curTab.animate({left:(width * -dir) + 'px'});
4208        $toTab.animate({left:'0'});
4209
4210        // Animate navigation highlight.
4211        $highlight.animate({left:$($li[toIndex]).position().left + 'px',
4212          width:$($li[toIndex]).outerWidth() + 'px'})
4213
4214        // Store new current section.
4215        $curTab = $toTab;
4216      }
4217    }
4218  }
4219})();