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