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