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