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