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