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