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