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