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