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