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