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