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