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