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