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