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