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