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