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