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