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