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