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