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