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