blob: 7c2af5dbd7cba6b3ce6944d0b55d06f21f4ea2fc [file] [log] [blame]
smain@google.com5fea5df2016-03-07 18:43:21 -08001var classesNav;
2var devdocNav;
3var sidenav;
4var cookie_namespace = 'android_developer';
5var NAV_PREF_TREE = "tree";
6var NAV_PREF_PANELS = "panels";
7var nav_pref;
8var isMobile = false; // true if mobile, so we can adjust some layout
9var mPagePath; // initialized in ready() function
10
11var basePath = getBaseUri(location.pathname);
12var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
13var GOOGLE_DATA; // combined data for google service apis, used for search suggest
14
15// Ensure that all ajax getScript() requests allow caching
16$.ajaxSetup({
17 cache: true
18});
19
20/****** ON LOAD SET UP STUFF *********/
21
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 && $(event.target).is(':not(:input)')) {
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 if (window.innerWidth >= 720) {
75 $('.scroll-pane').jScrollPane({verticalGutter: 0});
76 }
77
78 // set up the search close button
79 $('#search-close').on('click touchend', function() {
80 $searchInput = $('#search_autocomplete');
81 $searchInput.attr('value', '');
82 $(this).addClass("hide");
83 $("#search-container").removeClass('active');
84 $("#search_autocomplete").blur();
85 search_focus_changed($searchInput.get(), false);
86 hideResults();
87 });
88
89
90 //Set up search
91 $("#search_autocomplete").focus(function() {
92 $("#search-container").addClass('active');
93 })
94 $("#search-container").on('mouseover touchend', function(e) {
95 if ($(e.target).is('#search-close')) { return; }
96 $("#search-container").addClass('active');
97 $("#search_autocomplete").focus();
98 })
99 $("#search-container").mouseout(function() {
100 if ($("#search_autocomplete").is(":focus")) return;
101 if ($("#search_autocomplete").val() == '') {
102 setTimeout(function(){
103 $("#search-container").removeClass('active');
104 $("#search_autocomplete").blur();
105 },250);
106 }
107 })
108 $("#search_autocomplete").blur(function() {
109 if ($("#search_autocomplete").val() == '') {
110 $("#search-container").removeClass('active');
111 }
112 })
113
114
115 // prep nav expandos
116 var pagePath = document.location.pathname;
117 // account for intl docs by removing the intl/*/ path
118 if (pagePath.indexOf("/intl/") == 0) {
119 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
120 }
121
122 if (pagePath.indexOf(SITE_ROOT) == 0) {
123 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
124 pagePath += 'index.html';
125 }
126 }
127
128 // Need a copy of the pagePath before it gets changed in the next block;
129 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
130 var pagePathOriginal = pagePath;
131 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
132 // If running locally, SITE_ROOT will be a relative path, so account for that by
133 // finding the relative URL to this page. This will allow us to find links on the page
134 // leading back to this page.
135 var pathParts = pagePath.split('/');
136 var relativePagePathParts = [];
137 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
138 for (var i = 0; i < upDirs; i++) {
139 relativePagePathParts.push('..');
140 }
141 for (var i = 0; i < upDirs; i++) {
142 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
143 }
144 relativePagePathParts.push(pathParts[pathParts.length - 1]);
145 pagePath = relativePagePathParts.join('/');
146 } else {
147 // Otherwise the page path is already an absolute URL
148 }
149
150 // Highlight the header tabs...
151 // highlight Design tab
152 var urlSegments = pagePathOriginal.split('/');
153 var navEl = $(".dac-nav-list");
154 var subNavEl = navEl.find(".dac-nav-secondary");
155 var parentNavEl;
156
157 if ($("body").hasClass("design")) {
158 navEl.find("> li.design > a").addClass("selected");
159 // highlight About tabs
160 } else if ($("body").hasClass("about")) {
161 if (urlSegments[1] == "about" || urlSegments[1] == "wear" || urlSegments[1] == "tv" || urlSegments[1] == "auto") {
162 navEl.find("> li.home > a").addClass('has-subnav');
163 subNavEl.find("li." + urlSegments[1] + " > a").addClass("selected");
164 } else {
165 navEl.find("> li.home > a").addClass('selected');
166 }
167
168// highlight NDK tabs
169 } else if ($("body").hasClass("ndk")) {
170 parentNavEl = navEl.find("> li.ndk > a");
171 parentNavEl.addClass('has-subnav');
172 if ($("body").hasClass("guide")) {
173 navEl.find("> li.guides > a").addClass("selected ndk");
174 } else if ($("body").hasClass("reference")) {
175 navEl.find("> li.reference > a").addClass("selected ndk");
176 } else if ($("body").hasClass("samples")) {
177 navEl.find("> li.samples > a").addClass("selected ndk");
178 } else if ($("body").hasClass("downloads")) {
179 navEl.find("> li.downloads > a").addClass("selected ndk");
180 }
181
182 // highlight Develop tab
183 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
184 parentNavEl = navEl.find("> li.develop > a");
185 parentNavEl.addClass('has-subnav');
186
187 // In Develop docs, also highlight appropriate sub-tab
188 if (urlSegments[1] == "training") {
189 subNavEl.find("li.training > a").addClass("selected");
190 } else if (urlSegments[1] == "guide") {
191 subNavEl.find("li.guide > a").addClass("selected");
192 } else if (urlSegments[1] == "reference") {
193 // If the root is reference, but page is also part of Google Services, select Google
194 if ($("body").hasClass("google")) {
195 subNavEl.find("li.google > a").addClass("selected");
196 } else {
197 subNavEl.find("li.reference > a").addClass("selected");
198 }
199 } else if ((urlSegments[1] == "tools") || (urlSegments[1] == "sdk")) {
200 subNavEl.find("li.tools > a").addClass("selected");
201 } else if ($("body").hasClass("google")) {
202 subNavEl.find("li.google > a").addClass("selected");
203 } else if ($("body").hasClass("samples")) {
204 subNavEl.find("li.samples > a").addClass("selected");
205 } else if ($("body").hasClass("preview")) {
206 subNavEl.find("li.preview > a").addClass("selected");
207 } else {
208 parentNavEl.removeClass('has-subnav').addClass("selected");
209 }
210 // highlight Distribute tab
211 } else if ($("body").hasClass("distribute")) {
212 parentNavEl = navEl.find("> li.distribute > a");
213 parentNavEl.addClass('has-subnav');
214
215 if (urlSegments[2] == "users") {
216 subNavEl.find("li.users > a").addClass("selected");
217 } else if (urlSegments[2] == "engage") {
218 subNavEl.find("li.engage > a").addClass("selected");
219 } else if (urlSegments[2] == "monetize") {
220 subNavEl.find("li.monetize > a").addClass("selected");
221 } else if (urlSegments[2] == "analyze") {
222 subNavEl.find("li.analyze > a").addClass("selected");
223 } else if (urlSegments[2] == "tools") {
224 subNavEl.find("li.essentials > a").addClass("selected");
225 } else if (urlSegments[2] == "stories") {
226 subNavEl.find("li.stories > a").addClass("selected");
227 } else if (urlSegments[2] == "essentials") {
228 subNavEl.find("li.essentials > a").addClass("selected");
229 } else if (urlSegments[2] == "googleplay") {
230 subNavEl.find("li.googleplay > a").addClass("selected");
231 } else {
232 parentNavEl.removeClass('has-subnav').addClass("selected");
233 }
234 }
235
236 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
237 // and highlight the sidenav
238 mPagePath = pagePath;
239 highlightSidenav();
240 buildBreadcrumbs();
241
242 // set up prev/next links if they exist
243 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
244 var $selListItem;
245 if ($selNavLink.length) {
246 $selListItem = $selNavLink.closest('li');
247
248 // set up prev links
249 var $prevLink = [];
250 var $prevListItem = $selListItem.prev('li');
251
252 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
253false; // navigate across topic boundaries only in design docs
254 if ($prevListItem.length) {
255 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
256 // jump to last topic of previous section
257 $prevLink = $prevListItem.find('a:last');
258 } else if (!$selListItem.hasClass('nav-section')) {
259 // jump to previous topic in this section
260 $prevLink = $prevListItem.find('a:eq(0)');
261 }
262 } else {
263 // jump to this section's index page (if it exists)
264 var $parentListItem = $selListItem.parents('li');
265 $prevLink = $selListItem.parents('li').find('a');
266
267 // except if cross boundaries aren't allowed, and we're at the top of a section already
268 // (and there's another parent)
269 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
270 && $selListItem.hasClass('nav-section')) {
271 $prevLink = [];
272 }
273 }
274
275 // set up next links
276 var $nextLink = [];
277 var startClass = false;
278 var isCrossingBoundary = false;
279
280 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
281 // we're on an index page, jump to the first topic
282 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
283
284 // if there aren't any children, go to the next section (required for About pages)
285 if($nextLink.length == 0) {
286 $nextLink = $selListItem.next('li').find('a');
287 } else if ($('.topic-start-link').length) {
288 // as long as there's a child link and there is a "topic start link" (we're on a landing)
289 // then set the landing page "start link" text to be the first doc title
290 $('.topic-start-link').text($nextLink.text().toUpperCase());
291 }
292
293 // If the selected page has a description, then it's a class or article homepage
294 if ($selListItem.find('a[description]').length) {
295 // this means we're on a class landing page
296 startClass = true;
297 }
298 } else {
299 // jump to the next topic in this section (if it exists)
300 $nextLink = $selListItem.next('li').find('a:eq(0)');
301 if ($nextLink.length == 0) {
302 isCrossingBoundary = true;
303 // no more topics in this section, jump to the first topic in the next section
304 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
305 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
306 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
307 if ($nextLink.length == 0) {
308 // if that doesn't work, we're at the end of the list, so disable NEXT link
309 $('.next-page-link').attr('href','').addClass("disabled")
310 .click(function() { return false; });
311 // and completely hide the one in the footer
312 $('.content-footer .next-page-link').hide();
313 }
314 }
315 }
316 }
317
318 if (startClass) {
319 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
320
321 // if there's no training bar (below the start button),
322 // then we need to add a bottom border to button
323 if (!$("#tb").length) {
324 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
325 }
326 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
327 $('.content-footer.next-class').show();
328 $('.next-page-link').attr('href','')
329 .removeClass("hide").addClass("disabled")
330 .click(function() { return false; });
331 // and completely hide the one in the footer
332 $('.content-footer .next-page-link').hide();
333 if ($nextLink.length) {
334 $('.next-class-link').attr('href',$nextLink.attr('href'))
335 .removeClass("hide")
336 .append(": " + $nextLink.html());
337 $('.next-class-link').find('.new').empty();
338 }
339 } else {
340 $('.next-page-link').attr('href', $nextLink.attr('href'))
341 .removeClass("hide");
342 // for the footer link, also add the next page title
343 $('.content-footer .next-page-link').append(": " + $nextLink.html());
344 }
345
346 if (!startClass && $prevLink.length) {
347 var prevHref = $prevLink.attr('href');
348 if (prevHref == SITE_ROOT + 'index.html') {
349 // Don't show Previous when it leads to the homepage
350 } else {
351 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
352 }
353 }
354
355 }
356
357
358
359 // Set up the course landing pages for Training with class names and descriptions
360 if ($('body.trainingcourse').length) {
361 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
362
363 // create an array for all the class descriptions
364 var $classDescriptions = new Array($classLinks.length);
365 var lang = getLangPref();
366 $classLinks.each(function(index) {
367 var langDescr = $(this).attr(lang + "-description");
368 if (typeof langDescr !== 'undefined' && langDescr !== false) {
369 // if there's a class description in the selected language, use that
370 $classDescriptions[index] = langDescr;
371 } else {
372 // otherwise, use the default english description
373 $classDescriptions[index] = $(this).attr("description");
374 }
375 });
376
377 var $olClasses = $('<ol class="class-list"></ol>');
378 var $liClass;
379 var $h2Title;
380 var $pSummary;
381 var $olLessons;
382 var $liLesson;
383 $classLinks.each(function(index) {
384 $liClass = $('<li class="clearfix"></li>');
385 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2 class="norule">' + $(this).html()+'</h2><span></span></a>');
386 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
387
388 $olLessons = $('<ol class="lesson-list"></ol>');
389
390 $lessons = $(this).closest('li').find('ul li a');
391
392 if ($lessons.length) {
393 $lessons.each(function(index) {
394 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
395 });
396 } else {
397 $pSummary.addClass('article');
398 }
399
400 $liClass.append($h2Title).append($pSummary).append($olLessons);
401 $olClasses.append($liClass);
402 });
403 $('.jd-descr').append($olClasses);
404 }
405
406 // Set up expand/collapse behavior
407 initExpandableNavItems("#nav");
408
409
410 $(".scroll-pane").scroll(function(event) {
411 event.preventDefault();
412 return false;
413 });
414
415 /* Resize nav height when window height changes */
416 $(window).resize(function() {
417 if ($('#side-nav').length == 0) return;
418 setNavBarDimensions(); // do this even if sidenav isn't fixed because it could become fixed
419 // make sidenav behave when resizing the window and side-scolling is a concern
420 updateSideNavDimensions();
421 checkSticky();
422 resizeNav(250);
423 });
424
425 if ($('#devdoc-nav').length) {
426 setNavBarDimensions();
427 }
428
429
430 // Set up play-on-hover <video> tags.
431 $('video.play-on-hover').bind('click', function(){
432 $(this).get(0).load(); // in case the video isn't seekable
433 $(this).get(0).play();
434 });
435
436 // Set up tooltips
437 var TOOLTIP_MARGIN = 10;
438 $('acronym,.tooltip-link').each(function() {
439 var $target = $(this);
440 var $tooltip = $('<div>')
441 .addClass('tooltip-box')
442 .append($target.attr('title'))
443 .hide()
444 .appendTo('body');
445 $target.removeAttr('title');
446
447 $target.hover(function() {
448 // in
449 var targetRect = $target.offset();
450 targetRect.width = $target.width();
451 targetRect.height = $target.height();
452
453 $tooltip.css({
454 left: targetRect.left,
455 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
456 });
457 $tooltip.addClass('below');
458 $tooltip.show();
459 }, function() {
460 // out
461 $tooltip.hide();
462 });
463 });
464
465 // Set up <h2> deeplinks
466 $('h2').click(function() {
467 var id = $(this).attr('id');
468 if (id) {
469 if (history && history.replaceState) {
470 // Change url without scrolling.
471 history.replaceState({}, '', '#' + id);
472 } else {
473 document.location.hash = id;
474 }
475 }
476 });
477
478 //Loads the +1 button
479 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
480 po.src = 'https://apis.google.com/js/plusone.js';
481 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
482
483 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
484
485 if ($(".scroll-pane").length > 1) {
486 // Check if there's a user preference for the panel heights
487 var cookieHeight = readCookie("reference_height");
488 if (cookieHeight) {
489 restoreHeight(cookieHeight);
490 }
491 }
492
493 // Resize once loading is finished
494 resizeNav();
495 // Check if there's an anchor that we need to scroll into view.
496 // A delay is needed, because some browsers do not immediately scroll down to the anchor
497 window.setTimeout(offsetScrollForSticky, 100);
498
499 /* init the language selector based on user cookie for lang */
500 loadLangPref();
501 changeNavLang(getLangPref());
502
503 /* setup event handlers to ensure the overflow menu is visible while picking lang */
504 $("#language select")
505 .mousedown(function() {
506 $("div.morehover").addClass("hover"); })
507 .blur(function() {
508 $("div.morehover").removeClass("hover"); });
509
510 /* some global variable setup */
511 resizePackagesNav = $("#resize-packages-nav");
512 classesNav = $("#classes-nav");
513 devdocNav = $("#devdoc-nav");
514
515 var cookiePath = "";
516 if (location.href.indexOf("/reference/") != -1) {
517 cookiePath = "reference_";
518 } else if (location.href.indexOf("/guide/") != -1) {
519 cookiePath = "guide_";
520 } else if (location.href.indexOf("/tools/") != -1) {
521 cookiePath = "tools_";
522 } else if (location.href.indexOf("/training/") != -1) {
523 cookiePath = "training_";
524 } else if (location.href.indexOf("/design/") != -1) {
525 cookiePath = "design_";
526 } else if (location.href.indexOf("/distribute/") != -1) {
527 cookiePath = "distribute_";
528 }
529
530
531 /* setup shadowbox for any videos that want it */
532 var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
533 if ($videoLinks.length) {
534 // if there's at least one, add the shadowbox HTML to the body
535 $('body').prepend(
536'<div id="video-container">'+
537 '<div id="video-frame">'+
538 '<div class="video-close">'+
539 '<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
540 '</div>'+
541 '<div id="youTubePlayer"></div>'+
542 '</div>'+
543'</div>');
544
545 // loads the IFrame Player API code asynchronously.
546 $.getScript("https://www.youtube.com/iframe_api");
547
548 $videoLinks.each(function() {
549 var videoId = $(this).attr('href').split('?v=')[1];
550 $(this).click(function(event) {
551 event.preventDefault();
552 startYouTubePlayer(videoId);
553 });
554 });
555 }
556});
557// END of the onload event
558
559
560var youTubePlayer;
561function onYouTubeIframeAPIReady() {
562}
563
564/* Returns the height the shadowbox video should be. It's based on the current
565 height of the "video-frame" element, which is 100% height for the window.
566 Then minus the margin so the video isn't actually the full window height. */
567function getVideoHeight() {
568 var frameHeight = $("#video-frame").height();
569 var marginTop = $("#video-frame").css('margin-top').split('px')[0];
570 return frameHeight - (marginTop * 2);
571}
572
573var mPlayerPaused = false;
574
575function startYouTubePlayer(videoId) {
576 $("#video-container").show();
577 $("#video-frame").show();
578 mPlayerPaused = false;
579
580 // compute the size of the player so it's centered in window
581 var maxWidth = 940; // the width of the web site content
582 var videoAspect = .5625; // based on 1280x720 resolution
583 var maxHeight = maxWidth * videoAspect;
584 var videoHeight = getVideoHeight();
585 var videoWidth = videoHeight / videoAspect;
586 if (videoWidth > maxWidth) {
587 videoWidth = maxWidth;
588 videoHeight = maxHeight;
589 }
590 $("#video-frame").css('width', videoWidth);
591
592 // check if we've already created this player
593 if (youTubePlayer == null) {
594 // check if there's a start time specified
595 var idAndHash = videoId.split("#");
596 var startTime = 0;
597 if (idAndHash.length > 1) {
598 startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0;
599 }
600 // enable localized player
601 var lang = getLangPref();
602 var captionsOn = lang == 'en' ? 0 : 1;
603
604 youTubePlayer = new YT.Player('youTubePlayer', {
605 height: videoHeight,
606 width: videoWidth,
607 videoId: idAndHash[0],
608 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
609 events: {
610 'onReady': onPlayerReady,
611 'onStateChange': onPlayerStateChange
612 }
613 });
614 } else {
615 // reset the size in case the user adjusted the window since last play
616 youTubePlayer.setSize(videoWidth, videoHeight);
617 // if a video different from the one already playing was requested, cue it up
618 if (videoId != youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]) {
619 youTubePlayer.cueVideoById(videoId);
620 }
621 youTubePlayer.playVideo();
622 }
623}
624
625function onPlayerReady(event) {
626 event.target.playVideo();
627 mPlayerPaused = false;
628}
629
630function closeVideo() {
631 try {
632 youTubePlayer.pauseVideo();
633 } catch(e) {
634 }
635 $("#video-container").fadeOut(200);
636}
637
638/* Track youtube playback for analytics */
639function onPlayerStateChange(event) {
640 // Video starts, send the video ID
641 if (event.data == YT.PlayerState.PLAYING) {
642 if (mPlayerPaused) {
643 ga('send', 'event', 'Videos', 'Resume',
644 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]);
645 } else {
646 // track the start playing event so we know from which page the video was selected
647 ga('send', 'event', 'Videos', 'Start: ' +
648 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
649 'on: ' + document.location.href);
650 }
651 mPlayerPaused = false;
652 }
653 // Video paused, send video ID and video elapsed time
654 if (event.data == YT.PlayerState.PAUSED) {
655 ga('send', 'event', 'Videos', 'Paused',
656 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
657 youTubePlayer.getCurrentTime());
658 mPlayerPaused = true;
659 }
660 // Video finished, send video ID and video elapsed time
661 if (event.data == YT.PlayerState.ENDED) {
662 ga('send', 'event', 'Videos', 'Finished',
663 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
664 youTubePlayer.getCurrentTime());
665 mPlayerPaused = true;
666 }
667}
668
669
670
671function initExpandableNavItems(rootTag) {
672 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
673 var section = $(this).closest('li.nav-section');
674 if (section.hasClass('expanded')) {
675 /* hide me and descendants */
676 section.find('ul').slideUp(250, function() {
677 // remove 'expanded' class from my section and any children
678 section.closest('li').removeClass('expanded');
679 $('li.nav-section', section).removeClass('expanded');
680 resizeNav();
681 });
682 } else {
683 /* show me */
684 // first hide all other siblings
685 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
686 $others.removeClass('expanded').children('ul').slideUp(250);
687
688 // now expand me
689 section.closest('li').addClass('expanded');
690 section.children('ul').slideDown(250, function() {
691 resizeNav();
692 });
693 }
694 });
695
696 // Stop expand/collapse behavior when clicking on nav section links
697 // (since we're navigating away from the page)
698 // This selector captures the first instance of <a>, but not those with "#" as the href.
699 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
700 window.location.href = $(this).attr('href');
701 return false;
702 });
703}
704
705
706/** Create the list of breadcrumb links in the sticky header */
707function buildBreadcrumbs() {
708 var $breadcrumbUl = $(".dac-header-crumbs");
709 var primaryNavLink = ".dac-nav-list > .dac-nav-item > .dac-nav-link";
710
711 // Add the secondary horizontal nav item, if provided
712 var $selectedSecondNav = $(".dac-nav-secondary .dac-nav-link.selected").clone()
713 .attr('class', 'dac-header-crumbs-link');
714
715 if ($selectedSecondNav.length) {
716 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedSecondNav));
717 }
718
719 // Add the primary horizontal nav
720 var $selectedFirstNav = $(primaryNavLink + ".selected, " + primaryNavLink + ".has-subnav").clone()
721 .attr('class', 'dac-header-crumbs-link');
722
723 // If there's no header nav item, use the logo link and title from alt text
724 if ($selectedFirstNav.length < 1) {
725 $selectedFirstNav = $('<a class="dac-header-crumbs-link">')
726 .attr('href', $("div#header .logo a").attr('href'))
727 .text($("div#header .logo img").attr('alt'));
728 }
729 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedFirstNav));
730}
731
732
733
734/** Highlight the current page in sidenav, expanding children as appropriate */
735function highlightSidenav() {
736 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
737 if ($("ul#nav li.selected").length) {
738 unHighlightSidenav();
739 }
740 // look for URL in sidenav, including the hash
741 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
742
743 // If the selNavLink is still empty, look for it without the hash
744 if ($selNavLink.length == 0) {
745 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
746 }
747
748 var $selListItem;
749 if ($selNavLink.length) {
750 // Find this page's <li> in sidenav and set selected
751 $selListItem = $selNavLink.closest('li');
752 $selListItem.addClass('selected');
753
754 // Traverse up the tree and expand all parent nav-sections
755 $selNavLink.parents('li.nav-section').each(function() {
756 $(this).addClass('expanded');
757 $(this).children('ul').show();
758 });
759 }
760}
761
762function unHighlightSidenav() {
763 $("ul#nav li.selected").removeClass("selected");
764 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
765}
766
767function toggleFullscreen(enable) {
768 var delay = 20;
769 var enabled = true;
770 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
771 if (enable) {
772 // Currently NOT USING fullscreen; enable fullscreen
773 stylesheet.removeAttr('disabled');
774 $('#nav-swap .fullscreen').removeClass('disabled');
775 $('#devdoc-nav').css({left:''});
776 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
777 enabled = true;
778 } else {
779 // Currently USING fullscreen; disable fullscreen
780 stylesheet.attr('disabled', 'disabled');
781 $('#nav-swap .fullscreen').addClass('disabled');
782 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
783 enabled = false;
784 }
785 writeCookie("fullscreen", enabled, null);
786 setNavBarDimensions();
787 resizeNav(delay);
788 updateSideNavDimensions();
789 setTimeout(initSidenavHeightResize,delay);
790}
791
792// TODO: Refactor into a closure.
793var navBarLeftPos;
794var navBarWidth;
795function setNavBarDimensions() {
796 navBarLeftPos = $('#body-content').offset().left;
797 navBarWidth = $('#side-nav').width();
798}
799
800
801function updateSideNavDimensions() {
802 var newLeft = $(window).scrollLeft() - navBarLeftPos;
803 $('#devdoc-nav').css({left: -newLeft, width: navBarWidth});
804 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('padding-left')))});
805}
806
807// TODO: use $(document).ready instead
808function addLoadEvent(newfun) {
809 var current = window.onload;
810 if (typeof window.onload != 'function') {
811 window.onload = newfun;
812 } else {
813 window.onload = function() {
814 current();
815 newfun();
816 }
817 }
818}
819
820var agent = navigator['userAgent'].toLowerCase();
821// If a mobile phone, set flag and do mobile setup
822if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
823 (agent.indexOf("blackberry") != -1) ||
824 (agent.indexOf("webos") != -1) ||
825 (agent.indexOf("mini") != -1)) { // opera mini browsers
826 isMobile = true;
827}
828
829
830$(document).ready(function() {
831 $("pre:not(.no-pretty-print)").addClass("prettyprint");
832 prettyPrint();
833});
834
835
836
837
838/* ######### RESIZE THE SIDENAV ########## */
839
840function resizeNav(delay) {
841 var $nav = $("#devdoc-nav");
842 var $window = $(window);
843 var navHeight;
844
845 // Get the height of entire window and the total header height.
846 // Then figure out based on scroll position whether the header is visible
847 var windowHeight = $window.height();
848 var scrollTop = $window.scrollTop();
849 var headerHeight = $('#header-wrapper').outerHeight();
850 var headerVisible = scrollTop < stickyTop;
851
852 // get the height of space between nav and top of window.
853 // Could be either margin or top position, depending on whether the nav is fixed.
854 var topMargin = (parseInt($nav.css('top')) || 20) + 1;
855 // add 1 for the #side-nav bottom margin
856
857 // Depending on whether the header is visible, set the side nav's height.
858 if (headerVisible) {
859 // The sidenav height grows as the header goes off screen
860 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
861 } else {
862 // Once header is off screen, the nav height is almost full window height
863 navHeight = windowHeight - topMargin;
864 }
865
866
867
868 $scrollPanes = $(".scroll-pane");
869 if ($window.width() < 720) {
870 $nav.css('height', '');
871 } else if ($scrollPanes.length > 1) {
872 // subtract the height of the api level widget and nav swapper from the available nav height
873 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
874
875 $("#swapper").css({height:navHeight + "px"});
876 if ($("#nav-tree").is(":visible")) {
877 $("#nav-tree").css({height:navHeight});
878 }
879
880 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
881 //subtract 10px to account for drag bar
882
883 // if the window becomes small enough to make the class panel height 0,
884 // then the package panel should begin to shrink
885 if (parseInt(classesHeight) <= 0) {
886 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
887 $("#packages-nav").css({height:navHeight - 10});
888 }
889
890 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
891 $("#classes-nav .jspContainer").css({height:classesHeight});
892
893
894 } else {
895 $nav.height(navHeight);
896 }
897
898 if (delay) {
899 updateFromResize = true;
900 delayedReInitScrollbars(delay);
901 } else {
902 reInitScrollbars();
903 }
904
905}
906
907var updateScrollbars = false;
908var updateFromResize = false;
909
910/* Re-initialize the scrollbars to account for changed nav size.
911 * This method postpones the actual update by a 1/4 second in order to optimize the
912 * scroll performance while the header is still visible, because re-initializing the
913 * scroll panes is an intensive process.
914 */
915function delayedReInitScrollbars(delay) {
916 // If we're scheduled for an update, but have received another resize request
917 // before the scheduled resize has occured, just ignore the new request
918 // (and wait for the scheduled one).
919 if (updateScrollbars && updateFromResize) {
920 updateFromResize = false;
921 return;
922 }
923
924 // We're scheduled for an update and the update request came from this method's setTimeout
925 if (updateScrollbars && !updateFromResize) {
926 reInitScrollbars();
927 updateScrollbars = false;
928 } else {
929 updateScrollbars = true;
930 updateFromResize = false;
931 setTimeout('delayedReInitScrollbars()',delay);
932 }
933}
934
935/* Re-initialize the scrollbars to account for changed nav size. */
936function reInitScrollbars() {
937 var pane = $(".scroll-pane").each(function(){
938 var api = $(this).data('jsp');
939 if (!api) {return;}
940 api.reinitialise( {verticalGutter:0} );
941 });
942 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
943}
944
945
946/* Resize the height of the nav panels in the reference,
947 * and save the new size to a cookie */
948function saveNavPanels() {
949 var basePath = getBaseUri(location.pathname);
950 var section = basePath.substring(1,basePath.indexOf("/",1));
951 writeCookie("height", resizePackagesNav.css("height"), section);
952}
953
954
955
956function restoreHeight(packageHeight) {
957 $("#resize-packages-nav").height(packageHeight);
958 $("#packages-nav").height(packageHeight);
959 // var classesHeight = navHeight - packageHeight;
960 // $("#classes-nav").css({height:classesHeight});
961 // $("#classes-nav .jspContainer").css({height:classesHeight});
962}
963
964
965
966/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
967
968
969
970
971
972/** Scroll the jScrollPane to make the currently selected item visible
973 This is called when the page finished loading. */
974function scrollIntoView(nav) {
975 return;
976 var $nav = $("#"+nav);
977 var element = $nav.jScrollPane({/* ...settings... */});
978 var api = element.data('jsp');
979
980 if ($nav.is(':visible')) {
981 var $selected = $(".selected", $nav);
982 if ($selected.length == 0) {
983 // If no selected item found, exit
984 return;
985 }
986 // get the selected item's offset from its container nav by measuring the item's offset
987 // relative to the document then subtract the container nav's offset relative to the document
988 var selectedOffset = $selected.offset().top - $nav.offset().top + 60;
989 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
990 // if it's more than 80% down the nav
991 // scroll the item up by an amount equal to 80% the container nav's height
992 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
993 }
994 }
995}
996
997
998
999
1000
1001
1002/* Show popup dialogs */
1003function showDialog(id) {
1004 $dialog = $("#"+id);
1005 $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>');
1006 $dialog.wrapInner('<div/>');
1007 $dialog.removeClass("hide");
1008}
1009
1010
1011
1012
1013
1014/* ######### COOKIES! ########## */
1015
1016function readCookie(cookie) {
1017 var myCookie = cookie_namespace+"_"+cookie+"=";
1018 if (document.cookie) {
1019 var index = document.cookie.indexOf(myCookie);
1020 if (index != -1) {
1021 var valStart = index + myCookie.length;
1022 var valEnd = document.cookie.indexOf(";", valStart);
1023 if (valEnd == -1) {
1024 valEnd = document.cookie.length;
1025 }
1026 var val = document.cookie.substring(valStart, valEnd);
1027 return val;
1028 }
1029 }
1030 return 0;
1031}
1032
1033function writeCookie(cookie, val, section) {
1034 if (val==undefined) return;
1035 section = section == null ? "_" : "_"+section+"_";
1036 var age = 2*365*24*60*60; // set max-age to 2 years
1037 var cookieValue = cookie_namespace + section + cookie + "=" + val
1038 + "; max-age=" + age +"; path=/";
1039 document.cookie = cookieValue;
1040}
1041
1042/* ######### END COOKIES! ########## */
1043
1044
1045var sticky = false;
1046var stickyTop;
1047var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
1048/* Sets the vertical scoll position at which the sticky bar should appear.
1049 This method is called to reset the position when search results appear or hide */
1050function setStickyTop() {
1051 stickyTop = $('#header-wrapper').outerHeight() - $('#header > .dac-header-inner').outerHeight();
1052}
1053
1054/*
1055 * Displays sticky nav bar on pages when dac header scrolls out of view
1056 */
1057$(window).scroll(function(event) {
1058 // Exit if the mouse target is a DIV, because that means the event is coming
1059 // from a scrollable div and so there's no need to make adjustments to our layout
1060 if ($(event.target).nodeName == "DIV") {
1061 return;
1062 }
1063
1064 checkSticky();
1065});
1066
1067function checkSticky() {
1068 setStickyTop();
1069 var $headerEl = $('#header');
1070 // Exit if there's no sidenav
1071 if ($('#side-nav').length == 0) return;
1072
1073 var top = $(window).scrollTop();
1074 // we set the navbar fixed when the scroll position is beyond the height of the site header...
1075 var shouldBeSticky = top > stickyTop;
1076 // ... except if the document content is shorter than the sidenav height.
1077 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1078 if ($("#doc-col").height() < $("#side-nav").height()) {
1079 shouldBeSticky = false;
1080 }
1081 // Nor on mobile
1082 if (window.innerWidth < 720) {
1083 shouldBeSticky = false;
1084 }
1085 // Account for horizontal scroll
1086 var scrollLeft = $(window).scrollLeft();
1087 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1088 if (sticky && (scrollLeft != prevScrollLeft)) {
1089 updateSideNavDimensions();
1090 prevScrollLeft = scrollLeft;
1091 }
1092
1093 // Don't continue if the header is sufficently far away
1094 // (to avoid intensive resizing that slows scrolling)
1095 if (sticky == shouldBeSticky) {
1096 return;
1097 }
1098
1099 // If sticky header visible and position is now near top, hide sticky
1100 if (sticky && !shouldBeSticky) {
1101 sticky = false;
1102 // make the sidenav static again
1103 $('#devdoc-nav')
1104 .removeClass('fixed')
1105 .css({'width':'auto','margin':''});
1106 // delay hide the sticky
1107 $headerEl.removeClass('is-sticky');
1108
1109 // update the sidenaav position for side scrolling
1110 updateSideNavDimensions();
1111 } else if (!sticky && shouldBeSticky) {
1112 sticky = true;
1113 $headerEl.addClass('is-sticky');
1114
1115 // make the sidenav fixed
1116 $('#devdoc-nav')
1117 .addClass('fixed');
1118
1119 // update the sidenaav position for side scrolling
1120 updateSideNavDimensions();
1121
1122 }
1123 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
1124}
1125
1126/*
1127 * Manages secion card states and nav resize to conclude loading
1128 */
1129(function() {
1130 $(document).ready(function() {
1131
1132 // Stack hover states
1133 $('.section-card-menu').each(function(index, el) {
1134 var height = $(el).height();
1135 $(el).css({height:height+'px', position:'relative'});
1136 var $cardInfo = $(el).find('.card-info');
1137
1138 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1139 });
1140
1141 });
1142
1143})();
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158/* MISC LIBRARY FUNCTIONS */
1159
1160
1161
1162
1163
1164function toggle(obj, slide) {
1165 var ul = $("ul:first", obj);
1166 var li = ul.parent();
1167 if (li.hasClass("closed")) {
1168 if (slide) {
1169 ul.slideDown("fast");
1170 } else {
1171 ul.show();
1172 }
1173 li.removeClass("closed");
1174 li.addClass("open");
1175 $(".toggle-img", li).attr("title", "hide pages");
1176 } else {
1177 ul.slideUp("fast");
1178 li.removeClass("open");
1179 li.addClass("closed");
1180 $(".toggle-img", li).attr("title", "show pages");
1181 }
1182}
1183
1184
1185function buildToggleLists() {
1186 $(".toggle-list").each(
1187 function(i) {
1188 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1189 $(this).addClass("closed");
1190 });
1191}
1192
1193
1194
1195function hideNestedItems(list, toggle) {
1196 $list = $(list);
1197 // hide nested lists
1198 if($list.hasClass('showing')) {
1199 $("li ol", $list).hide('fast');
1200 $list.removeClass('showing');
1201 // show nested lists
1202 } else {
1203 $("li ol", $list).show('fast');
1204 $list.addClass('showing');
1205 }
1206 $(".more,.less",$(toggle)).toggle();
1207}
1208
1209
1210/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1211function setupIdeDocToggle() {
1212 $( "select.ide" ).change(function() {
1213 var selected = $(this).find("option:selected").attr("value");
1214 $(".select-ide").hide();
1215 $(".select-ide."+selected).show();
1216
1217 $("select.ide").val(selected);
1218 });
1219}
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244/* REFERENCE NAV SWAP */
1245
1246
1247function getNavPref() {
1248 var v = readCookie('reference_nav');
1249 if (v != NAV_PREF_TREE) {
1250 v = NAV_PREF_PANELS;
1251 }
1252 return v;
1253}
1254
1255function chooseDefaultNav() {
1256 nav_pref = getNavPref();
1257 if (nav_pref == NAV_PREF_TREE) {
1258 $("#nav-panels").toggle();
1259 $("#panel-link").toggle();
1260 $("#nav-tree").toggle();
1261 $("#tree-link").toggle();
1262 }
1263}
1264
1265function swapNav() {
1266 if (nav_pref == NAV_PREF_TREE) {
1267 nav_pref = NAV_PREF_PANELS;
1268 } else {
1269 nav_pref = NAV_PREF_TREE;
1270 init_default_navtree(toRoot);
1271 }
1272 writeCookie("nav", nav_pref, "reference");
1273
1274 $("#nav-panels").toggle();
1275 $("#panel-link").toggle();
1276 $("#nav-tree").toggle();
1277 $("#tree-link").toggle();
1278
1279 resizeNav();
1280
1281 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1282 $("#nav-tree .jspContainer:visible")
1283 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1284 // Another nasty hack to make the scrollbar appear now that we have height
1285 resizeNav();
1286
1287 if ($("#nav-tree").is(':visible')) {
1288 scrollIntoView("nav-tree");
1289 } else {
1290 scrollIntoView("packages-nav");
1291 scrollIntoView("classes-nav");
1292 }
1293}
1294
1295
1296
1297/* ############################################ */
1298/* ########## LOCALIZATION ############ */
1299/* ############################################ */
1300
1301function getBaseUri(uri) {
1302 var intlUrl = (uri.substring(0,6) == "/intl/");
1303 if (intlUrl) {
1304 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1305 base = base.substring(base.indexOf('/')+1, base.length);
1306 //alert("intl, returning base url: /" + base);
1307 return ("/" + base);
1308 } else {
1309 //alert("not intl, returning uri as found.");
1310 return uri;
1311 }
1312}
1313
1314function requestAppendHL(uri) {
1315//append "?hl=<lang> to an outgoing request (such as to blog)
1316 var lang = getLangPref();
1317 if (lang) {
1318 var q = 'hl=' + lang;
1319 uri += '?' + q;
1320 window.location = uri;
1321 return false;
1322 } else {
1323 return true;
1324 }
1325}
1326
1327
1328function changeNavLang(lang) {
1329 if (lang === 'en') { return; }
1330
1331 var $links = $("a[" + lang + "-lang],p[" + lang + "-lang]");
1332 $links.each(function(){ // for each link with a translation
1333 var $link = $(this);
1334 // put the desired language from the attribute as the text
1335 $link.text($link.attr(lang + '-lang'))
1336 });
1337}
1338
1339function changeLangPref(lang, submit) {
1340 writeCookie("pref_lang", lang, null);
1341
1342 // ####### TODO: Remove this condition once we're stable on devsite #######
1343 // This condition is only needed if we still need to support legacy GAE server
1344 if (devsite) {
1345 // Switch language when on Devsite server
1346 if (submit) {
1347 $("#setlang").submit();
1348 }
1349 } else {
1350 // Switch language when on legacy GAE server
1351 if (submit) {
1352 window.location = getBaseUri(location.pathname);
1353 }
1354 }
1355}
1356
1357function loadLangPref() {
1358 var lang = readCookie("pref_lang");
1359 if (lang != 0) {
1360 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1361 }
1362}
1363
1364function getLangPref() {
1365 var lang = $("#language").find(":selected").attr("value");
1366 if (!lang) {
1367 lang = readCookie("pref_lang");
1368 }
1369 return (lang != 0) ? lang : 'en';
1370}
1371
1372/* ########## END LOCALIZATION ############ */
1373
1374
1375
1376
1377
1378
1379/* Used to hide and reveal supplemental content, such as long code samples.
1380 See the companion CSS in android-developer-docs.css */
1381function toggleContent(obj) {
1382 var div = $(obj).closest(".toggle-content");
1383 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
1384 if (div.hasClass("closed")) { // if it's closed, open it
1385 toggleMe.slideDown();
1386 $(".toggle-content-text:eq(0)", obj).toggle();
1387 div.removeClass("closed").addClass("open");
1388 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
1389 + "assets/images/triangle-opened.png");
1390 } else { // if it's open, close it
1391 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
1392 $(".toggle-content-text:eq(0)", obj).toggle();
1393 div.removeClass("open").addClass("closed");
1394 div.find(".toggle-content").removeClass("open").addClass("closed")
1395 .find(".toggle-content-toggleme").hide();
1396 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
1397 + "assets/images/triangle-closed.png");
1398 });
1399 }
1400 return false;
1401}
1402
1403
1404/* New version of expandable content */
1405function toggleExpandable(link,id) {
1406 if($(id).is(':visible')) {
1407 $(id).slideUp();
1408 $(link).removeClass('expanded');
1409 } else {
1410 $(id).slideDown();
1411 $(link).addClass('expanded');
1412 }
1413}
1414
1415function hideExpandable(ids) {
1416 $(ids).slideUp();
1417 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
1418}
1419
1420
1421
1422
1423
1424/*
1425 * Slideshow 1.0
1426 * Used on /index.html and /develop/index.html for carousel
1427 *
1428 * Sample usage:
1429 * HTML -
1430 * <div class="slideshow-container">
1431 * <a href="" class="slideshow-prev">Prev</a>
1432 * <a href="" class="slideshow-next">Next</a>
1433 * <ul>
1434 * <li class="item"><img src="images/marquee1.jpg"></li>
1435 * <li class="item"><img src="images/marquee2.jpg"></li>
1436 * <li class="item"><img src="images/marquee3.jpg"></li>
1437 * <li class="item"><img src="images/marquee4.jpg"></li>
1438 * </ul>
1439 * </div>
1440 *
1441 * <script type="text/javascript">
1442 * $('.slideshow-container').dacSlideshow({
1443 * auto: true,
1444 * btnPrev: '.slideshow-prev',
1445 * btnNext: '.slideshow-next'
1446 * });
1447 * </script>
1448 *
1449 * Options:
1450 * btnPrev: optional identifier for previous button
1451 * btnNext: optional identifier for next button
1452 * btnPause: optional identifier for pause button
1453 * auto: whether or not to auto-proceed
1454 * speed: animation speed
1455 * autoTime: time between auto-rotation
1456 * easing: easing function for transition
1457 * start: item to select by default
1458 * scroll: direction to scroll in
1459 * pagination: whether or not to include dotted pagination
1460 *
1461 */
1462
1463 (function($) {
1464 $.fn.dacSlideshow = function(o) {
1465
1466 //Options - see above
1467 o = $.extend({
1468 btnPrev: null,
1469 btnNext: null,
1470 btnPause: null,
1471 auto: true,
1472 speed: 500,
1473 autoTime: 12000,
1474 easing: null,
1475 start: 0,
1476 scroll: 1,
1477 pagination: true
1478
1479 }, o || {});
1480
1481 //Set up a carousel for each
1482 return this.each(function() {
1483
1484 var running = false;
1485 var animCss = o.vertical ? "top" : "left";
1486 var sizeCss = o.vertical ? "height" : "width";
1487 var div = $(this);
1488 var ul = $("ul", div);
1489 var tLi = $("li", ul);
1490 var tl = tLi.size();
1491 var timer = null;
1492
1493 var li = $("li", ul);
1494 var itemLength = li.size();
1495 var curr = o.start;
1496
1497 li.css({float: o.vertical ? "none" : "left"});
1498 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1499 div.css({position: "relative", "z-index": "2", left: "0px"});
1500
1501 var liSize = o.vertical ? height(li) : width(li);
1502 var ulSize = liSize * itemLength;
1503 var divSize = liSize;
1504
1505 li.css({width: li.width(), height: li.height()});
1506 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1507
1508 div.css(sizeCss, divSize+"px");
1509
1510 //Pagination
1511 if (o.pagination) {
1512 var pagination = $("<div class='pagination'></div>");
1513 var pag_ul = $("<ul></ul>");
1514 if (tl > 1) {
1515 for (var i=0;i<tl;i++) {
1516 var li = $("<li>"+i+"</li>");
1517 pag_ul.append(li);
1518 if (i==o.start) li.addClass('active');
1519 li.click(function() {
1520 go(parseInt($(this).text()));
1521 })
1522 }
1523 pagination.append(pag_ul);
1524 div.append(pagination);
1525 }
1526 }
1527
1528 //Previous button
1529 if(o.btnPrev)
1530 $(o.btnPrev).click(function(e) {
1531 e.preventDefault();
1532 return go(curr-o.scroll);
1533 });
1534
1535 //Next button
1536 if(o.btnNext)
1537 $(o.btnNext).click(function(e) {
1538 e.preventDefault();
1539 return go(curr+o.scroll);
1540 });
1541
1542 //Pause button
1543 if(o.btnPause)
1544 $(o.btnPause).click(function(e) {
1545 e.preventDefault();
1546 if ($(this).hasClass('paused')) {
1547 startRotateTimer();
1548 } else {
1549 pauseRotateTimer();
1550 }
1551 });
1552
1553 //Auto rotation
1554 if(o.auto) startRotateTimer();
1555
1556 function startRotateTimer() {
1557 clearInterval(timer);
1558 timer = setInterval(function() {
1559 if (curr == tl-1) {
1560 go(0);
1561 } else {
1562 go(curr+o.scroll);
1563 }
1564 }, o.autoTime);
1565 $(o.btnPause).removeClass('paused');
1566 }
1567
1568 function pauseRotateTimer() {
1569 clearInterval(timer);
1570 $(o.btnPause).addClass('paused');
1571 }
1572
1573 //Go to an item
1574 function go(to) {
1575 if(!running) {
1576
1577 if(to<0) {
1578 to = itemLength-1;
1579 } else if (to>itemLength-1) {
1580 to = 0;
1581 }
1582 curr = to;
1583
1584 running = true;
1585
1586 ul.animate(
1587 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1588 function() {
1589 running = false;
1590 }
1591 );
1592
1593 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1594 $( (curr-o.scroll<0 && o.btnPrev)
1595 ||
1596 (curr+o.scroll > itemLength && o.btnNext)
1597 ||
1598 []
1599 ).addClass("disabled");
1600
1601
1602 var nav_items = $('li', pagination);
1603 nav_items.removeClass('active');
1604 nav_items.eq(to).addClass('active');
1605
1606
1607 }
1608 if(o.auto) startRotateTimer();
1609 return false;
1610 };
1611 });
1612 };
1613
1614 function css(el, prop) {
1615 return parseInt($.css(el[0], prop)) || 0;
1616 };
1617 function width(el) {
1618 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1619 };
1620 function height(el) {
1621 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1622 };
1623
1624 })(jQuery);
1625
1626
1627/*
1628 * dacSlideshow 1.0
1629 * Used on develop/index.html for side-sliding tabs
1630 *
1631 * Sample usage:
1632 * HTML -
1633 * <div class="slideshow-container">
1634 * <a href="" class="slideshow-prev">Prev</a>
1635 * <a href="" class="slideshow-next">Next</a>
1636 * <ul>
1637 * <li class="item"><img src="images/marquee1.jpg"></li>
1638 * <li class="item"><img src="images/marquee2.jpg"></li>
1639 * <li class="item"><img src="images/marquee3.jpg"></li>
1640 * <li class="item"><img src="images/marquee4.jpg"></li>
1641 * </ul>
1642 * </div>
1643 *
1644 * <script type="text/javascript">
1645 * $('.slideshow-container').dacSlideshow({
1646 * auto: true,
1647 * btnPrev: '.slideshow-prev',
1648 * btnNext: '.slideshow-next'
1649 * });
1650 * </script>
1651 *
1652 * Options:
1653 * btnPrev: optional identifier for previous button
1654 * btnNext: optional identifier for next button
1655 * auto: whether or not to auto-proceed
1656 * speed: animation speed
1657 * autoTime: time between auto-rotation
1658 * easing: easing function for transition
1659 * start: item to select by default
1660 * scroll: direction to scroll in
1661 * pagination: whether or not to include dotted pagination
1662 *
1663 */
1664 (function($) {
1665 $.fn.dacTabbedList = function(o) {
1666
1667 //Options - see above
1668 o = $.extend({
1669 speed : 250,
1670 easing: null,
1671 nav_id: null,
1672 frame_id: null
1673 }, o || {});
1674
1675 //Set up a carousel for each
1676 return this.each(function() {
1677
1678 var curr = 0;
1679 var running = false;
1680 var animCss = "margin-left";
1681 var sizeCss = "width";
1682 var div = $(this);
1683
1684 var nav = $(o.nav_id, div);
1685 var nav_li = $("li", nav);
1686 var nav_size = nav_li.size();
1687 var frame = div.find(o.frame_id);
1688 var content_width = $(frame).find('ul').width();
1689 //Buttons
1690 $(nav_li).click(function(e) {
1691 go($(nav_li).index($(this)));
1692 })
1693
1694 //Go to an item
1695 function go(to) {
1696 if(!running) {
1697 curr = to;
1698 running = true;
1699
1700 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1701 function() {
1702 running = false;
1703 }
1704 );
1705
1706
1707 nav_li.removeClass('active');
1708 nav_li.eq(to).addClass('active');
1709
1710
1711 }
1712 return false;
1713 };
1714 });
1715 };
1716
1717 function css(el, prop) {
1718 return parseInt($.css(el[0], prop)) || 0;
1719 };
1720 function width(el) {
1721 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1722 };
1723 function height(el) {
1724 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1725 };
1726
1727 })(jQuery);
1728
1729
1730
1731
1732
1733/* ######################################################## */
1734/* ################ SEARCH SUGGESTIONS ################## */
1735/* ######################################################## */
1736
1737
1738
1739var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1740var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1741
1742var gMatches = new Array();
1743var gLastText = "";
1744var gInitialized = false;
1745var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1746var gListLength = 0;
1747
1748
1749var gGoogleMatches = new Array();
1750var ROW_COUNT_GOOGLE = 15; // max number of results in list
1751var gGoogleListLength = 0;
1752
1753var gDocsMatches = new Array();
1754var ROW_COUNT_DOCS = 100; // max number of results in list
1755var gDocsListLength = 0;
1756
1757function onSuggestionClick(link) {
1758 // When user clicks a suggested document, track it
1759 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
1760 'query: ' + $("#search_autocomplete").val().toLowerCase());
1761}
1762
1763function set_item_selected($li, selected)
1764{
1765 if (selected) {
1766 $li.attr('class','jd-autocomplete jd-selected');
1767 } else {
1768 $li.attr('class','jd-autocomplete');
1769 }
1770}
1771
1772function set_item_values(toroot, $li, match)
1773{
1774 var $link = $('a',$li);
1775 $link.html(match.__hilabel || match.label);
1776 $link.attr('href',toroot + match.link);
1777}
1778
1779function set_item_values_jd(toroot, $li, match)
1780{
1781 var $link = $('a',$li);
1782 $link.html(match.title);
1783 $link.attr('href',toroot + match.url);
1784}
1785
1786function new_suggestion($list) {
1787 var $li = $("<li class='jd-autocomplete'></li>");
1788 $list.append($li);
1789
1790 $li.mousedown(function() {
1791 window.location = this.firstChild.getAttribute("href");
1792 });
1793 $li.mouseover(function() {
1794 $('.search_filtered_wrapper li').removeClass('jd-selected');
1795 $(this).addClass('jd-selected');
1796 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1797 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
1798 });
1799 $li.append("<a onclick='onSuggestionClick(this)'></a>");
1800 $li.attr('class','show-item');
1801 return $li;
1802}
1803
1804function sync_selection_table(toroot)
1805{
1806 var $li; //list item jquery object
1807 var i; //list item iterator
1808
1809 // if there are NO results at all, hide all columns
1810 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1811 $('.suggest-card').hide(300);
1812 return;
1813 }
1814
1815 // if there are api results
1816 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
1817 // reveal suggestion list
1818 $('.suggest-card.reference').show();
1819 var listIndex = 0; // list index position
1820
1821 // reset the lists
1822 $(".suggest-card.reference li").remove();
1823
1824 // ########### ANDROID RESULTS #############
1825 if (gMatches.length > 0) {
1826
1827 // determine android results to show
1828 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1829 gMatches.length : ROW_COUNT_FRAMEWORK;
1830 for (i=0; i<gListLength; i++) {
1831 var $li = new_suggestion($(".suggest-card.reference ul"));
1832 set_item_values(toroot, $li, gMatches[i]);
1833 set_item_selected($li, i == gSelectedIndex);
1834 }
1835 }
1836
1837 // ########### GOOGLE RESULTS #############
1838 if (gGoogleMatches.length > 0) {
1839 // show header for list
1840 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
1841
1842 // determine google results to show
1843 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1844 for (i=0; i<gGoogleListLength; i++) {
1845 var $li = new_suggestion($(".suggest-card.reference ul"));
1846 set_item_values(toroot, $li, gGoogleMatches[i]);
1847 set_item_selected($li, i == gSelectedIndex);
1848 }
1849 }
1850 } else {
1851 $('.suggest-card.reference').hide();
1852 }
1853
1854 // ########### JD DOC RESULTS #############
1855 if (gDocsMatches.length > 0) {
1856 // reset the lists
1857 $(".suggest-card:not(.reference) li").remove();
1858
1859 // determine google results to show
1860 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1861 // The order must match the reverse order that each section appears as a card in
1862 // the suggestion UI... this may be only for the "develop" grouped items though.
1863 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1864 for (i=0; i<gDocsListLength; i++) {
1865 var sugg = gDocsMatches[i];
1866 var $li;
1867 if (sugg.type == "design") {
1868 $li = new_suggestion($(".suggest-card.design ul"));
1869 } else
1870 if (sugg.type == "distribute") {
1871 $li = new_suggestion($(".suggest-card.distribute ul"));
1872 } else
1873 if (sugg.type == "samples") {
1874 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1875 } else
1876 if (sugg.type == "training") {
1877 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1878 } else
1879 if (sugg.type == "about"||"guide"||"tools"||"google") {
1880 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1881 } else {
1882 continue;
1883 }
1884
1885 set_item_values_jd(toroot, $li, sugg);
1886 set_item_selected($li, i == gSelectedIndex);
1887 }
1888
1889 // add heading and show or hide card
1890 if ($(".suggest-card.design li").length > 0) {
1891 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1892 $(".suggest-card.design").show(300);
1893 } else {
1894 $('.suggest-card.design').hide(300);
1895 }
1896 if ($(".suggest-card.distribute li").length > 0) {
1897 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1898 $(".suggest-card.distribute").show(300);
1899 } else {
1900 $('.suggest-card.distribute').hide(300);
1901 }
1902 if ($(".child-card.guides li").length > 0) {
1903 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1904 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1905 }
1906 if ($(".child-card.training li").length > 0) {
1907 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1908 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1909 }
1910 if ($(".child-card.samples li").length > 0) {
1911 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1912 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1913 }
1914
1915 if ($(".suggest-card.develop li").length > 0) {
1916 $(".suggest-card.develop").show(300);
1917 } else {
1918 $('.suggest-card.develop').hide(300);
1919 }
1920
1921 } else {
1922 $('.suggest-card:not(.reference)').hide(300);
1923 }
1924}
1925
1926/** Called by the search input's onkeydown and onkeyup events.
1927 * Handles navigation with keyboard arrows, Enter key to invoke search,
1928 * otherwise invokes search suggestions on key-up event.
1929 * @param e The JS event
1930 * @param kd True if the event is key-down
1931 * @param toroot A string for the site's root path
1932 * @returns True if the event should bubble up
1933 */
1934function search_changed(e, kd, toroot)
1935{
1936 var currentLang = getLangPref();
1937 var search = document.getElementById("search_autocomplete");
1938 var text = search.value.replace(/(^ +)|( +$)/g, '');
1939 // get the ul hosting the currently selected item
1940 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1941 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1942 var $selectedUl = $columns[gSelectedColumn];
1943
1944 // show/hide the close button
1945 if (text != '') {
1946 $("#search-close").removeClass("hide");
1947 } else {
1948 $("#search-close").addClass("hide");
1949 }
1950 // 27 = esc
1951 if (e.keyCode == 27) {
1952 // close all search results
1953 if (kd) $('#search-close').trigger('click');
1954 return true;
1955 }
1956 // 13 = enter
1957 else if (e.keyCode == 13) {
1958 if (gSelectedIndex < 0) {
1959 $('.suggest-card').hide();
1960 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1961 // if results aren't showing (and text not empty), return true to allow search to execute
1962 $('body,html').animate({scrollTop:0}, '500', 'swing');
1963 return true;
1964 } else {
1965 // otherwise, results are already showing, so allow ajax to auto refresh the results
1966 // and ignore this Enter press to avoid the reload.
1967 return false;
1968 }
1969 } else if (kd && gSelectedIndex >= 0) {
1970 // click the link corresponding to selected item
1971 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
1972 return false;
1973 }
1974 }
1975 // If Google results are showing, return true to allow ajax search to execute
1976 else if ($("#searchResults").is(":visible")) {
1977 // Also, if search_results is scrolled out of view, scroll to top to make results visible
1978 if ((sticky ) && (search.value != "")) {
1979 $('body,html').animate({scrollTop:0}, '500', 'swing');
1980 }
1981 return true;
1982 }
1983 // 38 UP ARROW
1984 else if (kd && (e.keyCode == 38)) {
1985 // if the next item is a header, skip it
1986 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
1987 gSelectedIndex--;
1988 }
1989 if (gSelectedIndex >= 0) {
1990 $('li', $selectedUl).removeClass('jd-selected');
1991 gSelectedIndex--;
1992 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1993 // If user reaches top, reset selected column
1994 if (gSelectedIndex < 0) {
1995 gSelectedColumn = -1;
1996 }
1997 }
1998 return false;
1999 }
2000 // 40 DOWN ARROW
2001 else if (kd && (e.keyCode == 40)) {
2002 // if the next item is a header, skip it
2003 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
2004 gSelectedIndex++;
2005 }
2006 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
2007 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
2008 $('li', $selectedUl).removeClass('jd-selected');
2009 gSelectedIndex++;
2010 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2011 }
2012 return false;
2013 }
2014 // Consider left/right arrow navigation
2015 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
2016 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
2017 // 37 LEFT ARROW
2018 // go left only if current column is not left-most column (last column)
2019 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
2020 $('li', $selectedUl).removeClass('jd-selected');
2021 gSelectedColumn++;
2022 $selectedUl = $columns[gSelectedColumn];
2023 // keep or reset the selected item to last item as appropriate
2024 gSelectedIndex = gSelectedIndex >
2025 $("li", $selectedUl).length-1 ?
2026 $("li", $selectedUl).length-1 : gSelectedIndex;
2027 // if the corresponding item is a header, move down
2028 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2029 gSelectedIndex++;
2030 }
2031 // set item selected
2032 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2033 return false;
2034 }
2035 // 39 RIGHT ARROW
2036 // go right only if current column is not the right-most column (first column)
2037 else if (e.keyCode == 39 && gSelectedColumn > 0) {
2038 $('li', $selectedUl).removeClass('jd-selected');
2039 gSelectedColumn--;
2040 $selectedUl = $columns[gSelectedColumn];
2041 // keep or reset the selected item to last item as appropriate
2042 gSelectedIndex = gSelectedIndex >
2043 $("li", $selectedUl).length-1 ?
2044 $("li", $selectedUl).length-1 : gSelectedIndex;
2045 // if the corresponding item is a header, move down
2046 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2047 gSelectedIndex++;
2048 }
2049 // set item selected
2050 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2051 return false;
2052 }
2053 }
2054
2055 // if key-up event and not arrow down/up/left/right,
2056 // read the search query and add suggestions to gMatches
2057 else if (!kd && (e.keyCode != 40)
2058 && (e.keyCode != 38)
2059 && (e.keyCode != 37)
2060 && (e.keyCode != 39)) {
2061 gSelectedIndex = -1;
2062 gMatches = new Array();
2063 matchedCount = 0;
2064 gGoogleMatches = new Array();
2065 matchedCountGoogle = 0;
2066 gDocsMatches = new Array();
2067 matchedCountDocs = 0;
2068
2069 // Search for Android matches
2070 for (var i=0; i<DATA.length; i++) {
2071 var s = DATA[i];
2072 if (text.length != 0 &&
2073 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2074 gMatches[matchedCount] = s;
2075 matchedCount++;
2076 }
2077 }
2078 rank_autocomplete_api_results(text, gMatches);
2079 for (var i=0; i<gMatches.length; i++) {
2080 var s = gMatches[i];
2081 }
2082
2083
2084 // Search for Google matches
2085 for (var i=0; i<GOOGLE_DATA.length; i++) {
2086 var s = GOOGLE_DATA[i];
2087 if (text.length != 0 &&
2088 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2089 gGoogleMatches[matchedCountGoogle] = s;
2090 matchedCountGoogle++;
2091 }
2092 }
2093 rank_autocomplete_api_results(text, gGoogleMatches);
2094 for (var i=0; i<gGoogleMatches.length; i++) {
2095 var s = gGoogleMatches[i];
2096 }
2097
2098 highlight_autocomplete_result_labels(text);
2099
2100
2101
2102 // Search for matching JD docs
2103 if (text.length >= 2) {
2104 // match only the beginning of a word
2105 var queryStr = text.toLowerCase();
2106
2107 // Search for Training classes
2108 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
2109 // current search comparison, with counters for tag and title,
2110 // used later to improve ranking
2111 var s = TRAINING_RESOURCES[i];
2112 s.matched_tag = 0;
2113 s.matched_title = 0;
2114 var matched = false;
2115
2116 // Check if query matches any tags; work backwards toward 1 to assist ranking
2117 for (var j = s.keywords.length - 1; j >= 0; j--) {
2118 // it matches a tag
2119 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
2120 matched = true;
2121 s.matched_tag = j + 1; // add 1 to index position
2122 }
2123 }
2124 // Don't consider doc title for lessons (only for class landing pages),
2125 // unless the lesson has a tag that already matches
2126 if ((s.lang == currentLang) &&
2127 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
2128 // it matches the doc title
2129 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
2130 matched = true;
2131 s.matched_title = 1;
2132 }
2133 }
2134 if (matched) {
2135 gDocsMatches[matchedCountDocs] = s;
2136 matchedCountDocs++;
2137 }
2138 }
2139
2140
2141 // Search for API Guides
2142 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2143 // current search comparison, with counters for tag and title,
2144 // used later to improve ranking
2145 var s = GUIDE_RESOURCES[i];
2146 s.matched_tag = 0;
2147 s.matched_title = 0;
2148 var matched = false;
2149
2150 // Check if query matches any tags; work backwards toward 1 to assist ranking
2151 for (var j = s.keywords.length - 1; j >= 0; j--) {
2152 // it matches a tag
2153 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
2154
2155 matched = true;
2156 s.matched_tag = j + 1; // add 1 to index position
2157 }
2158 }
2159 // Check if query matches the doc title, but only for current language
2160 if (s.lang == currentLang) {
2161 // if query matches the doc title
2162 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
2163 matched = true;
2164 s.matched_title = 1;
2165 }
2166 }
2167 if (matched) {
2168 gDocsMatches[matchedCountDocs] = s;
2169 matchedCountDocs++;
2170 }
2171 }
2172
2173
2174 // Search for Tools Guides
2175 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2176 // current search comparison, with counters for tag and title,
2177 // used later to improve ranking
2178 var s = TOOLS_RESOURCES[i];
2179 s.matched_tag = 0;
2180 s.matched_title = 0;
2181 var matched = false;
2182
2183 // Check if query matches any tags; work backwards toward 1 to assist ranking
2184 for (var j = s.keywords.length - 1; j >= 0; j--) {
2185 // it matches a tag
2186 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
2187 matched = true;
2188 s.matched_tag = j + 1; // add 1 to index position
2189 }
2190 }
2191 // Check if query matches the doc title, but only for current language
2192 if (s.lang == currentLang) {
2193 // if query matches the doc title
2194 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
2195 matched = true;
2196 s.matched_title = 1;
2197 }
2198 }
2199 if (matched) {
2200 gDocsMatches[matchedCountDocs] = s;
2201 matchedCountDocs++;
2202 }
2203 }
2204
2205
2206 // Search for About docs
2207 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2208 // current search comparison, with counters for tag and title,
2209 // used later to improve ranking
2210 var s = ABOUT_RESOURCES[i];
2211 s.matched_tag = 0;
2212 s.matched_title = 0;
2213 var matched = false;
2214
2215 // Check if query matches any tags; work backwards toward 1 to assist ranking
2216 for (var j = s.keywords.length - 1; j >= 0; j--) {
2217 // it matches a tag
2218 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
2219 matched = true;
2220 s.matched_tag = j + 1; // add 1 to index position
2221 }
2222 }
2223 // Check if query matches the doc title, but only for current language
2224 if (s.lang == currentLang) {
2225 // if query matches the doc title
2226 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
2227 matched = true;
2228 s.matched_title = 1;
2229 }
2230 }
2231 if (matched) {
2232 gDocsMatches[matchedCountDocs] = s;
2233 matchedCountDocs++;
2234 }
2235 }
2236
2237
2238 // Search for Design guides
2239 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2240 // current search comparison, with counters for tag and title,
2241 // used later to improve ranking
2242 var s = DESIGN_RESOURCES[i];
2243 s.matched_tag = 0;
2244 s.matched_title = 0;
2245 var matched = false;
2246
2247 // Check if query matches any tags; work backwards toward 1 to assist ranking
2248 for (var j = s.keywords.length - 1; j >= 0; j--) {
2249 // it matches a tag
2250 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
2251 matched = true;
2252 s.matched_tag = j + 1; // add 1 to index position
2253 }
2254 }
2255 // Check if query matches the doc title, but only for current language
2256 if (s.lang == currentLang) {
2257 // if query matches the doc title
2258 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
2259 matched = true;
2260 s.matched_title = 1;
2261 }
2262 }
2263 if (matched) {
2264 gDocsMatches[matchedCountDocs] = s;
2265 matchedCountDocs++;
2266 }
2267 }
2268
2269
2270 // Search for Distribute guides
2271 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2272 // current search comparison, with counters for tag and title,
2273 // used later to improve ranking
2274 var s = DISTRIBUTE_RESOURCES[i];
2275 s.matched_tag = 0;
2276 s.matched_title = 0;
2277 var matched = false;
2278
2279 // Check if query matches any tags; work backwards toward 1 to assist ranking
2280 for (var j = s.keywords.length - 1; j >= 0; j--) {
2281 // it matches a tag
2282 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
2283 matched = true;
2284 s.matched_tag = j + 1; // add 1 to index position
2285 }
2286 }
2287 // Check if query matches the doc title, but only for current language
2288 if (s.lang == currentLang) {
2289 // if query matches the doc title
2290 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
2291 matched = true;
2292 s.matched_title = 1;
2293 }
2294 }
2295 if (matched) {
2296 gDocsMatches[matchedCountDocs] = s;
2297 matchedCountDocs++;
2298 }
2299 }
2300
2301
2302 // Search for Google guides
2303 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2304 // current search comparison, with counters for tag and title,
2305 // used later to improve ranking
2306 var s = GOOGLE_RESOURCES[i];
2307 s.matched_tag = 0;
2308 s.matched_title = 0;
2309 var matched = false;
2310
2311 // Check if query matches any tags; work backwards toward 1 to assist ranking
2312 for (var j = s.keywords.length - 1; j >= 0; j--) {
2313 // it matches a tag
2314 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
2315 matched = true;
2316 s.matched_tag = j + 1; // add 1 to index position
2317 }
2318 }
2319 // Check if query matches the doc title, but only for current language
2320 if (s.lang == currentLang) {
2321 // if query matches the doc title
2322 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
2323 matched = true;
2324 s.matched_title = 1;
2325 }
2326 }
2327 if (matched) {
2328 gDocsMatches[matchedCountDocs] = s;
2329 matchedCountDocs++;
2330 }
2331 }
2332
2333
2334 // Search for Samples
2335 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2336 // current search comparison, with counters for tag and title,
2337 // used later to improve ranking
2338 var s = SAMPLES_RESOURCES[i];
2339 s.matched_tag = 0;
2340 s.matched_title = 0;
2341 var matched = false;
2342 // Check if query matches any tags; work backwards toward 1 to assist ranking
2343 for (var j = s.keywords.length - 1; j >= 0; j--) {
2344 // it matches a tag
2345 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
2346 matched = true;
2347 s.matched_tag = j + 1; // add 1 to index position
2348 }
2349 }
2350 // Check if query matches the doc title, but only for current language
2351 if (s.lang == currentLang) {
2352 // if query matches the doc title.t
2353 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
2354 matched = true;
2355 s.matched_title = 1;
2356 }
2357 }
2358 if (matched) {
2359 gDocsMatches[matchedCountDocs] = s;
2360 matchedCountDocs++;
2361 }
2362 }
2363
2364 // Search for Preview Guides
2365 for (var i=0; i<PREVIEW_RESOURCES.length; i++) {
2366 // current search comparison, with counters for tag and title,
2367 // used later to improve ranking
2368 var s = _RESOURCES[i];
2369 s.matched_tag = 0;
2370 s.matched_title = 0;
2371 var matched = false;
2372
2373 // Check if query matches any tags; work backwards toward 1 to assist ranking
2374 for (var j = s.keywords.length - 1; j >= 0; j--) {
2375 // it matches a tag
2376 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
2377 matched = true;
2378 s.matched_tag = j + 1; // add 1 to index position
2379 }
2380 }
2381 // Check if query matches the doc title, but only for current language
2382 if (s.lang == currentLang) {
2383 // if query matches the doc title
2384 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
2385 matched = true;
2386 s.matched_title = 1;
2387 }
2388 }
2389 if (matched) {
2390 gDocsMatches[matchedCountDocs] = s;
2391 matchedCountDocs++;
2392 }
2393 }
2394
2395 // Rank/sort all the matched pages
2396 rank_autocomplete_doc_results(text, gDocsMatches);
2397 }
2398
2399 // draw the suggestions
2400 sync_selection_table(toroot);
2401 return true; // allow the event to bubble up to the search api
2402 }
2403}
2404
2405/* Order the jd doc result list based on match quality */
2406function rank_autocomplete_doc_results(query, matches) {
2407 query = query || '';
2408 if (!matches || !matches.length)
2409 return;
2410
2411 var _resultScoreFn = function(match) {
2412 var score = 1.0;
2413
2414 // if the query matched a tag
2415 if (match.matched_tag > 0) {
2416 // multiply score by factor relative to position in tags list (max of 3)
2417 score *= 3 / match.matched_tag;
2418
2419 // if it also matched the title
2420 if (match.matched_title > 0) {
2421 score *= 2;
2422 }
2423 } else if (match.matched_title > 0) {
2424 score *= 3;
2425 }
2426
2427 return score;
2428 };
2429
2430 for (var i=0; i<matches.length; i++) {
2431 matches[i].__resultScore = _resultScoreFn(matches[i]);
2432 }
2433
2434 matches.sort(function(a,b){
2435 var n = b.__resultScore - a.__resultScore;
2436 if (n == 0) // lexicographical sort if scores are the same
2437 n = (a.label < b.label) ? -1 : 1;
2438 return n;
2439 });
2440}
2441
2442/* Order the result list based on match quality */
2443function rank_autocomplete_api_results(query, matches) {
2444 query = query || '';
2445 if (!matches || !matches.length)
2446 return;
2447
2448 // helper function that gets the last occurence index of the given regex
2449 // in the given string, or -1 if not found
2450 var _lastSearch = function(s, re) {
2451 if (s == '')
2452 return -1;
2453 var l = -1;
2454 var tmp;
2455 while ((tmp = s.search(re)) >= 0) {
2456 if (l < 0) l = 0;
2457 l += tmp;
2458 s = s.substr(tmp + 1);
2459 }
2460 return l;
2461 };
2462
2463 // helper function that counts the occurrences of a given character in
2464 // a given string
2465 var _countChar = function(s, c) {
2466 var n = 0;
2467 for (var i=0; i<s.length; i++)
2468 if (s.charAt(i) == c) ++n;
2469 return n;
2470 };
2471
2472 var queryLower = query.toLowerCase();
2473 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2474 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2475 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2476
2477 var _resultScoreFn = function(result) {
2478 // scores are calculated based on exact and prefix matches,
2479 // and then number of path separators (dots) from the last
2480 // match (i.e. favoring classes and deep package names)
2481 var score = 1.0;
2482 var labelLower = result.label.toLowerCase();
2483 var t;
2484 t = _lastSearch(labelLower, partExactAlnumRE);
2485 if (t >= 0) {
2486 // exact part match
2487 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2488 score *= 200 / (partsAfter + 1);
2489 } else {
2490 t = _lastSearch(labelLower, partPrefixAlnumRE);
2491 if (t >= 0) {
2492 // part prefix match
2493 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2494 score *= 20 / (partsAfter + 1);
2495 }
2496 }
2497
2498 return score;
2499 };
2500
2501 for (var i=0; i<matches.length; i++) {
2502 // if the API is deprecated, default score is 0; otherwise, perform scoring
2503 if (matches[i].deprecated == "true") {
2504 matches[i].__resultScore = 0;
2505 } else {
2506 matches[i].__resultScore = _resultScoreFn(matches[i]);
2507 }
2508 }
2509
2510 matches.sort(function(a,b){
2511 var n = b.__resultScore - a.__resultScore;
2512 if (n == 0) // lexicographical sort if scores are the same
2513 n = (a.label < b.label) ? -1 : 1;
2514 return n;
2515 });
2516}
2517
2518/* Add emphasis to part of string that matches query */
2519function highlight_autocomplete_result_labels(query) {
2520 query = query || '';
2521 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
2522 return;
2523
2524 var queryLower = query.toLowerCase();
2525 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2526 var queryRE = new RegExp(
2527 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2528 for (var i=0; i<gMatches.length; i++) {
2529 gMatches[i].__hilabel = gMatches[i].label.replace(
2530 queryRE, '<b>$1</b>');
2531 }
2532 for (var i=0; i<gGoogleMatches.length; i++) {
2533 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2534 queryRE, '<b>$1</b>');
2535 }
2536}
2537
2538function search_focus_changed(obj, focused)
2539{
2540 if (!focused) {
2541 if(obj.value == ""){
2542 $("#search-close").addClass("hide");
2543 }
2544 $(".suggest-card").hide();
2545 }
2546}
2547
2548function submit_search() {
2549 var query = document.getElementById('search_autocomplete').value;
2550 location.hash = 'q=' + query;
2551 loadSearchResults();
2552 $("#searchResults").slideDown('slow', setStickyTop);
2553 return false;
2554}
2555
2556
2557function hideResults() {
2558 $("#searchResults").slideUp('fast', setStickyTop);
2559 $("#search-close").addClass("hide");
2560 location.hash = '';
2561
2562 $("#search_autocomplete").val("").blur();
2563
2564 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2565 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
2566
2567 // forcefully regain key-up event control (previously jacked by search api)
2568 $("#search_autocomplete").keyup(function(event) {
2569 return search_changed(event, false, toRoot);
2570 });
2571
2572 return false;
2573}
2574
2575
2576
2577/* ########################################################## */
2578/* ################ CUSTOM SEARCH ENGINE ################## */
2579/* ########################################################## */
2580
2581var searchControl;
2582google.load('search', '1', {"callback" : function() {
2583 searchControl = new google.search.SearchControl();
2584 } });
2585
2586function loadSearchResults() {
2587 document.getElementById("search_autocomplete").style.color = "#000";
2588
2589 searchControl = new google.search.SearchControl();
2590
2591 // use our existing search form and use tabs when multiple searchers are used
2592 drawOptions = new google.search.DrawOptions();
2593 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2594 drawOptions.setInput(document.getElementById("search_autocomplete"));
2595
2596 // configure search result options
2597 searchOptions = new google.search.SearcherOptions();
2598 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2599
2600 // configure each of the searchers, for each tab
2601 devSiteSearcher = new google.search.WebSearch();
2602 devSiteSearcher.setUserDefinedLabel("All");
2603 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2604
2605 designSearcher = new google.search.WebSearch();
2606 designSearcher.setUserDefinedLabel("Design");
2607 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2608
2609 trainingSearcher = new google.search.WebSearch();
2610 trainingSearcher.setUserDefinedLabel("Training");
2611 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2612
2613 guidesSearcher = new google.search.WebSearch();
2614 guidesSearcher.setUserDefinedLabel("Guides");
2615 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2616
2617 referenceSearcher = new google.search.WebSearch();
2618 referenceSearcher.setUserDefinedLabel("Reference");
2619 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2620
2621 googleSearcher = new google.search.WebSearch();
2622 googleSearcher.setUserDefinedLabel("Google Services");
2623 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2624
2625 blogSearcher = new google.search.WebSearch();
2626 blogSearcher.setUserDefinedLabel("Blog");
2627 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2628
2629 // add each searcher to the search control
2630 searchControl.addSearcher(devSiteSearcher, searchOptions);
2631 searchControl.addSearcher(designSearcher, searchOptions);
2632 searchControl.addSearcher(trainingSearcher, searchOptions);
2633 searchControl.addSearcher(guidesSearcher, searchOptions);
2634 searchControl.addSearcher(referenceSearcher, searchOptions);
2635 searchControl.addSearcher(googleSearcher, searchOptions);
2636 searchControl.addSearcher(blogSearcher, searchOptions);
2637
2638 // configure result options
2639 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2640 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2641 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2642 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2643
2644 // upon ajax search, refresh the url and search title
2645 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2646 updateResultTitle(query);
2647 var query = document.getElementById('search_autocomplete').value;
2648 location.hash = 'q=' + query;
2649 });
2650
2651 // once search results load, set up click listeners
2652 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2653 addResultClickListeners();
2654 });
2655
2656 // draw the search results box
2657 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2658
2659 // get query and execute the search
2660 searchControl.execute(decodeURI(getQuery(location.hash)));
2661
2662 document.getElementById("search_autocomplete").focus();
2663 addTabListeners();
2664}
2665// End of loadSearchResults
2666
2667
2668google.setOnLoadCallback(function(){
2669 if (location.hash.indexOf("q=") == -1) {
2670 // if there's no query in the url, don't search and make sure results are hidden
2671 $('#searchResults').hide();
2672 return;
2673 } else {
2674 // first time loading search results for this page
2675 $('#searchResults').slideDown('slow', setStickyTop);
2676 $("#search-close").removeClass("hide");
2677 loadSearchResults();
2678 }
2679}, true);
2680
2681/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2682 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
2683function offsetScrollForSticky() {
2684 // Ignore if there's no search bar (some special pages have no header)
2685 if ($("#search-container").length < 1) return;
2686
2687 var hash = escape(location.hash.substr(1));
2688 var $matchingElement = $("#"+hash);
2689 // Sanity check that there's an element with that ID on the page
2690 if ($matchingElement.length) {
2691 // If the position of the target element is near the top of the page (<20px, where we expect it
2692 // to be because we need to move it down 60px to become in view), then move it down 60px
2693 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2694 $(window).scrollTop($(window).scrollTop() - 60);
2695 }
2696 }
2697}
2698
2699// when an event on the browser history occurs (back, forward, load) requery hash and do search
2700$(window).hashchange( function(){
2701 // Ignore if there's no search bar (some special pages have no header)
2702 if ($("#search-container").length < 1) return;
2703
2704 // If the hash isn't a search query or there's an error in the query,
2705 // then adjust the scroll position to account for sticky header, then exit.
2706 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2707 // If the results pane is open, close it.
2708 if (!$("#searchResults").is(":hidden")) {
2709 hideResults();
2710 }
2711 offsetScrollForSticky();
2712 return;
2713 }
2714
2715 // Otherwise, we have a search to do
2716 var query = decodeURI(getQuery(location.hash));
2717 searchControl.execute(query);
2718 $('#searchResults').slideDown('slow', setStickyTop);
2719 $("#search_autocomplete").focus();
2720 $("#search-close").removeClass("hide");
2721
2722 updateResultTitle(query);
2723});
2724
2725function updateResultTitle(query) {
2726 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2727}
2728
2729// forcefully regain key-up event control (previously jacked by search api)
2730$("#search_autocomplete").keyup(function(event) {
2731 return search_changed(event, false, toRoot);
2732});
2733
2734// add event listeners to each tab so we can track the browser history
2735function addTabListeners() {
2736 var tabHeaders = $(".gsc-tabHeader");
2737 for (var i = 0; i < tabHeaders.length; i++) {
2738 $(tabHeaders[i]).attr("id",i).click(function() {
2739 /*
2740 // make a copy of the page numbers for the search left pane
2741 setTimeout(function() {
2742 // remove any residual page numbers
2743 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
2744 // move the page numbers to the left position; make a clone,
2745 // because the element is drawn to the DOM only once
2746 // and because we're going to remove it (previous line),
2747 // we need it to be available to move again as the user navigates
2748 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2749 .clone().appendTo('#searchResults .gsc-tabsArea');
2750 }, 200);
2751 */
2752 });
2753 }
2754 setTimeout(function(){$(tabHeaders[0]).click()},200);
2755}
2756
2757// add analytics tracking events to each result link
2758function addResultClickListeners() {
2759 $("#searchResults a.gs-title").each(function(index, link) {
2760 // When user clicks enter for Google search results, track it
2761 $(link).click(function() {
2762 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'),
2763 'query: ' + $("#search_autocomplete").val().toLowerCase());
2764 });
2765 });
2766}
2767
2768
2769function getQuery(hash) {
2770 var queryParts = hash.split('=');
2771 return queryParts[1];
2772}
2773
2774/* returns the given string with all HTML brackets converted to entities
2775 TODO: move this to the site's JS library */
2776function escapeHTML(string) {
2777 return string.replace(/</g,"&lt;")
2778 .replace(/>/g,"&gt;");
2779}
2780
2781
2782
2783
2784
2785
2786
2787/* ######################################################## */
2788/* ################# JAVADOC REFERENCE ################### */
2789/* ######################################################## */
2790
2791/* Initialize some droiddoc stuff, but only if we're in the reference */
2792if (location.pathname.indexOf("/reference") == 0) {
2793 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2794 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2795 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
2796 $(document).ready(function() {
2797 // init available apis based on user pref
2798 changeApiLevel();
2799 initSidenavHeightResize()
2800 });
2801 }
2802}
2803
2804var API_LEVEL_COOKIE = "api_level";
2805var minLevel = 1;
2806var maxLevel = 1;
2807
2808/******* SIDENAV DIMENSIONS ************/
2809
2810 function initSidenavHeightResize() {
2811 // Change the drag bar size to nicely fit the scrollbar positions
2812 var $dragBar = $(".ui-resizable-s");
2813 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
2814
2815 $( "#resize-packages-nav" ).resizable({
2816 containment: "#nav-panels",
2817 handles: "s",
2818 alsoResize: "#packages-nav",
2819 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2820 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2821 });
2822
2823 }
2824
2825function updateSidenavFixedWidth() {
2826 if (!sticky) return;
2827 $('#devdoc-nav').css({
2828 'width' : $('#side-nav').css('width'),
2829 'margin' : $('#side-nav').css('margin')
2830 });
2831 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
2832
2833 initSidenavHeightResize();
2834}
2835
2836function updateSidenavFullscreenWidth() {
2837 if (!sticky) return;
2838 $('#devdoc-nav').css({
2839 'width' : $('#side-nav').css('width'),
2840 'margin' : $('#side-nav').css('margin')
2841 });
2842 $('#devdoc-nav .totop').css({'left': 'inherit'});
2843
2844 initSidenavHeightResize();
2845}
2846
2847function buildApiLevelSelector() {
2848 maxLevel = SINCE_DATA.length;
2849 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2850 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2851
2852 minLevel = parseInt($("#doc-api-level").attr("class"));
2853 // Handle provisional api levels; the provisional level will always be the highest possible level
2854 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2855 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2856 if (isNaN(minLevel) && minLevel.length) {
2857 minLevel = maxLevel;
2858 }
2859 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2860 for (var i = maxLevel-1; i >= 0; i--) {
2861 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2862 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2863 select.append(option);
2864 }
2865
2866 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2867 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2868 selectedLevelItem.setAttribute('selected',true);
2869}
2870
2871function changeApiLevel() {
2872 maxLevel = SINCE_DATA.length;
2873 var selectedLevel = maxLevel;
2874
2875 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2876 toggleVisisbleApis(selectedLevel, "body");
2877
2878 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
2879
2880 if (selectedLevel < minLevel) {
2881 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
2882 $("#naMessage").show().html("<div><p><strong>This " + thing
2883 + " requires API level " + minLevel + " or higher.</strong></p>"
2884 + "<p>This document is hidden because your selected API level for the documentation is "
2885 + selectedLevel + ". You can change the documentation API level with the selector "
2886 + "above the left navigation.</p>"
2887 + "<p>For more information about specifying the API level your app requires, "
2888 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2889 + ">Supporting Different Platform Versions</a>.</p>"
2890 + "<input type='button' value='OK, make this page visible' "
2891 + "title='Change the API level to " + minLevel + "' "
2892 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2893 + "</div>");
2894 } else {
2895 $("#naMessage").hide();
2896 }
2897}
2898
2899function toggleVisisbleApis(selectedLevel, context) {
2900 var apis = $(".api",context);
2901 apis.each(function(i) {
2902 var obj = $(this);
2903 var className = obj.attr("class");
2904 var apiLevelIndex = className.lastIndexOf("-")+1;
2905 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2906 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2907 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2908 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2909 return;
2910 }
2911 apiLevel = parseInt(apiLevel);
2912
2913 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2914 var selectedLevelNum = parseInt(selectedLevel)
2915 var apiLevelNum = parseInt(apiLevel);
2916 if (isNaN(apiLevelNum)) {
2917 apiLevelNum = maxLevel;
2918 }
2919
2920 // Grey things out that aren't available and give a tooltip title
2921 if (apiLevelNum > selectedLevelNum) {
2922 obj.addClass("absent").attr("title","Requires API Level \""
2923 + apiLevel + "\" or higher. To reveal, change the target API level "
2924 + "above the left navigation.");
2925 }
2926 else obj.removeClass("absent").removeAttr("title");
2927 });
2928}
2929
2930
2931
2932
2933/* ################# SIDENAV TREE VIEW ################### */
2934
2935function new_node(me, mom, text, link, children_data, api_level)
2936{
2937 var node = new Object();
2938 node.children = Array();
2939 node.children_data = children_data;
2940 node.depth = mom.depth + 1;
2941
2942 node.li = document.createElement("li");
2943 mom.get_children_ul().appendChild(node.li);
2944
2945 node.label_div = document.createElement("div");
2946 node.label_div.className = "label";
2947 if (api_level != null) {
2948 $(node.label_div).addClass("api");
2949 $(node.label_div).addClass("api-level-"+api_level);
2950 }
2951 node.li.appendChild(node.label_div);
2952
2953 if (children_data != null) {
2954 node.expand_toggle = document.createElement("a");
2955 node.expand_toggle.href = "javascript:void(0)";
2956 node.expand_toggle.onclick = function() {
2957 if (node.expanded) {
2958 $(node.get_children_ul()).slideUp("fast");
2959 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2960 node.expanded = false;
2961 } else {
2962 expand_node(me, node);
2963 }
2964 };
2965 node.label_div.appendChild(node.expand_toggle);
2966
2967 node.plus_img = document.createElement("img");
2968 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2969 node.plus_img.className = "plus";
2970 node.plus_img.width = "8";
2971 node.plus_img.border = "0";
2972 node.expand_toggle.appendChild(node.plus_img);
2973
2974 node.expanded = false;
2975 }
2976
2977 var a = document.createElement("a");
2978 node.label_div.appendChild(a);
2979 node.label = document.createTextNode(text);
2980 a.appendChild(node.label);
2981 if (link) {
2982 a.href = me.toroot + link;
2983 } else {
2984 if (children_data != null) {
2985 a.className = "nolink";
2986 a.href = "javascript:void(0)";
2987 a.onclick = node.expand_toggle.onclick;
2988 // This next line shouldn't be necessary. I'll buy a beer for the first
2989 // person who figures out how to remove this line and have the link
2990 // toggle shut on the first try. --joeo@android.com
2991 node.expanded = false;
2992 }
2993 }
2994
2995
2996 node.children_ul = null;
2997 node.get_children_ul = function() {
2998 if (!node.children_ul) {
2999 node.children_ul = document.createElement("ul");
3000 node.children_ul.className = "children_ul";
3001 node.children_ul.style.display = "none";
3002 node.li.appendChild(node.children_ul);
3003 }
3004 return node.children_ul;
3005 };
3006
3007 return node;
3008}
3009
3010
3011
3012
3013function expand_node(me, node)
3014{
3015 if (node.children_data && !node.expanded) {
3016 if (node.children_visited) {
3017 $(node.get_children_ul()).slideDown("fast");
3018 } else {
3019 get_node(me, node);
3020 if ($(node.label_div).hasClass("absent")) {
3021 $(node.get_children_ul()).addClass("absent");
3022 }
3023 $(node.get_children_ul()).slideDown("fast");
3024 }
3025 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
3026 node.expanded = true;
3027
3028 // perform api level toggling because new nodes are new to the DOM
3029 var selectedLevel = $("#apiLevelSelector option:selected").val();
3030 toggleVisisbleApis(selectedLevel, "#side-nav");
3031 }
3032}
3033
3034function get_node(me, mom)
3035{
3036 mom.children_visited = true;
3037 for (var i in mom.children_data) {
3038 var node_data = mom.children_data[i];
3039 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
3040 node_data[2], node_data[3]);
3041 }
3042}
3043
3044function this_page_relative(toroot)
3045{
3046 var full = document.location.pathname;
3047 var file = "";
3048 if (toroot.substr(0, 1) == "/") {
3049 if (full.substr(0, toroot.length) == toroot) {
3050 return full.substr(toroot.length);
3051 } else {
3052 // the file isn't under toroot. Fail.
3053 return null;
3054 }
3055 } else {
3056 if (toroot != "./") {
3057 toroot = "./" + toroot;
3058 }
3059 do {
3060 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3061 var pos = full.lastIndexOf("/");
3062 file = full.substr(pos) + file;
3063 full = full.substr(0, pos);
3064 toroot = toroot.substr(0, toroot.length-3);
3065 }
3066 } while (toroot != "" && toroot != "/");
3067 return file.substr(1);
3068 }
3069}
3070
3071function find_page(url, data)
3072{
3073 var nodes = data;
3074 var result = null;
3075 for (var i in nodes) {
3076 var d = nodes[i];
3077 if (d[1] == url) {
3078 return new Array(i);
3079 }
3080 else if (d[2] != null) {
3081 result = find_page(url, d[2]);
3082 if (result != null) {
3083 return (new Array(i).concat(result));
3084 }
3085 }
3086 }
3087 return null;
3088}
3089
3090function init_default_navtree(toroot) {
3091 // load json file for navtree data
3092 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3093 // when the file is loaded, initialize the tree
3094 if(jqxhr.status === 200) {
3095 init_navtree("tree-list", toroot, NAVTREE_DATA);
3096 }
3097 });
3098
3099 // perform api level toggling because because the whole tree is new to the DOM
3100 var selectedLevel = $("#apiLevelSelector option:selected").val();
3101 toggleVisisbleApis(selectedLevel, "#side-nav");
3102}
3103
3104function init_navtree(navtree_id, toroot, root_nodes)
3105{
3106 var me = new Object();
3107 me.toroot = toroot;
3108 me.node = new Object();
3109
3110 me.node.li = document.getElementById(navtree_id);
3111 me.node.children_data = root_nodes;
3112 me.node.children = new Array();
3113 me.node.children_ul = document.createElement("ul");
3114 me.node.get_children_ul = function() { return me.node.children_ul; };
3115 //me.node.children_ul.className = "children_ul";
3116 me.node.li.appendChild(me.node.children_ul);
3117 me.node.depth = 0;
3118
3119 get_node(me, me.node);
3120
3121 me.this_page = this_page_relative(toroot);
3122 me.breadcrumbs = find_page(me.this_page, root_nodes);
3123 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3124 var mom = me.node;
3125 for (var i in me.breadcrumbs) {
3126 var j = me.breadcrumbs[i];
3127 mom = mom.children[j];
3128 expand_node(me, mom);
3129 }
3130 mom.label_div.className = mom.label_div.className + " selected";
3131 addLoadEvent(function() {
3132 scrollIntoView("nav-tree");
3133 });
3134 }
3135}
3136
3137
3138
3139
3140
3141
3142
3143
3144/* TODO: eliminate redundancy with non-google functions */
3145function init_google_navtree(navtree_id, toroot, root_nodes)
3146{
3147 var me = new Object();
3148 me.toroot = toroot;
3149 me.node = new Object();
3150
3151 me.node.li = document.getElementById(navtree_id);
3152 if (!me.node.li) {
3153 return;
3154 }
3155
3156 me.node.children_data = root_nodes;
3157 me.node.children = new Array();
3158 me.node.children_ul = document.createElement("ul");
3159 me.node.get_children_ul = function() { return me.node.children_ul; };
3160 //me.node.children_ul.className = "children_ul";
3161 me.node.li.appendChild(me.node.children_ul);
3162 me.node.depth = 0;
3163
3164 get_google_node(me, me.node);
3165}
3166
3167function new_google_node(me, mom, text, link, children_data, api_level)
3168{
3169 var node = new Object();
3170 var child;
3171 node.children = Array();
3172 node.children_data = children_data;
3173 node.depth = mom.depth + 1;
3174 node.get_children_ul = function() {
3175 if (!node.children_ul) {
3176 node.children_ul = document.createElement("ul");
3177 node.children_ul.className = "tree-list-children";
3178 node.li.appendChild(node.children_ul);
3179 }
3180 return node.children_ul;
3181 };
3182 node.li = document.createElement("li");
3183
3184 mom.get_children_ul().appendChild(node.li);
3185
3186
3187 if(link) {
3188 child = document.createElement("a");
3189
3190 }
3191 else {
3192 child = document.createElement("span");
3193 child.className = "tree-list-subtitle";
3194
3195 }
3196 if (children_data != null) {
3197 node.li.className="nav-section";
3198 node.label_div = document.createElement("div");
3199 node.label_div.className = "nav-section-header-ref";
3200 node.li.appendChild(node.label_div);
3201 get_google_node(me, node);
3202 node.label_div.appendChild(child);
3203 }
3204 else {
3205 node.li.appendChild(child);
3206 }
3207 if(link) {
3208 child.href = me.toroot + link;
3209 }
3210 node.label = document.createTextNode(text);
3211 child.appendChild(node.label);
3212
3213 node.children_ul = null;
3214
3215 return node;
3216}
3217
3218function get_google_node(me, mom)
3219{
3220 mom.children_visited = true;
3221 var linkText;
3222 for (var i in mom.children_data) {
3223 var node_data = mom.children_data[i];
3224 linkText = node_data[0];
3225
3226 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3227 linkText = linkText.substr(19, linkText.length);
3228 }
3229 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3230 node_data[2], node_data[3]);
3231 }
3232}
3233
3234
3235
3236
3237
3238
3239/****** NEW version of script to build google and sample navs dynamically ******/
3240// TODO: update Google reference docs to tolerate this new implementation
3241
3242var NODE_NAME = 0;
3243var NODE_HREF = 1;
3244var NODE_GROUP = 2;
3245var NODE_TAGS = 3;
3246var NODE_CHILDREN = 4;
3247
3248function init_google_navtree2(navtree_id, data)
3249{
3250 var $containerUl = $("#"+navtree_id);
3251 for (var i in data) {
3252 var node_data = data[i];
3253 $containerUl.append(new_google_node2(node_data));
3254 }
3255
3256 // Make all third-generation list items 'sticky' to prevent them from collapsing
3257 $containerUl.find('li li li.nav-section').addClass('sticky');
3258
3259 initExpandableNavItems("#"+navtree_id);
3260}
3261
3262function new_google_node2(node_data)
3263{
3264 var linkText = node_data[NODE_NAME];
3265 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3266 linkText = linkText.substr(19, linkText.length);
3267 }
3268 var $li = $('<li>');
3269 var $a;
3270 if (node_data[NODE_HREF] != null) {
3271 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3272 + linkText + '</a>');
3273 } else {
3274 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3275 + linkText + '/</a>');
3276 }
3277 var $childUl = $('<ul>');
3278 if (node_data[NODE_CHILDREN] != null) {
3279 $li.addClass("nav-section");
3280 $a = $('<div class="nav-section-header">').append($a);
3281 if (node_data[NODE_HREF] == null) $a.addClass('empty');
3282
3283 for (var i in node_data[NODE_CHILDREN]) {
3284 var child_node_data = node_data[NODE_CHILDREN][i];
3285 $childUl.append(new_google_node2(child_node_data));
3286 }
3287 $li.append($childUl);
3288 }
3289 $li.prepend($a);
3290
3291 return $li;
3292}
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304function showGoogleRefTree() {
3305 init_default_google_navtree(toRoot);
3306 init_default_gcm_navtree(toRoot);
3307}
3308
3309function init_default_google_navtree(toroot) {
3310 // load json file for navtree data
3311 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3312 // when the file is loaded, initialize the tree
3313 if(jqxhr.status === 200) {
3314 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3315 highlightSidenav();
3316 resizeNav();
3317 }
3318 });
3319}
3320
3321function init_default_gcm_navtree(toroot) {
3322 // load json file for navtree data
3323 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3324 // when the file is loaded, initialize the tree
3325 if(jqxhr.status === 200) {
3326 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3327 highlightSidenav();
3328 resizeNav();
3329 }
3330 });
3331}
3332
3333function showSamplesRefTree() {
3334 init_default_samples_navtree(toRoot);
3335}
3336
3337function init_default_samples_navtree(toroot) {
3338 // load json file for navtree data
3339 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3340 // when the file is loaded, initialize the tree
3341 if(jqxhr.status === 200) {
3342 // hack to remove the "about the samples" link then put it back in
3343 // after we nuke the list to remove the dummy static list of samples
3344 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3345 $("#nav.samples-nav").empty();
3346 $("#nav.samples-nav").append($firstLi);
3347
3348 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
3349 highlightSidenav();
3350 resizeNav();
3351 if ($("#jd-content #samples").length) {
3352 showSamples();
3353 }
3354 }
3355 });
3356}
3357
3358/* TOGGLE INHERITED MEMBERS */
3359
3360/* Toggle an inherited class (arrow toggle)
3361 * @param linkObj The link that was clicked.
3362 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3363 * 'null' to simply toggle.
3364 */
3365function toggleInherited(linkObj, expand) {
3366 var base = linkObj.getAttribute("id");
3367 var list = document.getElementById(base + "-list");
3368 var summary = document.getElementById(base + "-summary");
3369 var trigger = document.getElementById(base + "-trigger");
3370 var a = $(linkObj);
3371 if ( (expand == null && a.hasClass("closed")) || expand ) {
3372 list.style.display = "none";
3373 summary.style.display = "block";
3374 trigger.src = toRoot + "assets/images/triangle-opened.png";
3375 a.removeClass("closed");
3376 a.addClass("opened");
3377 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3378 list.style.display = "block";
3379 summary.style.display = "none";
3380 trigger.src = toRoot + "assets/images/triangle-closed.png";
3381 a.removeClass("opened");
3382 a.addClass("closed");
3383 }
3384 return false;
3385}
3386
3387/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3388 * @param linkObj The link that was clicked.
3389 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3390 * 'null' to simply toggle.
3391 */
3392function toggleAllInherited(linkObj, expand) {
3393 var a = $(linkObj);
3394 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3395 var expandos = $(".jd-expando-trigger", table);
3396 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3397 expandos.each(function(i) {
3398 toggleInherited(this, true);
3399 });
3400 a.text("[Collapse]");
3401 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3402 expandos.each(function(i) {
3403 toggleInherited(this, false);
3404 });
3405 a.text("[Expand]");
3406 }
3407 return false;
3408}
3409
3410/* Toggle all inherited members in the class (link in the class title)
3411 */
3412function toggleAllClassInherited() {
3413 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3414 var toggles = $(".toggle-all", $("#body-content"));
3415 if (a.text() == "[Expand All]") {
3416 toggles.each(function(i) {
3417 toggleAllInherited(this, true);
3418 });
3419 a.text("[Collapse All]");
3420 } else {
3421 toggles.each(function(i) {
3422 toggleAllInherited(this, false);
3423 });
3424 a.text("[Expand All]");
3425 }
3426 return false;
3427}
3428
3429/* Expand all inherited members in the class. Used when initiating page search */
3430function ensureAllInheritedExpanded() {
3431 var toggles = $(".toggle-all", $("#body-content"));
3432 toggles.each(function(i) {
3433 toggleAllInherited(this, true);
3434 });
3435 $("#toggleAllClassInherited").text("[Collapse All]");
3436}
3437
3438
3439/* HANDLE KEY EVENTS
3440 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3441 */
3442var agent = navigator['userAgent'].toLowerCase();
3443var mac = agent.indexOf("macintosh") != -1;
3444
3445$(document).keydown( function(e) {
3446var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3447 if (control && e.which == 70) { // 70 is "F"
3448 ensureAllInheritedExpanded();
3449 }
3450});
3451
3452
3453
3454
3455
3456
3457/* On-demand functions */
3458
3459/** Move sample code line numbers out of PRE block and into non-copyable column */
3460function initCodeLineNumbers() {
3461 var numbers = $("#codesample-block a.number");
3462 if (numbers.length) {
3463 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3464 }
3465
3466 $(document).ready(function() {
3467 // select entire line when clicked
3468 $("span.code-line").click(function() {
3469 if (!shifted) {
3470 selectText(this);
3471 }
3472 });
3473 // invoke line link on double click
3474 $(".code-line").dblclick(function() {
3475 document.location.hash = $(this).attr('id');
3476 });
3477 // highlight the line when hovering on the number
3478 $("#codesample-line-numbers a.number").mouseover(function() {
3479 var id = $(this).attr('href');
3480 $(id).css('background','#e7e7e7');
3481 });
3482 $("#codesample-line-numbers a.number").mouseout(function() {
3483 var id = $(this).attr('href');
3484 $(id).css('background','none');
3485 });
3486 });
3487}
3488
3489// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3490var shifted = false;
3491$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3492
3493// courtesy of jasonedelman.com
3494function selectText(element) {
3495 var doc = document
3496 , range, selection
3497 ;
3498 if (doc.body.createTextRange) { //ms
3499 range = doc.body.createTextRange();
3500 range.moveToElementText(element);
3501 range.select();
3502 } else if (window.getSelection) { //all others
3503 selection = window.getSelection();
3504 range = doc.createRange();
3505 range.selectNodeContents(element);
3506 selection.removeAllRanges();
3507 selection.addRange(range);
3508 }
3509}
3510
3511
3512
3513
3514/** Display links and other information about samples that match the
3515 group specified by the URL */
3516function showSamples() {
3517 var group = $("#samples").attr('class');
3518 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3519
3520 var $ul = $("<ul>");
3521 $selectedLi = $("#nav li.selected");
3522
3523 $selectedLi.children("ul").children("li").each(function() {
3524 var $li = $("<li>").append($(this).find("a").first().clone());
3525 $ul.append($li);
3526 });
3527
3528 $("#samples").append($ul);
3529
3530}
3531
3532
3533
3534/* ########################################################## */
3535/* ################### RESOURCE CARDS ##################### */
3536/* ########################################################## */
3537
3538/** Handle resource queries, collections, and grids (sections). Requires
3539 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3540
3541(function() {
3542 // Prevent the same resource from being loaded more than once per page.
3543 var addedPageResources = {};
3544
3545 $(document).ready(function() {
3546 // Need to initialize hero carousel before other sections for dedupe
3547 // to work correctly.
3548 $('[data-carousel-query]').dacCarouselQuery();
3549
3550 $('.resource-widget').each(function() {
3551 initResourceWidget(this);
3552 });
3553
3554 /* Pass the line height to ellipsisfade() to adjust the height of the
3555 text container to show the max number of lines possible, without
3556 showing lines that are cut off. This works with the css ellipsis
3557 classes to fade last text line and apply an ellipsis char. */
3558
3559 //card text currently uses 20px line height.
3560 var lineHeight = 20;
3561 $('.card-info .text').ellipsisfade(lineHeight);
3562 });
3563
3564 /*
3565 Three types of resource layouts:
3566 Flow - Uses a fixed row-height flow using float left style.
3567 Carousel - Single card slideshow all same dimension absolute.
3568 Stack - Uses fixed columns and flexible element height.
3569 */
3570 function initResourceWidget(widget) {
3571 var $widget = $(widget);
3572 var isFlow = $widget.hasClass('resource-flow-layout'),
3573 isCarousel = $widget.hasClass('resource-carousel-layout'),
3574 isStack = $widget.hasClass('resource-stack-layout');
3575
3576 // remove illegal col-x class which is not relevant anymore thanks to responsive styles.
3577 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
3578 if (m && !$widget.is('.cols > *')) {
3579 $widget.removeClass('col-' + m[1]);
3580 }
3581
3582 var opts = {
3583 cardSizes: ($widget.data('cardsizes') || '').split(','),
3584 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3585 initialResults: $widget.data('initialResults'),
3586 itemsPerPage: $widget.data('itemsperpage'),
3587 sortOrder: $widget.data('sortorder'),
3588 query: $widget.data('query'),
3589 section: $widget.data('section'),
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 = false; // stop showing plusone buttons on cards
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 = false; // stop showing plusone buttons on 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 function createResponsiveFlowColumn(cardSize) {
3752 var cardWidth = parseInt(cardSize.match(/(\d+)/)[1], 10);
3753 var column = $('<div>').addClass('col-' + (cardWidth / 3) + 'of6');
3754 if (cardWidth < 9) {
3755 column.addClass('col-tablet-1of2');
3756 } else if (cardWidth > 9 && cardWidth < 18) {
3757 column.addClass('col-tablet-1of1');
3758 }
3759 if (cardWidth < 18) {
3760 column.addClass('col-mobile-1of1')
3761 }
3762 return column;
3763 }
3764
3765 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3766 function drawResourcesFlowWidget($widget, opts, resources) {
3767 $widget.empty().addClass('cols');
3768 var cardSizes = opts.cardSizes || ['6x6'];
3769 var initialResults = opts.initialResults || resources.length;
3770 var i = 0, j = 0;
3771 var plusone = false; // stop showing plusone buttons on cards
3772 var cardParent = $widget;
3773
3774 while (i < resources.length) {
3775
3776 if (i === initialResults && initialResults < resources.length) {
3777 // Toggle remaining cards
3778 cardParent = $('<div class="dac-toggle-content clearfix">').appendTo($widget);
3779 $widget.addClass('dac-toggle');
3780 $('<div class="col-1of1 dac-section-links dac-text-center">')
3781 .append(
3782 $('<div class="dac-section-link" data-toggle="section">')
3783 .append('<span class="dac-toggle-expand">More<i class="dac-sprite dac-auto-unfold-more"></i></span>')
3784 .append('<span class="dac-toggle-collapse">Less<i class="dac-sprite dac-auto-unfold-less"></i></span>')
3785 )
3786 .appendTo($widget)
3787 }
3788
3789 var cardSize = cardSizes[j++ % cardSizes.length];
3790 cardSize = cardSize.replace(/^\s+|\s+$/,'');
3791
3792 var column = createResponsiveFlowColumn(cardSize).appendTo(cardParent);
3793
3794 // A stack has a third dimension which is the number of stacked items
3795 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3796 var stackCount = 0;
3797 var $stackDiv = null;
3798
3799 if (isStack) {
3800 // Create a stack container which should have the dimensions defined
3801 // by the product of the items inside.
3802 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
3803 + 'x' + isStack[2] * isStack[3]) .appendTo(column);
3804 }
3805
3806 // Build each stack item or just a single item
3807 do {
3808 var resource = resources[i];
3809
3810 var $card = createResourceElement(resources[i], opts, plusone);
3811
3812 $card.addClass('resource-card-' + cardSize +
3813 ' resource-card-' + resource.type);
3814
3815 if (isStack) {
3816 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3817 if (++stackCount == parseInt(isStack[3])) {
3818 $card.addClass('resource-card-row-stack-last');
3819 stackCount = 0;
3820 }
3821 } else {
3822 stackCount = 0;
3823 }
3824
3825 $card.appendTo($stackDiv || column);
3826
3827 } while (++i < resources.length && stackCount > 0);
3828 }
3829 }
3830
3831 /* Build a site map of resources using a section as a root. */
3832 function buildSectionList(opts) {
3833 if (opts.section && SECTION_BY_ID[opts.section]) {
3834 return SECTION_BY_ID[opts.section].sections || [];
3835 }
3836 return [];
3837 }
3838
3839 function buildResourceList(opts) {
3840 return $.queryResources(opts);
3841 }
3842
3843 $.queryResources = function(opts) {
3844 var maxResults = opts.maxResults || 100;
3845
3846 var query = opts.query || '';
3847 var expressions = parseResourceQuery(query);
3848 var addedResourceIndices = {};
3849 var results = [];
3850
3851 for (var i = 0; i < expressions.length; i++) {
3852 var clauses = expressions[i];
3853
3854 // build initial set of resources from first clause
3855 var firstClause = clauses[0];
3856 var resources = [];
3857 switch (firstClause.attr) {
3858 case 'type':
3859 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3860 break;
3861 case 'lang':
3862 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3863 break;
3864 case 'tag':
3865 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3866 break;
3867 case 'collection':
3868 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3869 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3870 break;
3871 case 'section':
3872 var urls = SITE_MAP[firstClause.value].sections || [];
3873 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3874 break;
3875 }
3876 // console.log(firstClause.attr + ':' + firstClause.value);
3877 resources = resources || [];
3878
3879 // use additional clauses to filter corpus
3880 if (clauses.length > 1) {
3881 var otherClauses = clauses.slice(1);
3882 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3883 }
3884
3885 // filter out resources already added
3886 if (i > 1) {
3887 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3888 }
3889
3890 // add to list of already added indices
3891 for (var j = 0; j < resources.length; j++) {
3892 if (resources[j]) {
3893 addedResourceIndices[resources[j].index] = 1;
3894 }
3895 }
3896
3897 // concat to final results list
3898 results = results.concat(resources);
3899 }
3900
3901 if (opts.sortOrder && results.length) {
3902 var attr = opts.sortOrder;
3903
3904 if (opts.sortOrder == 'random') {
3905 var i = results.length, j, temp;
3906 while (--i) {
3907 j = Math.floor(Math.random() * (i + 1));
3908 temp = results[i];
3909 results[i] = results[j];
3910 results[j] = temp;
3911 }
3912 } else {
3913 var desc = attr.charAt(0) == '-';
3914 if (desc) {
3915 attr = attr.substring(1);
3916 }
3917 results = results.sort(function(x,y) {
3918 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3919 });
3920 }
3921 }
3922
3923 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3924 results = results.slice(0, maxResults);
3925
3926 for (var j = 0; j < results.length; ++j) {
3927 addedPageResources[results[j].index] = 1;
3928 }
3929
3930 return results;
3931 }
3932
3933
3934 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3935 return function(resource) {
3936 return resource && !addedResourceIndices[resource.index];
3937 };
3938 }
3939
3940
3941 function getResourceMatchesClausesFilter(clauses) {
3942 return function(resource) {
3943 return doesResourceMatchClauses(resource, clauses);
3944 };
3945 }
3946
3947
3948 function doesResourceMatchClauses(resource, clauses) {
3949 for (var i = 0; i < clauses.length; i++) {
3950 var map;
3951 switch (clauses[i].attr) {
3952 case 'type':
3953 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3954 break;
3955 case 'lang':
3956 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3957 break;
3958 case 'tag':
3959 map = IS_RESOURCE_TAGGED[clauses[i].value];
3960 break;
3961 }
3962
3963 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3964 return clauses[i].negative;
3965 }
3966 }
3967 return true;
3968 }
3969
3970 function cleanUrl(url)
3971 {
3972 if (url && url.indexOf('//') === -1) {
3973 url = toRoot + url;
3974 }
3975
3976 return url;
3977 }
3978
3979
3980 function parseResourceQuery(query) {
3981 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3982 var expressions = [];
3983 var expressionStrs = query.split(',') || [];
3984 for (var i = 0; i < expressionStrs.length; i++) {
3985 var expr = expressionStrs[i] || '';
3986
3987 // Break expression into clauses (clause e.g. 'tag:foo')
3988 var clauses = [];
3989 var clauseStrs = expr.split(/(?=[\+\-])/);
3990 for (var j = 0; j < clauseStrs.length; j++) {
3991 var clauseStr = clauseStrs[j] || '';
3992
3993 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3994 var parts = clauseStr.split(':');
3995 var clause = {};
3996
3997 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3998 if (clause.attr) {
3999 if (clause.attr.charAt(0) == '+') {
4000 clause.attr = clause.attr.substring(1);
4001 } else if (clause.attr.charAt(0) == '-') {
4002 clause.negative = true;
4003 clause.attr = clause.attr.substring(1);
4004 }
4005 }
4006
4007 if (parts.length > 1) {
4008 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
4009 }
4010
4011 clauses.push(clause);
4012 }
4013
4014 if (!clauses.length) {
4015 continue;
4016 }
4017
4018 expressions.push(clauses);
4019 }
4020
4021 return expressions;
4022 }
4023})();
4024
4025(function($) {
4026
4027 /*
4028 Utility method for creating dom for the description area of a card.
4029 Used in decorateResourceCard and decorateResource.
4030 */
4031 function buildResourceCardDescription(resource, plusone) {
4032 var $description = $('<div>').addClass('description ellipsis');
4033
4034 $description.append($('<div>').addClass('text').html(resource.summary));
4035
4036 if (resource.cta) {
4037 $description.append($('<a>').addClass('cta').html(resource.cta));
4038 }
4039
4040 if (plusone) {
4041 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
4042 "//developer.android.com/" + resource.url;
4043
4044 $description.append($('<div>').addClass('util')
4045 .append($('<div>').addClass('g-plusone')
4046 .attr('data-size', 'small')
4047 .attr('data-align', 'right')
4048 .attr('data-href', plusurl)));
4049 }
4050
4051 return $description;
4052 }
4053
4054
4055 /* Simple jquery function to create dom for a standard resource card */
4056 $.fn.decorateResourceCard = function(resource,plusone) {
4057 var section = resource.group || resource.type;
4058 var imgUrl = resource.image ||
4059 'assets/images/resource-card-default-android.jpg';
4060
4061 if (imgUrl.indexOf('//') === -1) {
4062 imgUrl = toRoot + imgUrl;
4063 }
4064
4065 if (resource.type === 'youtube') {
4066 $('<div>').addClass('play-button')
4067 .append($('<i class="dac-sprite dac-play-white">'))
4068 .appendTo(this);
4069 }
4070
4071 $('<div>').addClass('card-bg')
4072 .css('background-image', 'url(' + (imgUrl || toRoot +
4073 'assets/images/resource-card-default-android.jpg') + ')')
4074 .appendTo(this);
4075
4076 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
4077 .append($('<div>').addClass('section').text(section))
4078 .append($('<div>').addClass('title').html(resource.title))
4079 .append(buildResourceCardDescription(resource, plusone))
4080 .appendTo(this);
4081
4082 return this;
4083 };
4084
4085 /* Simple jquery function to create dom for a resource section card (menu) */
4086 $.fn.decorateResourceSection = function(section,plusone) {
4087 var resource = section.resource;
4088 //keep url clean for matching and offline mode handling
4089 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
4090 var $base = $('<a>')
4091 .addClass('card-bg')
4092 .attr('href', resource.url)
4093 .append($('<div>').addClass('card-section-icon')
4094 .append($('<div>').addClass('icon'))
4095 .append($('<div>').addClass('section').html(resource.title)))
4096 .appendTo(this);
4097
4098 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
4099
4100 if (section.sections && section.sections.length) {
4101 // Recurse the section sub-tree to find a resource image.
4102 var stack = [section];
4103
4104 while (stack.length) {
4105 if (stack[0].resource.image) {
4106 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4107 break;
4108 }
4109
4110 if (stack[0].sections) {
4111 stack = stack.concat(stack[0].sections);
4112 }
4113
4114 stack.shift();
4115 }
4116
4117 var $ul = $('<ul>')
4118 .appendTo($cardInfo);
4119
4120 var max = section.sections.length > 3 ? 3 : section.sections.length;
4121
4122 for (var i = 0; i < max; ++i) {
4123
4124 var subResource = section.sections[i];
4125 if (!plusone) {
4126 $('<li>')
4127 .append($('<a>').attr('href', subResource.url)
4128 .append($('<div>').addClass('title').html(subResource.title))
4129 .append($('<div>').addClass('description ellipsis')
4130 .append($('<div>').addClass('text').html(subResource.summary))
4131 .append($('<div>').addClass('util'))))
4132 .appendTo($ul);
4133 } else {
4134 $('<li>')
4135 .append($('<a>').attr('href', subResource.url)
4136 .append($('<div>').addClass('title').html(subResource.title))
4137 .append($('<div>').addClass('description ellipsis')
4138 .append($('<div>').addClass('text').html(subResource.summary))
4139 .append($('<div>').addClass('util')
4140 .append($('<div>').addClass('g-plusone')
4141 .attr('data-size', 'small')
4142 .attr('data-align', 'right')
4143 .attr('data-href', resource.url)))))
4144 .appendTo($ul);
4145 }
4146 }
4147
4148 // Add a more row
4149 if (max < section.sections.length) {
4150 $('<li>')
4151 .append($('<a>').attr('href', resource.url)
4152 .append($('<div>')
4153 .addClass('title')
4154 .text('More')))
4155 .appendTo($ul);
4156 }
4157 } else {
4158 // No sub-resources, just render description?
4159 }
4160
4161 return this;
4162 };
4163
4164
4165
4166
4167 /* Render other types of resource styles that are not cards. */
4168 $.fn.decorateResource = function(resource, opts) {
4169 var imgUrl = resource.image ||
4170 'assets/images/resource-card-default-android.jpg';
4171 var linkUrl = resource.url;
4172
4173 if (imgUrl.indexOf('//') === -1) {
4174 imgUrl = toRoot + imgUrl;
4175 }
4176
4177 if (linkUrl && linkUrl.indexOf('//') === -1) {
4178 linkUrl = toRoot + linkUrl;
4179 }
4180
4181 $(this).append(
4182 $('<div>').addClass('image')
4183 .css('background-image', 'url(' + imgUrl + ')'),
4184 $('<div>').addClass('info').append(
4185 $('<h4>').addClass('title').html(resource.title),
4186 $('<p>').addClass('summary').html(resource.summary),
4187 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4188 )
4189 );
4190
4191 return this;
4192 };
4193})(jQuery);
4194
4195
4196/* Calculate the vertical area remaining */
4197(function($) {
4198 $.fn.ellipsisfade= function(lineHeight) {
4199 this.each(function() {
4200 // get element text
4201 var $this = $(this);
4202 var remainingHeight = $this.parent().parent().height();
4203 $this.parent().siblings().each(function ()
4204 {
4205 if ($(this).is(":visible")) {
4206 var h = $(this).outerHeight(true);
4207 remainingHeight = remainingHeight - h;
4208 }
4209 });
4210
4211 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4212 $this.parent().css({'height': adjustedRemainingHeight});
4213 $this.css({'height': "auto"});
4214 });
4215
4216 return this;
4217 };
4218}) (jQuery);
4219
4220/*
4221 Fullscreen Carousel
4222
4223 The following allows for an area at the top of the page that takes over the
4224 entire browser height except for its top offset and an optional bottom
4225 padding specified as a data attribute.
4226
4227 HTML:
4228
4229 <div class="fullscreen-carousel">
4230 <div class="fullscreen-carousel-content">
4231 <!-- content here -->
4232 </div>
4233 <div class="fullscreen-carousel-content">
4234 <!-- content here -->
4235 </div>
4236
4237 etc ...
4238
4239 </div>
4240
4241 Control over how the carousel takes over the screen can mostly be defined in
4242 a css file. Setting min-height on the .fullscreen-carousel-content elements
4243 will prevent them from shrinking to far vertically when the browser is very
4244 short, and setting max-height on the .fullscreen-carousel itself will prevent
4245 the area from becoming to long in the case that the browser is stretched very
4246 tall.
4247
4248 There is limited functionality for having multiple sections since that request
4249 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4250 scroll between multiple content areas.
4251*/
4252
4253(function() {
4254 $(document).ready(function() {
4255 $('.fullscreen-carousel').each(function() {
4256 initWidget(this);
4257 });
4258 });
4259
4260 function initWidget(widget) {
4261 var $widget = $(widget);
4262
4263 var topOffset = $widget.offset().top;
4264 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4265 var maxHeight = 0;
4266 var minHeight = 0;
4267 var $content = $widget.find('.fullscreen-carousel-content');
4268 var $nextArrow = $widget.find('.next-arrow');
4269 var $prevArrow = $widget.find('.prev-arrow');
4270 var $curSection = $($content[0]);
4271
4272 if ($content.length <= 1) {
4273 $nextArrow.hide();
4274 $prevArrow.hide();
4275 } else {
4276 $nextArrow.click(function() {
4277 var index = ($content.index($curSection) + 1);
4278 $curSection.hide();
4279 $curSection = $($content[index >= $content.length ? 0 : index]);
4280 $curSection.show();
4281 });
4282
4283 $prevArrow.click(function() {
4284 var index = ($content.index($curSection) - 1);
4285 $curSection.hide();
4286 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4287 $curSection.show();
4288 });
4289 }
4290
4291 // Just hide all content sections except first.
4292 $content.each(function(index) {
4293 if ($(this).height() > minHeight) minHeight = $(this).height();
4294 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4295 });
4296
4297 // Register for changes to window size, and trigger.
4298 $(window).resize(resizeWidget);
4299 resizeWidget();
4300
4301 function resizeWidget() {
4302 var height = $(window).height() - topOffset - padBottom;
4303 $widget.width($(window).width());
4304 $widget.height(height < minHeight ? minHeight :
4305 (maxHeight && height > maxHeight ? maxHeight : height));
4306 }
4307 }
4308})();
4309
4310
4311
4312
4313
4314/*
4315 Tab Carousel
4316
4317 The following allows tab widgets to be installed via the html below. Each
4318 tab content section should have a data-tab attribute matching one of the
4319 nav items'. Also each tab content section should have a width matching the
4320 tab carousel.
4321
4322 HTML:
4323
4324 <div class="tab-carousel">
4325 <ul class="tab-nav">
4326 <li><a href="#" data-tab="handsets">Handsets</a>
4327 <li><a href="#" data-tab="wearable">Wearable</a>
4328 <li><a href="#" data-tab="tv">TV</a>
4329 </ul>
4330
4331 <div class="tab-carousel-content">
4332 <div data-tab="handsets">
4333 <!--Full width content here-->
4334 </div>
4335
4336 <div data-tab="wearable">
4337 <!--Full width content here-->
4338 </div>
4339
4340 <div data-tab="tv">
4341 <!--Full width content here-->
4342 </div>
4343 </div>
4344 </div>
4345
4346*/
4347(function() {
4348 $(document).ready(function() {
4349 $('.tab-carousel').each(function() {
4350 initWidget(this);
4351 });
4352 });
4353
4354 function initWidget(widget) {
4355 var $widget = $(widget);
4356 var $nav = $widget.find('.tab-nav');
4357 var $anchors = $nav.find('[data-tab]');
4358 var $li = $nav.find('li');
4359 var $contentContainer = $widget.find('.tab-carousel-content');
4360 var $tabs = $contentContainer.find('[data-tab]');
4361 var $curTab = $($tabs[0]); // Current tab is first tab.
4362 var width = $widget.width();
4363
4364 // Setup nav interactivity.
4365 $anchors.click(function(evt) {
4366 evt.preventDefault();
4367 var query = '[data-tab=' + $(this).data('tab') + ']';
4368 transitionWidget($tabs.filter(query));
4369 });
4370
4371 // Add highlight for navigation on first item.
4372 var $highlight = $('<div>').addClass('highlight')
4373 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4374 .appendTo($nav);
4375
4376 // Store height since we will change contents to absolute.
4377 $contentContainer.height($contentContainer.height());
4378
4379 // Absolutely position tabs so they're ready for transition.
4380 $tabs.each(function(index) {
4381 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4382 });
4383
4384 function transitionWidget($toTab) {
4385 if (!$curTab.is($toTab)) {
4386 var curIndex = $tabs.index($curTab[0]);
4387 var toIndex = $tabs.index($toTab[0]);
4388 var dir = toIndex > curIndex ? 1 : -1;
4389
4390 // Animate content sections.
4391 $toTab.css({left:(width * dir) + 'px'});
4392 $curTab.animate({left:(width * -dir) + 'px'});
4393 $toTab.animate({left:'0'});
4394
4395 // Animate navigation highlight.
4396 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
4397 width:$($li[toIndex]).outerWidth() + 'px'})
4398
4399 // Store new current section.
4400 $curTab = $toTab;
4401 }
4402 }
4403 }
4404})();
4405
4406/**
4407 * Auto TOC
4408 *
4409 * Upgrades h2s on the page to have a rule and be toggle-able on mobile.
4410 */
4411(function($) {
4412 var upgraded = false;
4413 var h2Titles;
4414
4415 function initWidget() {
4416 // add HRs below all H2s (except for a few other h2 variants)
4417 // Consider doing this with css instead.
4418 h2Titles = $('h2').not('#qv h2, #tb h2, .sidebox h2, #devdoc-nav h2, h2.norule');
4419 h2Titles.css({marginBottom:0}).after('<hr/>');
4420
4421 // Exit early if on older browser.
4422 if (!window.matchMedia) {
4423 return;
4424 }
4425
4426 // Only run logic in mobile layout.
4427 var query = window.matchMedia('(max-width: 719px)');
4428 if (query.matches) {
4429 makeTogglable();
4430 } else {
4431 query.addListener(makeTogglable);
4432 }
4433 }
4434
4435 function makeTogglable() {
4436 // Only run this logic once.
4437 if (upgraded) { return; }
4438 upgraded = true;
4439
4440 // Only make content h2s togglable.
4441 var contentTitles = h2Titles.filter('#jd-content *');
4442
4443 // If there are more than 1
4444 if (contentTitles.size() < 2) {
4445 return;
4446 }
4447
4448 contentTitles.each(function() {
4449 // Find all the relevant nodes.
4450 var $title = $(this);
4451 var $hr = $title.next();
4452 var $contents = $hr.nextUntil('h2, .next-docs');
4453 var $section = $($title)
4454 .add($hr)
4455 .add($title.prev('a[name]'))
4456 .add($contents);
4457 var $anchor = $section.first().prev();
4458 var anchorMethod = 'after';
4459 if ($anchor.length === 0) {
4460 $anchor = $title.parent();
4461 anchorMethod = 'prepend';
4462 }
4463
4464 // Some h2s are in their own container making it pretty hard to find the end, so skip.
4465 if ($contents.length === 0) {
4466 return;
4467 }
4468
4469 // Remove from DOM before messing with it. DOM is slow!
4470 $section.detach();
4471
4472 // Add mobile-only expand arrows.
4473 $title.prepend('<span class="dac-visible-mobile-inline-block">' +
4474 '<i class="dac-toggle-expand dac-sprite dac-expand-more-black"></i>' +
4475 '<i class="dac-toggle-collapse dac-sprite dac-expand-less-black"></i>' +
4476 '</span>')
4477 .attr('data-toggle', 'section');
4478
4479 // Wrap in magic markup.
4480 $section = $section.wrapAll('<div class="dac-toggle dac-mobile">').parent();
4481 $contents.wrapAll('<div class="dac-toggle-content"><div>'); // extra div used for max-height calculation.
4482
4483 // Pre-expand section if requested.
4484 if ($title.hasClass('is-expanded')) {
4485 $section.addClass('is-expanded');
4486 }
4487
4488 // Pre-expand section if targetted by hash.
4489 if (location.hash && $section.find(location.hash).length) {
4490 $section.addClass('is-expanded');
4491 }
4492
4493 // Add it back to the dom.
4494 $anchor[anchorMethod].call($anchor, $section);
4495 });
4496 }
4497
4498 $(function() {
4499 initWidget();
4500 });
4501})(jQuery);
4502
4503(function($) {
4504 'use strict';
4505
4506 /**
4507 * Toggle Floating Label state.
4508 * @param {HTMLElement} el - The DOM element.
4509 * @param options
4510 * @constructor
4511 */
4512 function FloatingLabel(el, options) {
4513 this.el = $(el);
4514 this.options = $.extend({}, FloatingLabel.DEFAULTS_, options);
4515 this.group = this.el.closest('.dac-form-input-group');
4516 this.input = this.group.find('.dac-form-input');
4517
4518 this.checkValue_ = this.checkValue_.bind(this);
4519 this.checkValue_();
4520
4521 this.input.on('focus', function() {
4522 this.group.addClass('dac-focused');
4523 }.bind(this));
4524 this.input.on('blur', function() {
4525 this.group.removeClass('dac-focused');
4526 this.checkValue_();
4527 }.bind(this));
4528 this.input.on('keyup', this.checkValue_);
4529 }
4530
4531 /**
4532 * The label is moved out of the textbox when it has a value.
4533 */
4534 FloatingLabel.prototype.checkValue_ = function() {
4535 if (this.input.val().length) {
4536 this.group.addClass('dac-has-value');
4537 } else {
4538 this.group.removeClass('dac-has-value');
4539 }
4540 };
4541
4542 /**
4543 * jQuery plugin
4544 * @param {object} options - Override default options.
4545 */
4546 $.fn.dacFloatingLabel = function(options) {
4547 return this.each(function() {
4548 new FloatingLabel(this, options);
4549 });
4550 };
4551
4552 $(document).on('ready.aranja', function() {
4553 $('.dac-form-floatlabel').each(function() {
4554 $(this).dacFloatingLabel($(this).data());
4555 });
4556 });
4557})(jQuery);
4558
4559/* global toRoot, CAROUSEL_OVERRIDE */
4560(function($) {
4561 // Ordering matters
4562 var TAG_MAP = [
4563 {from: 'developerstory', to: 'Android Developer Story'},
4564 {from: 'googleplay', to: 'Google Play'}
4565 ];
4566
4567 function DacCarouselQuery(el) {
4568 this.el = $(el);
4569
4570 var opts = this.el.data();
4571 opts.maxResults = parseInt(opts.maxResults || '100', 10);
4572 opts.query = opts.carouselQuery;
4573 var resources = $.queryResources(opts);
4574
4575 this.el.empty();
4576 $(resources).map(function() {
4577 var resource = $.extend({}, this, CAROUSEL_OVERRIDE[this.url]);
4578 var slide = $('<article class="dac-expand dac-hero">');
4579 var image = cleanUrl(resource.heroImage || resource.image);
4580 var fullBleed = image && !resource.heroColor;
4581
4582 // Configure background
4583 slide.css({
4584 backgroundImage: fullBleed ? 'url(' + image + ')' : '',
4585 backgroundColor: resource.heroColor || ''
4586 });
4587
4588 // Should copy be inverted
4589 slide.toggleClass('dac-invert', resource.heroInvert || fullBleed);
4590 slide.toggleClass('dac-darken', fullBleed);
4591
4592 // Should be clickable
4593 slide.append($('<a class="dac-hero-carousel-action">').attr('href', cleanUrl(resource.url)));
4594
4595 var cols = $('<div class="cols dac-hero-content">');
4596
4597 // inline image column
4598 var rightCol = $('<div class="col-1of2 col-push-1of2 dac-hero-figure">')
4599 .appendTo(cols);
4600
4601 if (!fullBleed && image) {
4602 rightCol.append($('<img>').attr('src', image));
4603 }
4604
4605 // info column
4606 $('<div class="col-1of2 col-pull-1of2">')
4607 .append($('<div class="dac-hero-tag">').text(formatTag(resource)))
4608 .append($('<h1 class="dac-hero-title">').text(formatTitle(resource)))
4609 .append($('<p class="dac-hero-description">').text(resource.summary))
4610 .append($('<a class="dac-hero-cta">')
4611 .text(formatCTA(resource))
4612 .attr('href', cleanUrl(resource.url))
4613 .prepend($('<span class="dac-sprite dac-auto-chevron">'))
4614 )
4615 .appendTo(cols);
4616
4617 slide.append(cols.wrap('<div class="wrap">').parent());
4618 return slide[0];
4619 }).prependTo(this.el);
4620
4621 // Pagination element.
4622 this.el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>');
4623
4624 this.el.dacCarousel();
4625 }
4626
4627 function cleanUrl(url) {
4628 if (url && url.indexOf('//') === -1) {
4629 url = toRoot + url;
4630 }
4631 return url;
4632 }
4633
4634 function formatTag(resource) {
4635 // Hmm, need a better more scalable solution for this.
4636 for (var i = 0, mapping; mapping = TAG_MAP[i]; i++) {
4637 if (resource.tags.indexOf(mapping.from) > -1) {
4638 return mapping.to;
4639 }
4640 }
4641 return resource.type;
4642 }
4643
4644 function formatTitle(resource) {
4645 return resource.title.replace(/android developer story: /i, '');
4646 }
4647
4648 function formatCTA(resource) {
4649 return resource.type === 'youtube' ? 'Watch the video' : 'Learn more';
4650 }
4651
4652 // jQuery plugin
4653 $.fn.dacCarouselQuery = function() {
4654 return this.each(function() {
4655 var el = $(this);
4656 var data = el.data('dac.carouselQuery');
4657
4658 if (!data) { el.data('dac.carouselQuery', (data = new DacCarouselQuery(el))); }
4659 });
4660 };
4661
4662 // Data API
4663 $(function() {
4664 $('[data-carousel-query]').dacCarouselQuery();
4665 });
4666})(jQuery);
4667
4668(function($) {
4669 /**
4670 * A CSS based carousel, inspired by SequenceJS.
4671 * @param {jQuery} el
4672 * @param {object} options
4673 * @constructor
4674 */
4675 function DacCarousel(el, options) {
4676 this.el = $(el);
4677 this.options = options = $.extend({}, DacCarousel.OPTIONS, this.el.data(), options || {});
4678 this.frames = this.el.find(options.frameSelector);
4679 this.count = this.frames.size();
4680 this.current = options.start;
4681
4682 this.initPagination();
4683 this.initEvents();
4684 this.initFrame();
4685 }
4686
4687 DacCarousel.OPTIONS = {
4688 auto: true,
4689 autoTime: 10000,
4690 autoMinTime: 5000,
4691 btnPrev: '[data-carousel-prev]',
4692 btnNext: '[data-carousel-next]',
4693 frameSelector: 'article',
4694 loop: true,
4695 start: 0,
4696 swipeThreshold: 160,
4697 pagination: '[data-carousel-pagination]'
4698 };
4699
4700 DacCarousel.prototype.initPagination = function() {
4701 this.pagination = $([]);
4702 if (!this.options.pagination) { return; }
4703
4704 var pagination = $('<ul class="dac-pagination">');
4705 var parent = this.el;
4706 if (typeof this.options.pagination === 'string') { parent = this.el.find(this.options.pagination); }
4707
4708 if (this.count > 1) {
4709 for (var i = 0; i < this.count; i++) {
4710 var li = $('<li class="dac-pagination-item">').text(i);
4711 if (i === this.options.start) { li.addClass('active'); }
4712 li.click(this.go.bind(this, i));
4713
4714 pagination.append(li);
4715 }
4716 this.pagination = pagination.children();
4717 parent.append(pagination);
4718 }
4719 };
4720
4721 DacCarousel.prototype.initEvents = function() {
4722 var that = this;
4723
4724 this.touch = {
4725 start: {x: 0, y: 0},
4726 end: {x: 0, y: 0}
4727 };
4728
4729 this.el.on('touchstart', this.touchstart_.bind(this));
4730 this.el.on('touchend', this.touchend_.bind(this));
4731 this.el.on('touchmove', this.touchmove_.bind(this));
4732
4733 this.el.hover(function() {
4734 that.pauseRotateTimer();
4735 }, function() {
4736 that.startRotateTimer();
4737 });
4738
4739 $(this.options.btnPrev).click(function(e) {
4740 e.preventDefault();
4741 that.prev();
4742 });
4743
4744 $(this.options.btnNext).click(function(e) {
4745 e.preventDefault();
4746 that.next();
4747 });
4748 };
4749
4750 DacCarousel.prototype.touchstart_ = function(event) {
4751 var t = event.originalEvent.touches[0];
4752 this.touch.start = {x: t.screenX, y: t.screenY};
4753 };
4754
4755 DacCarousel.prototype.touchend_ = function() {
4756 var deltaX = this.touch.end.x - this.touch.start.x;
4757 var deltaY = Math.abs(this.touch.end.y - this.touch.start.y);
4758 var shouldSwipe = (deltaY < Math.abs(deltaX)) && (Math.abs(deltaX) >= this.options.swipeThreshold);
4759
4760 if (shouldSwipe) {
4761 if (deltaX > 0) {
4762 this.prev();
4763 } else {
4764 this.next();
4765 }
4766 }
4767 };
4768
4769 DacCarousel.prototype.touchmove_ = function(event) {
4770 var t = event.originalEvent.touches[0];
4771 this.touch.end = {x: t.screenX, y: t.screenY};
4772 };
4773
4774 DacCarousel.prototype.initFrame = function() {
4775 this.frames.removeClass('active').eq(this.options.start).addClass('active');
4776 };
4777
4778 DacCarousel.prototype.startRotateTimer = function() {
4779 if (!this.options.auto || this.rotateTimer) { return; }
4780 this.rotateTimer = setTimeout(this.next.bind(this), this.options.autoTime);
4781 };
4782
4783 DacCarousel.prototype.pauseRotateTimer = function() {
4784 clearTimeout(this.rotateTimer);
4785 this.rotateTimer = null;
4786 };
4787
4788 DacCarousel.prototype.prev = function() {
4789 this.go(this.current - 1);
4790 };
4791
4792 DacCarousel.prototype.next = function() {
4793 this.go(this.current + 1);
4794 };
4795
4796 DacCarousel.prototype.go = function(next) {
4797 // Figure out what the next slide is.
4798 while (this.count > 0 && next >= this.count) { next -= this.count; }
4799 while (next < 0) { next += this.count; }
4800
4801 // Cancel if we're already on that slide.
4802 if (next === this.current) { return; }
4803
4804 // Prepare next slide.
4805 this.frames.eq(next).removeClass('out');
4806
4807 // Recalculate styles before starting slide transition.
4808 this.el.resolveStyles();
4809 // Update pagination
4810 this.pagination.removeClass('active').eq(next).addClass('active');
4811
4812 // Transition out current frame
4813 this.frames.eq(this.current).toggleClass('active out');
4814
4815 // Transition in a new frame
4816 this.frames.eq(next).toggleClass('active');
4817
4818 this.current = next;
4819 };
4820
4821 // Helper which resolves new styles for an element, so it can start transitioning
4822 // from the new values.
4823 $.fn.resolveStyles = function() {
4824 /*jshint expr:true*/
4825 this[0] && this[0].offsetTop;
4826 return this;
4827 };
4828
4829 // jQuery plugin
4830 $.fn.dacCarousel = function() {
4831 this.each(function() {
4832 var $el = $(this);
4833 $el.data('dac-carousel', new DacCarousel(this));
4834 });
4835 return this;
4836 };
4837
4838 // Data API
4839 $(function() {
4840 $('[data-carousel]').dacCarousel();
4841 });
4842})(jQuery);
4843
4844(function($) {
4845 'use strict';
4846
4847 function Modal(el, options) {
4848 this.el = $(el);
4849 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
4850 this.isOpen = false;
4851
4852 this.el.on('click', function(event) {
4853 if (!$.contains($('.dac-modal-window')[0], event.target)) {
4854 return this.el.trigger('modal-close');
4855 }
4856 }.bind(this));
4857
4858 this.el.on('modal-open', this.open_.bind(this));
4859 this.el.on('modal-close', this.close_.bind(this));
4860 this.el.on('modal-toggle', this.toggle_.bind(this));
4861 }
4862
4863 Modal.prototype.toggle_ = function() {
4864 this.el.trigger('modal-' + (this.isOpen ? 'close' : 'open'));
4865 };
4866
4867 Modal.prototype.close_ = function() {
4868 this.el.removeClass('dac-active');
4869 $('body').removeClass('dac-modal-open');
4870 this.isOpen = false;
4871 };
4872
4873 Modal.prototype.open_ = function() {
4874 this.el.addClass('dac-active');
4875 $('body').addClass('dac-modal-open');
4876 this.isOpen = true;
4877 };
4878
4879 function ToggleModal(el, options) {
4880 this.el = $(el);
4881 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
4882 this.modal = this.options.modalToggle ? $('[data-modal="' + this.options.modalToggle + '"]') :
4883 this.el.closest('[data-modal]');
4884
4885 this.el.on('click', this.clickHandler_.bind(this));
4886 }
4887
4888 ToggleModal.prototype.clickHandler_ = function(event) {
4889 event.preventDefault();
4890 this.modal.trigger('modal-toggle');
4891 };
4892
4893 /**
4894 * jQuery plugin
4895 * @param {object} options - Override default options.
4896 */
4897 $.fn.dacModal = function(options) {
4898 return this.each(function() {
4899 new Modal(this, options);
4900 });
4901 };
4902
4903 $.fn.dacToggleModal = function(options) {
4904 return this.each(function() {
4905 new ToggleModal(this, options);
4906 });
4907 };
4908
4909 /**
4910 * Data Attribute API
4911 */
4912 $(document).on('ready.aranja', function() {
4913 $('[data-modal]').each(function() {
4914 $(this).dacModal($(this).data());
4915 });
4916
4917 $('[data-modal-toggle]').each(function() {
4918 $(this).dacToggleModal($(this).data());
4919 });
4920 });
4921})(jQuery);
4922
4923(function($) {
4924 'use strict';
4925
4926 /**
4927 * Toggle the visabilty of the mobile navigation.
4928 * @param {HTMLElement} el - The DOM element.
4929 * @param options
4930 * @constructor
4931 */
4932 function ToggleNav(el, options) {
4933 this.el = $(el);
4934 this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
4935 this.options.target = [this.options.navigation];
4936
4937 if (this.options.body) {this.options.target.push('body')}
4938 if (this.options.dimmer) {this.options.target.push(this.options.dimmer)}
4939
4940 this.el.on('click', this.clickHandler_.bind(this));
4941 }
4942
4943 /**
4944 * ToggleNav Default Settings
4945 * @type {{body: boolean, dimmer: string, navigation: string, toggleClass: string}}
4946 * @private
4947 */
4948 ToggleNav.DEFAULTS_ = {
4949 body: true,
4950 dimmer: '.dac-nav-dimmer',
4951 navigation: '[data-dac-nav]',
4952 toggleClass: 'dac-nav-open'
4953 };
4954
4955 /**
4956 * The actual toggle logic.
4957 * @param event
4958 * @private
4959 */
4960 ToggleNav.prototype.clickHandler_ = function(event) {
4961 event.preventDefault();
4962 $(this.options.target.join(', ')).toggleClass(this.options.toggleClass);
4963 };
4964
4965 /**
4966 * jQuery plugin
4967 * @param {object} options - Override default options.
4968 */
4969 $.fn.dacToggleMobileNav = function(options) {
4970 return this.each(function() {
4971 new ToggleNav(this, options);
4972 });
4973 };
4974
4975 /**
4976 * Data Attribute API
4977 */
4978 $(window).on('load.aranja', function() {
4979 $('[data-dac-toggle-nav]').each(function() {
4980 $(this).dacToggleMobileNav($(this).data());
4981 });
4982 });
4983})(jQuery);
4984
4985(function($) {
4986 'use strict';
4987
4988 /**
4989 * Submit the newsletter form to a Google Form.
4990 * @param {HTMLElement} el - The Form DOM element.
4991 * @constructor
4992 */
4993 function NewsletterForm(el) {
4994 this.el = $(el);
4995 this.form = this.el.find('form');
4996 $('<iframe/>').hide()
4997 .attr('name', 'dac-newsletter-iframe')
4998 .attr('src', '')
4999 .insertBefore(this.form);
5000 this.form.on('submit', this.submitHandler_.bind(this));
5001 }
5002
5003 /**
5004 * Milliseconds until modal has vanished after modal-close is triggered.
5005 * @type {number}
5006 * @private
5007 */
5008 NewsletterForm.CLOSE_DELAY_ = 300;
5009
5010 /**
5011 * Switch view to display form after close.
5012 * @private
5013 */
5014 NewsletterForm.prototype.closeHandler_ = function() {
5015 setTimeout(function() {
5016 this.el.trigger('swap-reset');
5017 }.bind(this), NewsletterForm.CLOSE_DELAY_);
5018 };
5019
5020 /**
5021 * Reset the modal to initial state.
5022 * @private
5023 */
5024 NewsletterForm.prototype.reset_ = function() {
5025 this.form.trigger('reset');
5026 this.el.one('modal-close', this.closeHandler_.bind(this));
5027 };
5028
5029 /**
5030 * Display a success view on submit.
5031 * @private
5032 */
5033 NewsletterForm.prototype.submitHandler_ = function() {
5034 this.el.one('swap-complete', this.reset_.bind(this));
5035 this.el.trigger('swap-content');
5036 };
5037
5038 /**
5039 * jQuery plugin
5040 * @param {object} options - Override default options.
5041 */
5042 $.fn.dacNewsletterForm = function(options) {
5043 return this.each(function() {
5044 new NewsletterForm(this, options);
5045 });
5046 };
5047
5048 /**
5049 * Data Attribute API
5050 */
5051 $(document).on('ready.aranja', function() {
5052 $('[data-newsletter]').each(function() {
5053 $(this).dacNewsletterForm();
5054 });
5055 });
5056})(jQuery);
5057
5058(function($) {
5059 'use strict';
5060
5061 /**
5062 * Smoothly scroll to location on current page.
5063 * @param el
5064 * @param options
5065 * @constructor
5066 */
5067 function ScrollButton(el, options) {
5068 this.el = $(el);
5069 this.target = $(this.el.attr('href'));
5070 this.options = $.extend({}, ScrollButton.DEFAULTS_, options);
5071
5072 if (typeof this.options.offset === 'string') {
5073 this.options.offset = $(this.options.offset).height();
5074 }
5075
5076 this.el.on('click', this.clickHandler_.bind(this));
5077 }
5078
5079 /**
5080 * Default options
5081 * @type {{duration: number, easing: string, offset: number, scrollContainer: string}}
5082 * @private
5083 */
5084 ScrollButton.DEFAULTS_ = {
5085 duration: 300,
5086 easing: 'swing',
5087 offset: 0,
5088 scrollContainer: 'html, body'
5089 };
5090
5091 /**
5092 * Scroll logic
5093 * @param event
5094 * @private
5095 */
5096 ScrollButton.prototype.clickHandler_ = function(event) {
5097 if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
5098 return;
5099 }
5100
5101 event.preventDefault();
5102
5103 $(this.options.scrollContainer).animate({
5104 scrollTop: this.target.offset().top - this.options.offset
5105 }, this.options);
5106 };
5107
5108 /**
5109 * jQuery plugin
5110 * @param {object} options - Override default options.
5111 */
5112 $.fn.dacScrollButton = function(options) {
5113 return this.each(function() {
5114 new ScrollButton(this, options);
5115 });
5116 };
5117
5118 /**
5119 * Data Attribute API
5120 */
5121 $(document).on('ready.aranja', function() {
5122 $('[data-scroll-button]').each(function() {
5123 $(this).dacScrollButton($(this).data());
5124 });
5125 });
5126})(jQuery);
5127
5128(function($) {
5129 'use strict';
5130
5131 /**
5132 * A component that swaps two dynamic height views with an animation.
5133 * Listens for the following events:
5134 * * swap-content: triggers SwapContent.swap_()
5135 * * swap-reset: triggers SwapContent.reset()
5136 * @param el
5137 * @param options
5138 * @constructor
5139 */
5140 function SwapContent(el, options) {
5141 this.el = $(el);
5142 this.options = $.extend({}, SwapContent.DEFAULTS_, options);
5143 this.containers = this.el.find(this.options.container);
5144 this.initiallyActive = this.containers.children('.' + this.options.activeClass).eq(0);
5145 this.el.on('swap-content', this.swap.bind(this));
5146 this.el.on('swap-reset', this.reset.bind(this));
5147 }
5148
5149 /**
5150 * SwapContent's default settings.
5151 * @type {{activeClass: string, container: string, transitionSpeed: number}}
5152 * @private
5153 */
5154 SwapContent.DEFAULTS_ = {
5155 activeClass: 'dac-active',
5156 container: '[data-swap-container]',
5157 transitionSpeed: 500
5158 };
5159
5160 /**
5161 * Returns container's visible height.
5162 * @param container
5163 * @returns {number}
5164 */
5165 SwapContent.prototype.currentHeight = function(container) {
5166 return container.children('.' + this.options.activeClass).outerHeight();
5167 };
5168
5169 /**
5170 * Reset to show initial content
5171 */
5172 SwapContent.prototype.reset = function() {
5173 if (!this.initiallyActive.hasClass(this.initiallyActive)) {
5174 this.containers.children().toggleClass(this.options.activeClass);
5175 }
5176 };
5177
5178 /**
5179 * Complete the swap.
5180 */
5181 SwapContent.prototype.complete = function() {
5182 this.containers.height('auto');
5183 this.containers.trigger('swap-complete');
5184 };
5185
5186 /**
5187 * Perform the swap of content.
5188 */
5189 SwapContent.prototype.swap = function() {
5190 console.log(this.containers);
5191 this.containers.each(function(index, container) {
5192 container = $(container);
5193 container.height(this.currentHeight(container)).children().toggleClass(this.options.activeClass);
5194 container.animate({height: this.currentHeight(container)}, this.options.transitionSpeed,
5195 this.complete.bind(this));
5196 }.bind(this));
5197 };
5198
5199 /**
5200 * jQuery plugin
5201 * @param {object} options - Override default options.
5202 */
5203 $.fn.dacSwapContent = function(options) {
5204 return this.each(function() {
5205 new SwapContent(this, options);
5206 });
5207 };
5208
5209 /**
5210 * Data Attribute API
5211 */
5212 $(document).on('ready.aranja', function() {
5213 $('[data-swap]').each(function() {
5214 $(this).dacSwapContent($(this).data());
5215 });
5216 });
5217})(jQuery);
5218
5219(function($) {
5220 function Toggle(el) {
5221 $(el).on('click.dac.togglesection', this.toggle);
5222 }
5223
5224 Toggle.prototype.toggle = function() {
5225 var $this = $(this);
5226
5227 var $parent = getParent($this);
5228 var isExpanded = $parent.hasClass('is-expanded');
5229
5230 transitionMaxHeight($parent.find('.dac-toggle-content'), !isExpanded);
5231 $parent.toggleClass('is-expanded');
5232
5233 return false;
5234 };
5235
5236 function getParent($this) {
5237 var selector = $this.attr('data-target');
5238
5239 if (!selector) {
5240 selector = $this.attr('href');
5241 selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '');
5242 }
5243
5244 var $parent = selector && $(selector);
5245
5246 $parent = $parent && $parent.length ? $parent : $this.closest('.dac-toggle');
5247
5248 return $parent.length ? $parent : $this.parent();
5249 }
5250
5251 /**
5252 * Runs a transition of max-height along with responsive styles which hide or expand the element.
5253 * @param $el
5254 * @param visible
5255 */
5256 function transitionMaxHeight($el, visible) {
5257 var contentHeight = $el.prop('scrollHeight');
5258 var targetHeight = visible ? contentHeight : 0;
5259 var duration = $el.transitionDuration();
5260
5261 // If we're hiding, first set the maxHeight we're transitioning from.
5262 if (!visible) {
5263 $el.css('maxHeight', contentHeight + 'px')
5264 .resolveStyles();
5265 }
5266
5267 // Transition to new state
5268 $el.css('maxHeight', targetHeight);
5269
5270 // Reset maxHeight to css value after transition.
5271 setTimeout(function() {
5272 $el.css('maxHeight', '');
5273 }, duration);
5274 }
5275
5276 // Utility to get the transition duration for the element.
5277 $.fn.transitionDuration = function() {
5278 var d = $(this).css('transitionDuration') || '0s';
5279
5280 return +(parseFloat(d) * (/ms/.test(d) ? 1 : 1000)).toFixed(0);
5281 };
5282
5283 // jQuery plugin
5284 $.fn.toggleSection = function(option) {
5285 return this.each(function() {
5286 var $this = $(this);
5287 var data = $this.data('dac.togglesection');
5288 if (!data) {$this.data('dac.togglesection', (data = new Toggle(this)));}
5289 if (typeof option === 'string') {data[option].call($this);}
5290 });
5291 };
5292
5293 // Data api
5294 $(document)
5295 .on('click.toggle', '[data-toggle="section"]', Toggle.prototype.toggle);
5296})(jQuery);