blob: ca642171f30899840fe5e0e060d12f2ffc5e1bb1 [file] [log] [blame]
Dirk Dougherty541b4942014-02-14 18:31:53 -08001var classesNav;
2var devdocNav;
3var sidenav;
4var cookie_namespace = 'android_developer';
5var NAV_PREF_TREE = "tree";
6var NAV_PREF_PANELS = "panels";
7var nav_pref;
8var isMobile = false; // true if mobile, so we can adjust some layout
9var mPagePath; // initialized in ready() function
10
11var basePath = getBaseUri(location.pathname);
12var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
13var GOOGLE_DATA; // combined data for google service apis, used for search suggest
14
15// Ensure that all ajax getScript() requests allow caching
16$.ajaxSetup({
17 cache: true
18});
19
20/****** ON LOAD SET UP STUFF *********/
21
Dirk Dougherty541b4942014-02-14 18:31:53 -080022$(document).ready(function() {
23
Dirk Doughertyff233cc2015-05-04 14:37:05 -070024 // 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
Dirk Dougherty541b4942014-02-14 18:31:53 -080035 // load json file for JD doc search suggestions
36 $.getScript(toRoot + 'jd_lists_unified.js');
37 // load json file for Android API search suggestions
38 $.getScript(toRoot + 'reference/lists.js');
39 // load json files for Google services API suggestions
40 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
41 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
42 if(jqxhr.status === 200) {
43 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
44 if(jqxhr.status === 200) {
45 // combine GCM and GMS data
46 GOOGLE_DATA = GMS_DATA;
47 var start = GOOGLE_DATA.length;
48 for (var i=0; i<GCM_DATA.length; i++) {
49 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
50 link:GCM_DATA[i].link, type:GCM_DATA[i].type});
51 }
52 }
53 });
54 }
55 });
56
57 // setup keyboard listener for search shortcut
58 $('body').keyup(function(event) {
59 if (event.which == 191) {
60 $('#search_autocomplete').focus();
61 }
62 });
63
64 // init the fullscreen toggle click event
65 $('#nav-swap .fullscreen').click(function(){
66 if ($(this).hasClass('disabled')) {
67 toggleFullscreen(true);
68 } else {
69 toggleFullscreen(false);
70 }
71 });
72
73 // initialize the divs with custom scrollbars
74 $('.scroll-pane').jScrollPane( {verticalGutter:0} );
75
76 // add HRs below all H2s (except for a few other h2 variants)
Dirk Doughertyff233cc2015-05-04 14:37:05 -070077 $('h2').not('#qv h2')
78 .not('#tb h2')
79 .not('.sidebox h2')
80 .not('#devdoc-nav h2')
81 .not('h2.norule').css({marginBottom:0})
82 .after('<hr/>');
Dirk Dougherty541b4942014-02-14 18:31:53 -080083
84 // set up the search close button
Dirk Dougherty032a4942015-05-04 18:17:33 -070085 $('#search-close').click(function() {
Dirk Dougherty541b4942014-02-14 18:31:53 -080086 $searchInput = $('#search_autocomplete');
87 $searchInput.attr('value', '');
88 $(this).addClass("hide");
89 $("#search-container").removeClass('active');
90 $("#search_autocomplete").blur();
91 search_focus_changed($searchInput.get(), false);
92 hideResults();
93 });
94
Dirk Dougherty541b4942014-02-14 18:31:53 -080095
96 //Set up search
97 $("#search_autocomplete").focus(function() {
98 $("#search-container").addClass('active');
99 })
100 $("#search-container").mouseover(function() {
101 $("#search-container").addClass('active');
102 $("#search_autocomplete").focus();
103 })
104 $("#search-container").mouseout(function() {
105 if ($("#search_autocomplete").is(":focus")) return;
106 if ($("#search_autocomplete").val() == '') {
107 setTimeout(function(){
108 $("#search-container").removeClass('active');
109 $("#search_autocomplete").blur();
110 },250);
111 }
112 })
113 $("#search_autocomplete").blur(function() {
114 if ($("#search_autocomplete").val() == '') {
115 $("#search-container").removeClass('active');
116 }
117 })
118
119
120 // prep nav expandos
121 var pagePath = document.location.pathname;
122 // account for intl docs by removing the intl/*/ path
123 if (pagePath.indexOf("/intl/") == 0) {
124 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
125 }
126
127 if (pagePath.indexOf(SITE_ROOT) == 0) {
128 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
129 pagePath += 'index.html';
130 }
131 }
132
133 // Need a copy of the pagePath before it gets changed in the next block;
134 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
135 var pagePathOriginal = pagePath;
136 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
137 // If running locally, SITE_ROOT will be a relative path, so account for that by
138 // finding the relative URL to this page. This will allow us to find links on the page
139 // leading back to this page.
140 var pathParts = pagePath.split('/');
141 var relativePagePathParts = [];
142 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
143 for (var i = 0; i < upDirs; i++) {
144 relativePagePathParts.push('..');
145 }
146 for (var i = 0; i < upDirs; i++) {
147 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
148 }
149 relativePagePathParts.push(pathParts[pathParts.length - 1]);
150 pagePath = relativePagePathParts.join('/');
151 } else {
152 // Otherwise the page path is already an absolute URL
153 }
154
155 // Highlight the header tabs...
156 // highlight Design tab
Dirk Dougherty032a4942015-05-04 18:17:33 -0700157 var urlSegments = pagePathOriginal.split('/');
158 var navEl = $(".dac-nav-list");
159 var subNavEl = navEl.find(".dac-nav-secondary");
160 var parentNavEl;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800161
Dirk Dougherty032a4942015-05-04 18:17:33 -0700162 if ($("body").hasClass("design")) {
163 navEl.find("> li.design > a").addClass("selected");
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700164 // highlight About tabs
165 } else if ($("body").hasClass("about")) {
Dirk Dougherty032a4942015-05-04 18:17:33 -0700166 if (urlSegments[1] == "about" || urlSegments[1] == "wear" || urlSegments[1] == "tv" || urlSegments[1] == "auto") {
167 navEl.find("> li.home > a").addClass('has-subnav');
168 subNavEl.find("li." + urlSegments[1] + " > a").addClass("selected");
169 } else {
170 navEl.find("> li.home > a").addClass('selected');
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700171 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800172 // highlight Develop tab
173 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
Dirk Dougherty032a4942015-05-04 18:17:33 -0700174 parentNavEl = navEl.find("> li.develop > a");
175 parentNavEl.addClass('has-subnav');
176
Dirk Dougherty541b4942014-02-14 18:31:53 -0800177 // In Develop docs, also highlight appropriate sub-tab
Dirk Dougherty032a4942015-05-04 18:17:33 -0700178 if (urlSegments[1] == "training") {
179 subNavEl.find("li.training > a").addClass("selected");
180 } else if (urlSegments[1] == "guide") {
181 subNavEl.find("li.guide > a").addClass("selected");
182 } else if (urlSegments[1] == "reference") {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800183 // If the root is reference, but page is also part of Google Services, select Google
184 if ($("body").hasClass("google")) {
Dirk Dougherty032a4942015-05-04 18:17:33 -0700185 subNavEl.find("li.google > a").addClass("selected");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800186 } else {
Dirk Dougherty032a4942015-05-04 18:17:33 -0700187 subNavEl.find("li.reference > a").addClass("selected");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800188 }
Dirk Dougherty032a4942015-05-04 18:17:33 -0700189 } else if ((urlSegments[1] == "tools") || (urlSegments[1] == "sdk")) {
190 subNavEl.find("li.tools > a").addClass("selected");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800191 } else if ($("body").hasClass("google")) {
Dirk Dougherty032a4942015-05-04 18:17:33 -0700192 subNavEl.find("li.google > a").addClass("selected");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800193 } else if ($("body").hasClass("samples")) {
Dirk Dougherty032a4942015-05-04 18:17:33 -0700194 subNavEl.find("li.samples > a").addClass("selected");
195 } else {
196 parentNavEl.removeClass('has-subnav').addClass("selected");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800197 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800198 // highlight Distribute tab
199 } else if ($("body").hasClass("distribute")) {
Dirk Dougherty032a4942015-05-04 18:17:33 -0700200 parentNavEl = navEl.find("> li.distribute > a");
201 parentNavEl.addClass('has-subnav');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800202
Dirk Dougherty032a4942015-05-04 18:17:33 -0700203 if (urlSegments[2] == "users") {
204 subNavEl.find("li.users > a").addClass("selected");
205 } else if (urlSegments[2] == "engage") {
206 subNavEl.find("li.engage > a").addClass("selected");
207 } else if (urlSegments[2] == "monetize") {
208 subNavEl.find("li.monetize > a").addClass("selected");
209 } else if (urlSegments[2] == "analyze") {
210 subNavEl.find("li.analyze > a").addClass("selected");
211 } else if (urlSegments[2] == "tools") {
212 subNavEl.find("li.disttools > a").addClass("selected");
213 } else if (urlSegments[2] == "stories") {
214 subNavEl.find("li.stories > a").addClass("selected");
215 } else if (urlSegments[2] == "essentials") {
216 subNavEl.find("li.essentials > a").addClass("selected");
217 } else if (urlSegments[2] == "googleplay") {
218 subNavEl.find("li.googleplay > a").addClass("selected");
219 } else {
220 parentNavEl.removeClass('has-subnav').addClass("selected");
Dirk Dougherty08032402014-02-15 10:14:35 -0800221 }
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700222 }
Dirk Dougherty318fb972014-04-08 18:46:53 -0700223
Dirk Dougherty541b4942014-02-14 18:31:53 -0800224 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
225 // and highlight the sidenav
226 mPagePath = pagePath;
227 highlightSidenav();
Scott Main20cf2a92014-04-02 21:57:20 -0700228 buildBreadcrumbs();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800229
230 // set up prev/next links if they exist
231 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
232 var $selListItem;
233 if ($selNavLink.length) {
234 $selListItem = $selNavLink.closest('li');
235
236 // set up prev links
237 var $prevLink = [];
238 var $prevListItem = $selListItem.prev('li');
239
240 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
241false; // navigate across topic boundaries only in design docs
242 if ($prevListItem.length) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700243 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800244 // jump to last topic of previous section
245 $prevLink = $prevListItem.find('a:last');
246 } else if (!$selListItem.hasClass('nav-section')) {
247 // jump to previous topic in this section
248 $prevLink = $prevListItem.find('a:eq(0)');
249 }
250 } else {
251 // jump to this section's index page (if it exists)
252 var $parentListItem = $selListItem.parents('li');
253 $prevLink = $selListItem.parents('li').find('a');
254
255 // except if cross boundaries aren't allowed, and we're at the top of a section already
256 // (and there's another parent)
257 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
258 && $selListItem.hasClass('nav-section')) {
259 $prevLink = [];
260 }
261 }
262
263 // set up next links
264 var $nextLink = [];
265 var startClass = false;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800266 var isCrossingBoundary = false;
267
268 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
269 // we're on an index page, jump to the first topic
270 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
271
272 // if there aren't any children, go to the next section (required for About pages)
273 if($nextLink.length == 0) {
274 $nextLink = $selListItem.next('li').find('a');
275 } else if ($('.topic-start-link').length) {
276 // as long as there's a child link and there is a "topic start link" (we're on a landing)
277 // then set the landing page "start link" text to be the first doc title
278 $('.topic-start-link').text($nextLink.text().toUpperCase());
279 }
280
281 // If the selected page has a description, then it's a class or article homepage
282 if ($selListItem.find('a[description]').length) {
283 // this means we're on a class landing page
284 startClass = true;
285 }
286 } else {
287 // jump to the next topic in this section (if it exists)
288 $nextLink = $selListItem.next('li').find('a:eq(0)');
289 if ($nextLink.length == 0) {
290 isCrossingBoundary = true;
291 // no more topics in this section, jump to the first topic in the next section
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700292 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800293 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
294 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
295 if ($nextLink.length == 0) {
296 // if that doesn't work, we're at the end of the list, so disable NEXT link
297 $('.next-page-link').attr('href','').addClass("disabled")
298 .click(function() { return false; });
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700299 // and completely hide the one in the footer
300 $('.content-footer .next-page-link').hide();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800301 }
302 }
303 }
304 }
305
306 if (startClass) {
307 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
308
309 // if there's no training bar (below the start button),
310 // then we need to add a bottom border to button
311 if (!$("#tb").length) {
312 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
313 }
314 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
315 $('.content-footer.next-class').show();
316 $('.next-page-link').attr('href','')
317 .removeClass("hide").addClass("disabled")
318 .click(function() { return false; });
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700319 // and completely hide the one in the footer
320 $('.content-footer .next-page-link').hide();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800321 if ($nextLink.length) {
322 $('.next-class-link').attr('href',$nextLink.attr('href'))
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700323 .removeClass("hide")
324 .append(": " + $nextLink.html());
Dirk Dougherty541b4942014-02-14 18:31:53 -0800325 $('.next-class-link').find('.new').empty();
326 }
327 } else {
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700328 $('.next-page-link').attr('href', $nextLink.attr('href'))
329 .removeClass("hide");
330 // for the footer link, also add the next page title
331 $('.content-footer .next-page-link').append(": " + $nextLink.html());
Dirk Dougherty541b4942014-02-14 18:31:53 -0800332 }
333
334 if (!startClass && $prevLink.length) {
335 var prevHref = $prevLink.attr('href');
336 if (prevHref == SITE_ROOT + 'index.html') {
337 // Don't show Previous when it leads to the homepage
338 } else {
339 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
340 }
341 }
342
Dirk Dougherty541b4942014-02-14 18:31:53 -0800343 }
344
345
346
347 // Set up the course landing pages for Training with class names and descriptions
348 if ($('body.trainingcourse').length) {
349 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700350
351 // create an array for all the class descriptions
352 var $classDescriptions = new Array($classLinks.length);
353 var lang = getLangPref();
354 $classLinks.each(function(index) {
355 var langDescr = $(this).attr(lang + "-description");
356 if (typeof langDescr !== 'undefined' && langDescr !== false) {
357 // if there's a class description in the selected language, use that
358 $classDescriptions[index] = langDescr;
359 } else {
360 // otherwise, use the default english description
361 $classDescriptions[index] = $(this).attr("description");
362 }
363 });
Dirk Dougherty541b4942014-02-14 18:31:53 -0800364
365 var $olClasses = $('<ol class="class-list"></ol>');
366 var $liClass;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800367 var $h2Title;
368 var $pSummary;
369 var $olLessons;
370 var $liLesson;
371 $classLinks.each(function(index) {
Dirk Dougherty032a4942015-05-04 18:17:33 -0700372 $liClass = $('<li class="clearfix"></li>');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800373 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700374 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800375
376 $olLessons = $('<ol class="lesson-list"></ol>');
377
378 $lessons = $(this).closest('li').find('ul li a');
379
380 if ($lessons.length) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800381 $lessons.each(function(index) {
382 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
383 });
384 } else {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800385 $pSummary.addClass('article');
386 }
387
Dirk Dougherty032a4942015-05-04 18:17:33 -0700388 $liClass.append($h2Title).append($pSummary).append($olLessons);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800389 $olClasses.append($liClass);
390 });
391 $('.jd-descr').append($olClasses);
392 }
393
394 // Set up expand/collapse behavior
395 initExpandableNavItems("#nav");
396
397
398 $(".scroll-pane").scroll(function(event) {
399 event.preventDefault();
400 return false;
401 });
402
403 /* Resize nav height when window height changes */
404 $(window).resize(function() {
405 if ($('#side-nav').length == 0) return;
406 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
407 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
408 // make sidenav behave when resizing the window and side-scolling is a concern
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700409 if (sticky) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800410 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
411 updateSideNavPosition();
412 } else {
413 updateSidenavFullscreenWidth();
414 }
415 }
416 resizeNav();
417 });
418
419
Dirk Dougherty541b4942014-02-14 18:31:53 -0800420 var navBarLeftPos;
421 if ($('#devdoc-nav').length) {
422 setNavBarLeftPos();
423 }
424
425
426 // Set up play-on-hover <video> tags.
427 $('video.play-on-hover').bind('click', function(){
428 $(this).get(0).load(); // in case the video isn't seekable
429 $(this).get(0).play();
430 });
431
432 // Set up tooltips
433 var TOOLTIP_MARGIN = 10;
434 $('acronym,.tooltip-link').each(function() {
435 var $target = $(this);
436 var $tooltip = $('<div>')
437 .addClass('tooltip-box')
438 .append($target.attr('title'))
439 .hide()
440 .appendTo('body');
441 $target.removeAttr('title');
442
443 $target.hover(function() {
444 // in
445 var targetRect = $target.offset();
446 targetRect.width = $target.width();
447 targetRect.height = $target.height();
448
449 $tooltip.css({
450 left: targetRect.left,
451 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
452 });
453 $tooltip.addClass('below');
454 $tooltip.show();
455 }, function() {
456 // out
457 $tooltip.hide();
458 });
459 });
460
461 // Set up <h2> deeplinks
462 $('h2').click(function() {
463 var id = $(this).attr('id');
464 if (id) {
465 document.location.hash = id;
466 }
467 });
468
469 //Loads the +1 button
470 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
471 po.src = 'https://apis.google.com/js/plusone.js';
472 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
473
Dirk Dougherty541b4942014-02-14 18:31:53 -0800474 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
475
476 if ($(".scroll-pane").length > 1) {
477 // Check if there's a user preference for the panel heights
478 var cookieHeight = readCookie("reference_height");
479 if (cookieHeight) {
480 restoreHeight(cookieHeight);
481 }
482 }
483
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700484 // Resize once loading is finished
Dirk Dougherty541b4942014-02-14 18:31:53 -0800485 resizeNav();
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700486 // Check if there's an anchor that we need to scroll into view.
487 // A delay is needed, because some browsers do not immediately scroll down to the anchor
488 window.setTimeout(offsetScrollForSticky, 100);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800489
490 /* init the language selector based on user cookie for lang */
491 loadLangPref();
492 changeNavLang(getLangPref());
493
494 /* setup event handlers to ensure the overflow menu is visible while picking lang */
495 $("#language select")
496 .mousedown(function() {
497 $("div.morehover").addClass("hover"); })
498 .blur(function() {
499 $("div.morehover").removeClass("hover"); });
500
501 /* some global variable setup */
502 resizePackagesNav = $("#resize-packages-nav");
503 classesNav = $("#classes-nav");
504 devdocNav = $("#devdoc-nav");
505
506 var cookiePath = "";
507 if (location.href.indexOf("/reference/") != -1) {
508 cookiePath = "reference_";
509 } else if (location.href.indexOf("/guide/") != -1) {
510 cookiePath = "guide_";
511 } else if (location.href.indexOf("/tools/") != -1) {
512 cookiePath = "tools_";
513 } else if (location.href.indexOf("/training/") != -1) {
514 cookiePath = "training_";
515 } else if (location.href.indexOf("/design/") != -1) {
516 cookiePath = "design_";
517 } else if (location.href.indexOf("/distribute/") != -1) {
518 cookiePath = "distribute_";
519 }
520
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700521
522 /* setup shadowbox for any videos that want it */
523 var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
524 if ($videoLinks.length) {
525 // if there's at least one, add the shadowbox HTML to the body
526 $('body').prepend(
527'<div id="video-container">'+
528 '<div id="video-frame">'+
529 '<div class="video-close">'+
530 '<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
531 '</div>'+
532 '<div id="youTubePlayer"></div>'+
533 '</div>'+
534'</div>');
535
536 // loads the IFrame Player API code asynchronously.
537 $.getScript("https://www.youtube.com/iframe_api");
538
539 $videoLinks.each(function() {
540 var videoId = $(this).attr('href').split('?v=')[1];
541 $(this).click(function(event) {
542 event.preventDefault();
543 startYouTubePlayer(videoId);
544 });
545 });
546 }
Dirk Dougherty032a4942015-05-04 18:17:33 -0700547
548 // Responsive testing
549 var responsiveParam = location.href.match(/[?&]responsive=?(|true|false)/);
550 if (responsiveParam) {
551 localStorage['test-responsive'] = ['', 'true'].indexOf(responsiveParam) > -1;
552 }
553 if (localStorage['test-responsive']) {
554 $(document.body).addClass('responsive');
555 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800556});
557// END of the onload event
558
559
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700560var 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
Dirk Dougherty541b4942014-02-14 18:31:53 -0800671function 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
Scott Main20cf2a92014-04-02 21:57:20 -0700705
706/** Create the list of breadcrumb links in the sticky header */
707function buildBreadcrumbs() {
Dirk Dougherty032a4942015-05-04 18:17:33 -0700708 var $breadcrumbUl = $(".dac-header-crumbs");
709 var primaryNavLink = ".dac-nav-list > .dac-nav-item > .dac-nav-link";
710
Scott Main20cf2a92014-04-02 21:57:20 -0700711 // Add the secondary horizontal nav item, if provided
Dirk Dougherty032a4942015-05-04 18:17:33 -0700712 var $selectedSecondNav = $(".dac-nav-secondary .dac-nav-link.selected").clone()
713 .attr('class', 'dac-header-crumbs-link');
714
Scott Main20cf2a92014-04-02 21:57:20 -0700715 if ($selectedSecondNav.length) {
Dirk Dougherty032a4942015-05-04 18:17:33 -0700716 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedSecondNav));
Scott Main20cf2a92014-04-02 21:57:20 -0700717 }
Dirk Dougherty032a4942015-05-04 18:17:33 -0700718
Scott Main20cf2a92014-04-02 21:57:20 -0700719 // Add the primary horizontal nav
Dirk Dougherty032a4942015-05-04 18:17:33 -0700720 var $selectedFirstNav = $(primaryNavLink + ".selected, " + primaryNavLink + ".has-subnav").clone()
721 .attr('class', 'dac-header-crumbs-link');
722
Scott Main7a6ab432014-05-09 10:00:14 -0700723 // If there's no header nav item, use the logo link and title from alt text
724 if ($selectedFirstNav.length < 1) {
Dirk Dougherty032a4942015-05-04 18:17:33 -0700725 $selectedFirstNav = $('<a class="dac-header-crumbs-link">')
Scott Main7a6ab432014-05-09 10:00:14 -0700726 .attr('href', $("div#header .logo a").attr('href'))
727 .text($("div#header .logo img").attr('alt'));
728 }
Dirk Dougherty032a4942015-05-04 18:17:33 -0700729 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedFirstNav));
Scott Main20cf2a92014-04-02 21:57:20 -0700730}
731
732
733
Dirk Dougherty541b4942014-02-14 18:31:53 -0800734/** 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 }
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700785 writeCookie("fullscreen", enabled, null);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800786 setNavBarLeftPos();
787 resizeNav(delay);
788 updateSideNavPosition();
789 setTimeout(initSidenavHeightResize,delay);
790}
791
792
793function setNavBarLeftPos() {
794 navBarLeftPos = $('#body-content').offset().left;
795}
796
797
798function updateSideNavPosition() {
799 var newLeft = $(window).scrollLeft() - navBarLeftPos;
800 $('#devdoc-nav').css({left: -newLeft});
Dirk Dougherty032a4942015-05-04 18:17:33 -0700801 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('padding-left')))});
Dirk Dougherty541b4942014-02-14 18:31:53 -0800802}
803
804// TODO: use $(document).ready instead
805function addLoadEvent(newfun) {
806 var current = window.onload;
807 if (typeof window.onload != 'function') {
808 window.onload = newfun;
809 } else {
810 window.onload = function() {
811 current();
812 newfun();
813 }
814 }
815}
816
817var agent = navigator['userAgent'].toLowerCase();
818// If a mobile phone, set flag and do mobile setup
819if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
820 (agent.indexOf("blackberry") != -1) ||
821 (agent.indexOf("webos") != -1) ||
822 (agent.indexOf("mini") != -1)) { // opera mini browsers
823 isMobile = true;
824}
825
826
827$(document).ready(function() {
828 $("pre:not(.no-pretty-print)").addClass("prettyprint");
829 prettyPrint();
830});
831
832
833
834
835/* ######### RESIZE THE SIDENAV HEIGHT ########## */
836
837function resizeNav(delay) {
838 var $nav = $("#devdoc-nav");
839 var $window = $(window);
840 var navHeight;
841
842 // Get the height of entire window and the total header height.
843 // Then figure out based on scroll position whether the header is visible
844 var windowHeight = $window.height();
845 var scrollTop = $window.scrollTop();
Scott Main20cf2a92014-04-02 21:57:20 -0700846 var headerHeight = $('#header-wrapper').outerHeight();
847 var headerVisible = scrollTop < stickyTop;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800848
849 // get the height of space between nav and top of window.
850 // Could be either margin or top position, depending on whether the nav is fixed.
851 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
852 // add 1 for the #side-nav bottom margin
853
854 // Depending on whether the header is visible, set the side nav's height.
855 if (headerVisible) {
856 // The sidenav height grows as the header goes off screen
Scott Main20cf2a92014-04-02 21:57:20 -0700857 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800858 } else {
859 // Once header is off screen, the nav height is almost full window height
860 navHeight = windowHeight - topMargin;
861 }
862
863
864
865 $scrollPanes = $(".scroll-pane");
866 if ($scrollPanes.length > 1) {
867 // subtract the height of the api level widget and nav swapper from the available nav height
868 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
869
870 $("#swapper").css({height:navHeight + "px"});
871 if ($("#nav-tree").is(":visible")) {
872 $("#nav-tree").css({height:navHeight});
873 }
874
875 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
876 //subtract 10px to account for drag bar
877
878 // if the window becomes small enough to make the class panel height 0,
879 // then the package panel should begin to shrink
880 if (parseInt(classesHeight) <= 0) {
881 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
882 $("#packages-nav").css({height:navHeight - 10});
883 }
884
885 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
886 $("#classes-nav .jspContainer").css({height:classesHeight});
887
888
889 } else {
890 $nav.height(navHeight);
891 }
892
893 if (delay) {
894 updateFromResize = true;
895 delayedReInitScrollbars(delay);
896 } else {
897 reInitScrollbars();
898 }
899
900}
901
902var updateScrollbars = false;
903var updateFromResize = false;
904
905/* Re-initialize the scrollbars to account for changed nav size.
906 * This method postpones the actual update by a 1/4 second in order to optimize the
907 * scroll performance while the header is still visible, because re-initializing the
908 * scroll panes is an intensive process.
909 */
910function delayedReInitScrollbars(delay) {
911 // If we're scheduled for an update, but have received another resize request
912 // before the scheduled resize has occured, just ignore the new request
913 // (and wait for the scheduled one).
914 if (updateScrollbars && updateFromResize) {
915 updateFromResize = false;
916 return;
917 }
918
919 // We're scheduled for an update and the update request came from this method's setTimeout
920 if (updateScrollbars && !updateFromResize) {
921 reInitScrollbars();
922 updateScrollbars = false;
923 } else {
924 updateScrollbars = true;
925 updateFromResize = false;
926 setTimeout('delayedReInitScrollbars()',delay);
927 }
928}
929
930/* Re-initialize the scrollbars to account for changed nav size. */
931function reInitScrollbars() {
932 var pane = $(".scroll-pane").each(function(){
933 var api = $(this).data('jsp');
934 if (!api) { setTimeout(reInitScrollbars,300); return;}
935 api.reinitialise( {verticalGutter:0} );
936 });
937 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
938}
939
940
941/* Resize the height of the nav panels in the reference,
942 * and save the new size to a cookie */
943function saveNavPanels() {
944 var basePath = getBaseUri(location.pathname);
945 var section = basePath.substring(1,basePath.indexOf("/",1));
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700946 writeCookie("height", resizePackagesNav.css("height"), section);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800947}
948
949
950
951function restoreHeight(packageHeight) {
952 $("#resize-packages-nav").height(packageHeight);
953 $("#packages-nav").height(packageHeight);
954 // var classesHeight = navHeight - packageHeight;
955 // $("#classes-nav").css({height:classesHeight});
956 // $("#classes-nav .jspContainer").css({height:classesHeight});
957}
958
959
960
961/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
962
963
964
965
966
967/** Scroll the jScrollPane to make the currently selected item visible
968 This is called when the page finished loading. */
969function scrollIntoView(nav) {
970 var $nav = $("#"+nav);
971 var element = $nav.jScrollPane({/* ...settings... */});
972 var api = element.data('jsp');
973
974 if ($nav.is(':visible')) {
975 var $selected = $(".selected", $nav);
976 if ($selected.length == 0) {
977 // If no selected item found, exit
978 return;
979 }
980 // get the selected item's offset from its container nav by measuring the item's offset
981 // relative to the document then subtract the container nav's offset relative to the document
982 var selectedOffset = $selected.offset().top - $nav.offset().top;
983 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
984 // if it's more than 80% down the nav
985 // scroll the item up by an amount equal to 80% the container nav's height
986 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
987 }
988 }
989}
990
991
992
993
994
995
996/* Show popup dialogs */
997function showDialog(id) {
998 $dialog = $("#"+id);
999 $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>');
1000 $dialog.wrapInner('<div/>');
1001 $dialog.removeClass("hide");
1002}
1003
1004
1005
1006
1007
1008/* ######### COOKIES! ########## */
1009
1010function readCookie(cookie) {
1011 var myCookie = cookie_namespace+"_"+cookie+"=";
1012 if (document.cookie) {
1013 var index = document.cookie.indexOf(myCookie);
1014 if (index != -1) {
1015 var valStart = index + myCookie.length;
1016 var valEnd = document.cookie.indexOf(";", valStart);
1017 if (valEnd == -1) {
1018 valEnd = document.cookie.length;
1019 }
1020 var val = document.cookie.substring(valStart, valEnd);
1021 return val;
1022 }
1023 }
1024 return 0;
1025}
1026
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001027function writeCookie(cookie, val, section) {
Dirk Dougherty541b4942014-02-14 18:31:53 -08001028 if (val==undefined) return;
1029 section = section == null ? "_" : "_"+section+"_";
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001030 var age = 2*365*24*60*60; // set max-age to 2 years
Dirk Dougherty541b4942014-02-14 18:31:53 -08001031 var cookieValue = cookie_namespace + section + cookie + "=" + val
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001032 + "; max-age=" + age +"; path=/";
Dirk Dougherty541b4942014-02-14 18:31:53 -08001033 document.cookie = cookieValue;
1034}
1035
1036/* ######### END COOKIES! ########## */
1037
1038
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001039var sticky = false;
Scott Maind6a8e662014-04-12 16:40:48 -07001040var stickyTop;
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001041var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Scott Maind6a8e662014-04-12 16:40:48 -07001042/* Sets the vertical scoll position at which the sticky bar should appear.
1043 This method is called to reset the position when search results appear or hide */
1044function setStickyTop() {
Dirk Dougherty032a4942015-05-04 18:17:33 -07001045 stickyTop = $('#header-wrapper').outerHeight() - $('#header > .dac-header-inner').outerHeight();
Scott Maind6a8e662014-04-12 16:40:48 -07001046}
1047
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001048/*
1049 * Displays sticky nav bar on pages when dac header scrolls out of view
1050 */
1051$(window).scroll(function(event) {
Dirk Dougherty541b4942014-02-14 18:31:53 -08001052
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001053 setStickyTop();
1054 var hiding = false;
Dirk Dougherty032a4942015-05-04 18:17:33 -07001055 var $headerEl = $('#header');
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001056 // Exit if there's no sidenav
1057 if ($('#side-nav').length == 0) return;
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 var top = $(window).scrollTop();
1065 // we set the navbar fixed when the scroll position is beyond the height of the site header...
1066 var shouldBeSticky = top >= stickyTop;
1067 // ... except if the document content is shorter than the sidenav height.
1068 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1069 if ($("#doc-col").height() < $("#side-nav").height()) {
1070 shouldBeSticky = false;
1071 }
1072 // Account for horizontal scroll
1073 var scrollLeft = $(window).scrollLeft();
1074 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1075 if (sticky && (scrollLeft != prevScrollLeft)) {
1076 updateSideNavPosition();
1077 prevScrollLeft = scrollLeft;
1078 }
1079
1080 // Don't continue if the header is sufficently far away
1081 // (to avoid intensive resizing that slows scrolling)
1082 if (sticky == shouldBeSticky) {
1083 return;
1084 }
1085
1086 // If sticky header visible and position is now near top, hide sticky
1087 if (sticky && !shouldBeSticky) {
1088 sticky = false;
1089 hiding = true;
1090 // make the sidenav static again
1091 $('#devdoc-nav')
1092 .removeClass('fixed')
1093 .css({'width':'auto','margin':''})
1094 .prependTo('#side-nav');
1095 // delay hide the sticky
Dirk Dougherty032a4942015-05-04 18:17:33 -07001096 $headerEl.removeClass('is-sticky');
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001097 hiding = false;
1098
1099 // update the sidenaav position for side scrolling
1100 updateSideNavPosition();
1101 } else if (!sticky && shouldBeSticky) {
1102 sticky = true;
Dirk Dougherty032a4942015-05-04 18:17:33 -07001103 $headerEl.addClass('is-sticky');
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001104
1105 // make the sidenav fixed
1106 var width = $('#devdoc-nav').width();
1107 $('#devdoc-nav')
1108 .addClass('fixed')
1109 .css({'width':width+'px'})
1110 .prependTo('#body-content');
1111
1112 // update the sidenaav position for side scrolling
1113 updateSideNavPosition();
1114
1115 } else if (hiding && top < 15) {
Dirk Dougherty032a4942015-05-04 18:17:33 -07001116 $headerEl.removeClass('is-sticky');
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001117 hiding = false;
1118 }
1119 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
1120});
1121
1122/*
1123 * Manages secion card states and nav resize to conclude loading
Dirk Dougherty08032402014-02-15 10:14:35 -08001124 */
Dirk Dougherty08032402014-02-15 10:14:35 -08001125(function() {
1126 $(document).ready(function() {
1127
Dirk Dougherty08032402014-02-15 10:14:35 -08001128 // Stack hover states
1129 $('.section-card-menu').each(function(index, el) {
1130 var height = $(el).height();
1131 $(el).css({height:height+'px', position:'relative'});
1132 var $cardInfo = $(el).find('.card-info');
1133
1134 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1135 });
1136
Dirk Dougherty08032402014-02-15 10:14:35 -08001137 });
1138
1139})();
Dirk Dougherty541b4942014-02-14 18:31:53 -08001140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154/* MISC LIBRARY FUNCTIONS */
1155
1156
1157
1158
1159
1160function toggle(obj, slide) {
1161 var ul = $("ul:first", obj);
1162 var li = ul.parent();
1163 if (li.hasClass("closed")) {
1164 if (slide) {
1165 ul.slideDown("fast");
1166 } else {
1167 ul.show();
1168 }
1169 li.removeClass("closed");
1170 li.addClass("open");
1171 $(".toggle-img", li).attr("title", "hide pages");
1172 } else {
1173 ul.slideUp("fast");
1174 li.removeClass("open");
1175 li.addClass("closed");
1176 $(".toggle-img", li).attr("title", "show pages");
1177 }
1178}
1179
1180
1181function buildToggleLists() {
1182 $(".toggle-list").each(
1183 function(i) {
1184 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1185 $(this).addClass("closed");
1186 });
1187}
1188
1189
1190
1191function hideNestedItems(list, toggle) {
1192 $list = $(list);
1193 // hide nested lists
1194 if($list.hasClass('showing')) {
1195 $("li ol", $list).hide('fast');
1196 $list.removeClass('showing');
1197 // show nested lists
1198 } else {
1199 $("li ol", $list).show('fast');
1200 $list.addClass('showing');
1201 }
1202 $(".more,.less",$(toggle)).toggle();
1203}
1204
1205
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001206/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1207function setupIdeDocToggle() {
1208 $( "select.ide" ).change(function() {
1209 var selected = $(this).find("option:selected").attr("value");
1210 $(".select-ide").hide();
1211 $(".select-ide."+selected).show();
Dirk Dougherty541b4942014-02-14 18:31:53 -08001212
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001213 $("select.ide").val(selected);
1214 });
1215}
Dirk Dougherty541b4942014-02-14 18:31:53 -08001216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240/* REFERENCE NAV SWAP */
1241
1242
1243function getNavPref() {
1244 var v = readCookie('reference_nav');
1245 if (v != NAV_PREF_TREE) {
1246 v = NAV_PREF_PANELS;
1247 }
1248 return v;
1249}
1250
1251function chooseDefaultNav() {
1252 nav_pref = getNavPref();
1253 if (nav_pref == NAV_PREF_TREE) {
1254 $("#nav-panels").toggle();
1255 $("#panel-link").toggle();
1256 $("#nav-tree").toggle();
1257 $("#tree-link").toggle();
1258 }
1259}
1260
1261function swapNav() {
1262 if (nav_pref == NAV_PREF_TREE) {
1263 nav_pref = NAV_PREF_PANELS;
1264 } else {
1265 nav_pref = NAV_PREF_TREE;
1266 init_default_navtree(toRoot);
1267 }
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001268 writeCookie("nav", nav_pref, "reference");
Dirk Dougherty541b4942014-02-14 18:31:53 -08001269
1270 $("#nav-panels").toggle();
1271 $("#panel-link").toggle();
1272 $("#nav-tree").toggle();
1273 $("#tree-link").toggle();
1274
1275 resizeNav();
1276
1277 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1278 $("#nav-tree .jspContainer:visible")
1279 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1280 // Another nasty hack to make the scrollbar appear now that we have height
1281 resizeNav();
1282
1283 if ($("#nav-tree").is(':visible')) {
1284 scrollIntoView("nav-tree");
1285 } else {
1286 scrollIntoView("packages-nav");
1287 scrollIntoView("classes-nav");
1288 }
1289}
1290
1291
1292
1293/* ############################################ */
1294/* ########## LOCALIZATION ############ */
1295/* ############################################ */
1296
1297function getBaseUri(uri) {
1298 var intlUrl = (uri.substring(0,6) == "/intl/");
1299 if (intlUrl) {
1300 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1301 base = base.substring(base.indexOf('/')+1, base.length);
1302 //alert("intl, returning base url: /" + base);
1303 return ("/" + base);
1304 } else {
1305 //alert("not intl, returning uri as found.");
1306 return uri;
1307 }
1308}
1309
1310function requestAppendHL(uri) {
1311//append "?hl=<lang> to an outgoing request (such as to blog)
1312 var lang = getLangPref();
1313 if (lang) {
1314 var q = 'hl=' + lang;
1315 uri += '?' + q;
1316 window.location = uri;
1317 return false;
1318 } else {
1319 return true;
1320 }
1321}
1322
1323
1324function changeNavLang(lang) {
1325 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1326 $links.each(function(i){ // for each link with a translation
1327 var $link = $(this);
1328 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1329 // put the desired language from the attribute as the text
1330 $link.text($link.attr(lang+"-lang"))
1331 }
1332 });
1333}
1334
1335function changeLangPref(lang, submit) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001336 writeCookie("pref_lang", lang, null);
Dirk Dougherty541b4942014-02-14 18:31:53 -08001337
1338 // ####### TODO: Remove this condition once we're stable on devsite #######
1339 // This condition is only needed if we still need to support legacy GAE server
1340 if (devsite) {
1341 // Switch language when on Devsite server
1342 if (submit) {
1343 $("#setlang").submit();
1344 }
1345 } else {
1346 // Switch language when on legacy GAE server
1347 if (submit) {
1348 window.location = getBaseUri(location.pathname);
1349 }
1350 }
1351}
1352
1353function loadLangPref() {
1354 var lang = readCookie("pref_lang");
1355 if (lang != 0) {
1356 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1357 }
1358}
1359
1360function getLangPref() {
1361 var lang = $("#language").find(":selected").attr("value");
1362 if (!lang) {
1363 lang = readCookie("pref_lang");
1364 }
1365 return (lang != 0) ? lang : 'en';
1366}
1367
1368/* ########## END LOCALIZATION ############ */
1369
1370
1371
1372
1373
1374
1375/* Used to hide and reveal supplemental content, such as long code samples.
1376 See the companion CSS in android-developer-docs.css */
1377function toggleContent(obj) {
1378 var div = $(obj).closest(".toggle-content");
1379 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
1380 if (div.hasClass("closed")) { // if it's closed, open it
1381 toggleMe.slideDown();
1382 $(".toggle-content-text:eq(0)", obj).toggle();
1383 div.removeClass("closed").addClass("open");
1384 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
1385 + "assets/images/triangle-opened.png");
1386 } else { // if it's open, close it
1387 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
1388 $(".toggle-content-text:eq(0)", obj).toggle();
1389 div.removeClass("open").addClass("closed");
1390 div.find(".toggle-content").removeClass("open").addClass("closed")
1391 .find(".toggle-content-toggleme").hide();
1392 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
1393 + "assets/images/triangle-closed.png");
1394 });
1395 }
1396 return false;
1397}
1398
1399
1400/* New version of expandable content */
1401function toggleExpandable(link,id) {
1402 if($(id).is(':visible')) {
1403 $(id).slideUp();
1404 $(link).removeClass('expanded');
1405 } else {
1406 $(id).slideDown();
1407 $(link).addClass('expanded');
1408 }
1409}
1410
1411function hideExpandable(ids) {
1412 $(ids).slideUp();
1413 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
1414}
1415
1416
1417
1418
1419
1420/*
1421 * Slideshow 1.0
1422 * Used on /index.html and /develop/index.html for carousel
1423 *
1424 * Sample usage:
1425 * HTML -
1426 * <div class="slideshow-container">
1427 * <a href="" class="slideshow-prev">Prev</a>
1428 * <a href="" class="slideshow-next">Next</a>
1429 * <ul>
1430 * <li class="item"><img src="images/marquee1.jpg"></li>
1431 * <li class="item"><img src="images/marquee2.jpg"></li>
1432 * <li class="item"><img src="images/marquee3.jpg"></li>
1433 * <li class="item"><img src="images/marquee4.jpg"></li>
1434 * </ul>
1435 * </div>
1436 *
1437 * <script type="text/javascript">
1438 * $('.slideshow-container').dacSlideshow({
1439 * auto: true,
1440 * btnPrev: '.slideshow-prev',
1441 * btnNext: '.slideshow-next'
1442 * });
1443 * </script>
1444 *
1445 * Options:
1446 * btnPrev: optional identifier for previous button
1447 * btnNext: optional identifier for next button
1448 * btnPause: optional identifier for pause button
1449 * auto: whether or not to auto-proceed
1450 * speed: animation speed
1451 * autoTime: time between auto-rotation
1452 * easing: easing function for transition
1453 * start: item to select by default
1454 * scroll: direction to scroll in
1455 * pagination: whether or not to include dotted pagination
1456 *
1457 */
1458
1459 (function($) {
1460 $.fn.dacSlideshow = function(o) {
1461
1462 //Options - see above
1463 o = $.extend({
1464 btnPrev: null,
1465 btnNext: null,
1466 btnPause: null,
1467 auto: true,
1468 speed: 500,
1469 autoTime: 12000,
1470 easing: null,
1471 start: 0,
1472 scroll: 1,
1473 pagination: true
1474
1475 }, o || {});
1476
1477 //Set up a carousel for each
1478 return this.each(function() {
1479
1480 var running = false;
1481 var animCss = o.vertical ? "top" : "left";
1482 var sizeCss = o.vertical ? "height" : "width";
1483 var div = $(this);
1484 var ul = $("ul", div);
1485 var tLi = $("li", ul);
1486 var tl = tLi.size();
1487 var timer = null;
1488
1489 var li = $("li", ul);
1490 var itemLength = li.size();
1491 var curr = o.start;
1492
1493 li.css({float: o.vertical ? "none" : "left"});
1494 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1495 div.css({position: "relative", "z-index": "2", left: "0px"});
1496
1497 var liSize = o.vertical ? height(li) : width(li);
1498 var ulSize = liSize * itemLength;
1499 var divSize = liSize;
1500
1501 li.css({width: li.width(), height: li.height()});
1502 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1503
1504 div.css(sizeCss, divSize+"px");
1505
1506 //Pagination
1507 if (o.pagination) {
1508 var pagination = $("<div class='pagination'></div>");
1509 var pag_ul = $("<ul></ul>");
1510 if (tl > 1) {
1511 for (var i=0;i<tl;i++) {
1512 var li = $("<li>"+i+"</li>");
1513 pag_ul.append(li);
1514 if (i==o.start) li.addClass('active');
1515 li.click(function() {
1516 go(parseInt($(this).text()));
1517 })
1518 }
1519 pagination.append(pag_ul);
1520 div.append(pagination);
1521 }
1522 }
1523
1524 //Previous button
1525 if(o.btnPrev)
1526 $(o.btnPrev).click(function(e) {
1527 e.preventDefault();
1528 return go(curr-o.scroll);
1529 });
1530
1531 //Next button
1532 if(o.btnNext)
1533 $(o.btnNext).click(function(e) {
1534 e.preventDefault();
1535 return go(curr+o.scroll);
1536 });
1537
1538 //Pause button
1539 if(o.btnPause)
1540 $(o.btnPause).click(function(e) {
1541 e.preventDefault();
1542 if ($(this).hasClass('paused')) {
1543 startRotateTimer();
1544 } else {
1545 pauseRotateTimer();
1546 }
1547 });
1548
1549 //Auto rotation
1550 if(o.auto) startRotateTimer();
1551
1552 function startRotateTimer() {
1553 clearInterval(timer);
1554 timer = setInterval(function() {
1555 if (curr == tl-1) {
1556 go(0);
1557 } else {
1558 go(curr+o.scroll);
1559 }
1560 }, o.autoTime);
1561 $(o.btnPause).removeClass('paused');
1562 }
1563
1564 function pauseRotateTimer() {
1565 clearInterval(timer);
1566 $(o.btnPause).addClass('paused');
1567 }
1568
1569 //Go to an item
1570 function go(to) {
1571 if(!running) {
1572
1573 if(to<0) {
1574 to = itemLength-1;
1575 } else if (to>itemLength-1) {
1576 to = 0;
1577 }
1578 curr = to;
1579
1580 running = true;
1581
1582 ul.animate(
1583 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1584 function() {
1585 running = false;
1586 }
1587 );
1588
1589 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1590 $( (curr-o.scroll<0 && o.btnPrev)
1591 ||
1592 (curr+o.scroll > itemLength && o.btnNext)
1593 ||
1594 []
1595 ).addClass("disabled");
1596
1597
1598 var nav_items = $('li', pagination);
1599 nav_items.removeClass('active');
1600 nav_items.eq(to).addClass('active');
1601
1602
1603 }
1604 if(o.auto) startRotateTimer();
1605 return false;
1606 };
1607 });
1608 };
1609
1610 function css(el, prop) {
1611 return parseInt($.css(el[0], prop)) || 0;
1612 };
1613 function width(el) {
1614 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1615 };
1616 function height(el) {
1617 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1618 };
1619
1620 })(jQuery);
1621
1622
1623/*
1624 * dacSlideshow 1.0
1625 * Used on develop/index.html for side-sliding tabs
1626 *
1627 * Sample usage:
1628 * HTML -
1629 * <div class="slideshow-container">
1630 * <a href="" class="slideshow-prev">Prev</a>
1631 * <a href="" class="slideshow-next">Next</a>
1632 * <ul>
1633 * <li class="item"><img src="images/marquee1.jpg"></li>
1634 * <li class="item"><img src="images/marquee2.jpg"></li>
1635 * <li class="item"><img src="images/marquee3.jpg"></li>
1636 * <li class="item"><img src="images/marquee4.jpg"></li>
1637 * </ul>
1638 * </div>
1639 *
1640 * <script type="text/javascript">
1641 * $('.slideshow-container').dacSlideshow({
1642 * auto: true,
1643 * btnPrev: '.slideshow-prev',
1644 * btnNext: '.slideshow-next'
1645 * });
1646 * </script>
1647 *
1648 * Options:
1649 * btnPrev: optional identifier for previous button
1650 * btnNext: optional identifier for next button
1651 * auto: whether or not to auto-proceed
1652 * speed: animation speed
1653 * autoTime: time between auto-rotation
1654 * easing: easing function for transition
1655 * start: item to select by default
1656 * scroll: direction to scroll in
1657 * pagination: whether or not to include dotted pagination
1658 *
1659 */
1660 (function($) {
1661 $.fn.dacTabbedList = function(o) {
1662
1663 //Options - see above
1664 o = $.extend({
1665 speed : 250,
1666 easing: null,
1667 nav_id: null,
1668 frame_id: null
1669 }, o || {});
1670
1671 //Set up a carousel for each
1672 return this.each(function() {
1673
1674 var curr = 0;
1675 var running = false;
1676 var animCss = "margin-left";
1677 var sizeCss = "width";
1678 var div = $(this);
1679
1680 var nav = $(o.nav_id, div);
1681 var nav_li = $("li", nav);
1682 var nav_size = nav_li.size();
1683 var frame = div.find(o.frame_id);
1684 var content_width = $(frame).find('ul').width();
1685 //Buttons
1686 $(nav_li).click(function(e) {
1687 go($(nav_li).index($(this)));
1688 })
1689
1690 //Go to an item
1691 function go(to) {
1692 if(!running) {
1693 curr = to;
1694 running = true;
1695
1696 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1697 function() {
1698 running = false;
1699 }
1700 );
1701
1702
1703 nav_li.removeClass('active');
1704 nav_li.eq(to).addClass('active');
1705
1706
1707 }
1708 return false;
1709 };
1710 });
1711 };
1712
1713 function css(el, prop) {
1714 return parseInt($.css(el[0], prop)) || 0;
1715 };
1716 function width(el) {
1717 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1718 };
1719 function height(el) {
1720 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1721 };
1722
1723 })(jQuery);
1724
1725
1726
1727
1728
1729/* ######################################################## */
1730/* ################ SEARCH SUGGESTIONS ################## */
1731/* ######################################################## */
1732
1733
1734
1735var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1736var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1737
1738var gMatches = new Array();
1739var gLastText = "";
1740var gInitialized = false;
1741var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1742var gListLength = 0;
1743
1744
1745var gGoogleMatches = new Array();
1746var ROW_COUNT_GOOGLE = 15; // max number of results in list
1747var gGoogleListLength = 0;
1748
1749var gDocsMatches = new Array();
1750var ROW_COUNT_DOCS = 100; // max number of results in list
1751var gDocsListLength = 0;
1752
1753function onSuggestionClick(link) {
1754 // When user clicks a suggested document, track it
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001755 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
1756 'query: ' + $("#search_autocomplete").val().toLowerCase());
Dirk Dougherty541b4942014-02-14 18:31:53 -08001757}
1758
1759function set_item_selected($li, selected)
1760{
1761 if (selected) {
1762 $li.attr('class','jd-autocomplete jd-selected');
1763 } else {
1764 $li.attr('class','jd-autocomplete');
1765 }
1766}
1767
1768function set_item_values(toroot, $li, match)
1769{
1770 var $link = $('a',$li);
1771 $link.html(match.__hilabel || match.label);
1772 $link.attr('href',toroot + match.link);
1773}
1774
1775function set_item_values_jd(toroot, $li, match)
1776{
1777 var $link = $('a',$li);
1778 $link.html(match.title);
1779 $link.attr('href',toroot + match.url);
1780}
1781
1782function new_suggestion($list) {
1783 var $li = $("<li class='jd-autocomplete'></li>");
1784 $list.append($li);
1785
1786 $li.mousedown(function() {
1787 window.location = this.firstChild.getAttribute("href");
1788 });
1789 $li.mouseover(function() {
1790 $('.search_filtered_wrapper li').removeClass('jd-selected');
1791 $(this).addClass('jd-selected');
1792 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1793 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
1794 });
1795 $li.append("<a onclick='onSuggestionClick(this)'></a>");
1796 $li.attr('class','show-item');
1797 return $li;
1798}
1799
1800function sync_selection_table(toroot)
1801{
1802 var $li; //list item jquery object
1803 var i; //list item iterator
1804
1805 // if there are NO results at all, hide all columns
1806 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1807 $('.suggest-card').hide(300);
1808 return;
1809 }
1810
1811 // if there are api results
1812 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
1813 // reveal suggestion list
Dirk Dougherty541b4942014-02-14 18:31:53 -08001814 $('.suggest-card.reference').show();
1815 var listIndex = 0; // list index position
1816
1817 // reset the lists
Dirk Dougherty032a4942015-05-04 18:17:33 -07001818 $(".suggest-card.reference li").remove();
Dirk Dougherty541b4942014-02-14 18:31:53 -08001819
1820 // ########### ANDROID RESULTS #############
1821 if (gMatches.length > 0) {
1822
1823 // determine android results to show
1824 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1825 gMatches.length : ROW_COUNT_FRAMEWORK;
1826 for (i=0; i<gListLength; i++) {
1827 var $li = new_suggestion($(".suggest-card.reference ul"));
1828 set_item_values(toroot, $li, gMatches[i]);
1829 set_item_selected($li, i == gSelectedIndex);
1830 }
1831 }
1832
1833 // ########### GOOGLE RESULTS #############
1834 if (gGoogleMatches.length > 0) {
1835 // show header for list
1836 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
1837
1838 // determine google results to show
1839 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1840 for (i=0; i<gGoogleListLength; i++) {
1841 var $li = new_suggestion($(".suggest-card.reference ul"));
1842 set_item_values(toroot, $li, gGoogleMatches[i]);
1843 set_item_selected($li, i == gSelectedIndex);
1844 }
1845 }
1846 } else {
1847 $('.suggest-card.reference').hide();
Dirk Dougherty541b4942014-02-14 18:31:53 -08001848 }
1849
1850 // ########### JD DOC RESULTS #############
1851 if (gDocsMatches.length > 0) {
1852 // reset the lists
Dirk Dougherty032a4942015-05-04 18:17:33 -07001853 $(".suggest-card:not(.reference) li").remove();
Dirk Dougherty541b4942014-02-14 18:31:53 -08001854
1855 // determine google results to show
1856 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1857 // The order must match the reverse order that each section appears as a card in
1858 // the suggestion UI... this may be only for the "develop" grouped items though.
1859 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1860 for (i=0; i<gDocsListLength; i++) {
1861 var sugg = gDocsMatches[i];
1862 var $li;
1863 if (sugg.type == "design") {
1864 $li = new_suggestion($(".suggest-card.design ul"));
1865 } else
1866 if (sugg.type == "distribute") {
1867 $li = new_suggestion($(".suggest-card.distribute ul"));
1868 } else
1869 if (sugg.type == "samples") {
1870 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1871 } else
1872 if (sugg.type == "training") {
1873 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1874 } else
1875 if (sugg.type == "about"||"guide"||"tools"||"google") {
1876 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1877 } else {
1878 continue;
1879 }
1880
1881 set_item_values_jd(toroot, $li, sugg);
1882 set_item_selected($li, i == gSelectedIndex);
1883 }
1884
1885 // add heading and show or hide card
1886 if ($(".suggest-card.design li").length > 0) {
1887 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1888 $(".suggest-card.design").show(300);
1889 } else {
1890 $('.suggest-card.design').hide(300);
1891 }
1892 if ($(".suggest-card.distribute li").length > 0) {
1893 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1894 $(".suggest-card.distribute").show(300);
1895 } else {
1896 $('.suggest-card.distribute').hide(300);
1897 }
1898 if ($(".child-card.guides li").length > 0) {
1899 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1900 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1901 }
1902 if ($(".child-card.training li").length > 0) {
1903 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1904 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1905 }
1906 if ($(".child-card.samples li").length > 0) {
1907 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1908 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1909 }
1910
1911 if ($(".suggest-card.develop li").length > 0) {
1912 $(".suggest-card.develop").show(300);
1913 } else {
1914 $('.suggest-card.develop').hide(300);
1915 }
1916
1917 } else {
Dirk Dougherty032a4942015-05-04 18:17:33 -07001918 $('.suggest-card:not(.reference)').hide(300);
Dirk Dougherty541b4942014-02-14 18:31:53 -08001919 }
1920}
1921
1922/** Called by the search input's onkeydown and onkeyup events.
1923 * Handles navigation with keyboard arrows, Enter key to invoke search,
1924 * otherwise invokes search suggestions on key-up event.
1925 * @param e The JS event
1926 * @param kd True if the event is key-down
1927 * @param toroot A string for the site's root path
1928 * @returns True if the event should bubble up
1929 */
1930function search_changed(e, kd, toroot)
1931{
1932 var currentLang = getLangPref();
1933 var search = document.getElementById("search_autocomplete");
1934 var text = search.value.replace(/(^ +)|( +$)/g, '');
1935 // get the ul hosting the currently selected item
1936 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1937 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1938 var $selectedUl = $columns[gSelectedColumn];
1939
1940 // show/hide the close button
1941 if (text != '') {
Dirk Dougherty032a4942015-05-04 18:17:33 -07001942 $("#search-close").removeClass("hide");
Dirk Dougherty541b4942014-02-14 18:31:53 -08001943 } else {
Dirk Dougherty032a4942015-05-04 18:17:33 -07001944 $("#search-close").addClass("hide");
Dirk Dougherty541b4942014-02-14 18:31:53 -08001945 }
1946 // 27 = esc
1947 if (e.keyCode == 27) {
1948 // close all search results
Dirk Dougherty032a4942015-05-04 18:17:33 -07001949 if (kd) $('#search-close').trigger('click');
Dirk Dougherty541b4942014-02-14 18:31:53 -08001950 return true;
1951 }
1952 // 13 = enter
1953 else if (e.keyCode == 13) {
1954 if (gSelectedIndex < 0) {
1955 $('.suggest-card').hide();
1956 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1957 // if results aren't showing (and text not empty), return true to allow search to execute
Scott Main4868e9b2014-04-14 19:00:12 -07001958 $('body,html').animate({scrollTop:0}, '500', 'swing');
Dirk Dougherty541b4942014-02-14 18:31:53 -08001959 return true;
1960 } else {
1961 // otherwise, results are already showing, so allow ajax to auto refresh the results
1962 // and ignore this Enter press to avoid the reload.
1963 return false;
1964 }
1965 } else if (kd && gSelectedIndex >= 0) {
1966 // click the link corresponding to selected item
1967 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
1968 return false;
1969 }
1970 }
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001971 // If Google results are showing, return true to allow ajax search to execute
Dirk Dougherty541b4942014-02-14 18:31:53 -08001972 else if ($("#searchResults").is(":visible")) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001973 // Also, if search_results is scrolled out of view, scroll to top to make results visible
1974 if ((sticky ) && (search.value != "")) {
1975 $('body,html').animate({scrollTop:0}, '500', 'swing');
1976 }
Dirk Dougherty541b4942014-02-14 18:31:53 -08001977 return true;
1978 }
1979 // 38 UP ARROW
1980 else if (kd && (e.keyCode == 38)) {
1981 // if the next item is a header, skip it
1982 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
1983 gSelectedIndex--;
1984 }
1985 if (gSelectedIndex >= 0) {
1986 $('li', $selectedUl).removeClass('jd-selected');
1987 gSelectedIndex--;
1988 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1989 // If user reaches top, reset selected column
1990 if (gSelectedIndex < 0) {
1991 gSelectedColumn = -1;
1992 }
1993 }
1994 return false;
1995 }
1996 // 40 DOWN ARROW
1997 else if (kd && (e.keyCode == 40)) {
1998 // if the next item is a header, skip it
1999 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
2000 gSelectedIndex++;
2001 }
2002 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
2003 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
2004 $('li', $selectedUl).removeClass('jd-selected');
2005 gSelectedIndex++;
2006 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2007 }
2008 return false;
2009 }
2010 // Consider left/right arrow navigation
2011 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
2012 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
2013 // 37 LEFT ARROW
2014 // go left only if current column is not left-most column (last column)
2015 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
2016 $('li', $selectedUl).removeClass('jd-selected');
2017 gSelectedColumn++;
2018 $selectedUl = $columns[gSelectedColumn];
2019 // keep or reset the selected item to last item as appropriate
2020 gSelectedIndex = gSelectedIndex >
2021 $("li", $selectedUl).length-1 ?
2022 $("li", $selectedUl).length-1 : gSelectedIndex;
2023 // if the corresponding item is a header, move down
2024 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2025 gSelectedIndex++;
2026 }
2027 // set item selected
2028 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2029 return false;
2030 }
2031 // 39 RIGHT ARROW
2032 // go right only if current column is not the right-most column (first column)
2033 else if (e.keyCode == 39 && gSelectedColumn > 0) {
2034 $('li', $selectedUl).removeClass('jd-selected');
2035 gSelectedColumn--;
2036 $selectedUl = $columns[gSelectedColumn];
2037 // keep or reset the selected item to last item as appropriate
2038 gSelectedIndex = gSelectedIndex >
2039 $("li", $selectedUl).length-1 ?
2040 $("li", $selectedUl).length-1 : gSelectedIndex;
2041 // if the corresponding item is a header, move down
2042 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2043 gSelectedIndex++;
2044 }
2045 // set item selected
2046 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2047 return false;
2048 }
2049 }
2050
2051 // if key-up event and not arrow down/up/left/right,
2052 // read the search query and add suggestions to gMatches
2053 else if (!kd && (e.keyCode != 40)
2054 && (e.keyCode != 38)
2055 && (e.keyCode != 37)
2056 && (e.keyCode != 39)) {
2057 gSelectedIndex = -1;
2058 gMatches = new Array();
2059 matchedCount = 0;
2060 gGoogleMatches = new Array();
2061 matchedCountGoogle = 0;
2062 gDocsMatches = new Array();
2063 matchedCountDocs = 0;
2064
2065 // Search for Android matches
2066 for (var i=0; i<DATA.length; i++) {
2067 var s = DATA[i];
2068 if (text.length != 0 &&
2069 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2070 gMatches[matchedCount] = s;
2071 matchedCount++;
2072 }
2073 }
2074 rank_autocomplete_api_results(text, gMatches);
2075 for (var i=0; i<gMatches.length; i++) {
2076 var s = gMatches[i];
2077 }
2078
2079
2080 // Search for Google matches
2081 for (var i=0; i<GOOGLE_DATA.length; i++) {
2082 var s = GOOGLE_DATA[i];
2083 if (text.length != 0 &&
2084 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2085 gGoogleMatches[matchedCountGoogle] = s;
2086 matchedCountGoogle++;
2087 }
2088 }
2089 rank_autocomplete_api_results(text, gGoogleMatches);
2090 for (var i=0; i<gGoogleMatches.length; i++) {
2091 var s = gGoogleMatches[i];
2092 }
2093
2094 highlight_autocomplete_result_labels(text);
2095
2096
2097
2098 // Search for matching JD docs
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002099 if (text.length >= 2) {
Dirk Dougherty541b4942014-02-14 18:31:53 -08002100 // Regex to match only the beginning of a word
2101 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
2102
2103
2104 // Search for Training classes
2105 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
2106 // current search comparison, with counters for tag and title,
2107 // used later to improve ranking
2108 var s = TRAINING_RESOURCES[i];
2109 s.matched_tag = 0;
2110 s.matched_title = 0;
2111 var matched = false;
2112
2113 // Check if query matches any tags; work backwards toward 1 to assist ranking
2114 for (var j = s.keywords.length - 1; j >= 0; j--) {
2115 // it matches a tag
2116 if (s.keywords[j].toLowerCase().match(textRegex)) {
2117 matched = true;
2118 s.matched_tag = j + 1; // add 1 to index position
2119 }
2120 }
2121 // Don't consider doc title for lessons (only for class landing pages),
2122 // unless the lesson has a tag that already matches
2123 if ((s.lang == currentLang) &&
2124 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
2125 // it matches the doc title
2126 if (s.title.toLowerCase().match(textRegex)) {
2127 matched = true;
2128 s.matched_title = 1;
2129 }
2130 }
2131 if (matched) {
2132 gDocsMatches[matchedCountDocs] = s;
2133 matchedCountDocs++;
2134 }
2135 }
2136
2137
2138 // Search for API Guides
2139 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2140 // current search comparison, with counters for tag and title,
2141 // used later to improve ranking
2142 var s = GUIDE_RESOURCES[i];
2143 s.matched_tag = 0;
2144 s.matched_title = 0;
2145 var matched = false;
2146
2147 // Check if query matches any tags; work backwards toward 1 to assist ranking
2148 for (var j = s.keywords.length - 1; j >= 0; j--) {
2149 // it matches a tag
2150 if (s.keywords[j].toLowerCase().match(textRegex)) {
2151 matched = true;
2152 s.matched_tag = j + 1; // add 1 to index position
2153 }
2154 }
2155 // Check if query matches the doc title, but only for current language
2156 if (s.lang == currentLang) {
2157 // if query matches the doc title
2158 if (s.title.toLowerCase().match(textRegex)) {
2159 matched = true;
2160 s.matched_title = 1;
2161 }
2162 }
2163 if (matched) {
2164 gDocsMatches[matchedCountDocs] = s;
2165 matchedCountDocs++;
2166 }
2167 }
2168
2169
2170 // Search for Tools Guides
2171 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2172 // current search comparison, with counters for tag and title,
2173 // used later to improve ranking
2174 var s = TOOLS_RESOURCES[i];
2175 s.matched_tag = 0;
2176 s.matched_title = 0;
2177 var matched = false;
2178
2179 // Check if query matches any tags; work backwards toward 1 to assist ranking
2180 for (var j = s.keywords.length - 1; j >= 0; j--) {
2181 // it matches a tag
2182 if (s.keywords[j].toLowerCase().match(textRegex)) {
2183 matched = true;
2184 s.matched_tag = j + 1; // add 1 to index position
2185 }
2186 }
2187 // Check if query matches the doc title, but only for current language
2188 if (s.lang == currentLang) {
2189 // if query matches the doc title
2190 if (s.title.toLowerCase().match(textRegex)) {
2191 matched = true;
2192 s.matched_title = 1;
2193 }
2194 }
2195 if (matched) {
2196 gDocsMatches[matchedCountDocs] = s;
2197 matchedCountDocs++;
2198 }
2199 }
2200
2201
2202 // Search for About docs
2203 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2204 // current search comparison, with counters for tag and title,
2205 // used later to improve ranking
2206 var s = ABOUT_RESOURCES[i];
2207 s.matched_tag = 0;
2208 s.matched_title = 0;
2209 var matched = false;
2210
2211 // Check if query matches any tags; work backwards toward 1 to assist ranking
2212 for (var j = s.keywords.length - 1; j >= 0; j--) {
2213 // it matches a tag
2214 if (s.keywords[j].toLowerCase().match(textRegex)) {
2215 matched = true;
2216 s.matched_tag = j + 1; // add 1 to index position
2217 }
2218 }
2219 // Check if query matches the doc title, but only for current language
2220 if (s.lang == currentLang) {
2221 // if query matches the doc title
2222 if (s.title.toLowerCase().match(textRegex)) {
2223 matched = true;
2224 s.matched_title = 1;
2225 }
2226 }
2227 if (matched) {
2228 gDocsMatches[matchedCountDocs] = s;
2229 matchedCountDocs++;
2230 }
2231 }
2232
2233
2234 // Search for Design guides
2235 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2236 // current search comparison, with counters for tag and title,
2237 // used later to improve ranking
2238 var s = DESIGN_RESOURCES[i];
2239 s.matched_tag = 0;
2240 s.matched_title = 0;
2241 var matched = false;
2242
2243 // Check if query matches any tags; work backwards toward 1 to assist ranking
2244 for (var j = s.keywords.length - 1; j >= 0; j--) {
2245 // it matches a tag
2246 if (s.keywords[j].toLowerCase().match(textRegex)) {
2247 matched = true;
2248 s.matched_tag = j + 1; // add 1 to index position
2249 }
2250 }
2251 // Check if query matches the doc title, but only for current language
2252 if (s.lang == currentLang) {
2253 // if query matches the doc title
2254 if (s.title.toLowerCase().match(textRegex)) {
2255 matched = true;
2256 s.matched_title = 1;
2257 }
2258 }
2259 if (matched) {
2260 gDocsMatches[matchedCountDocs] = s;
2261 matchedCountDocs++;
2262 }
2263 }
2264
2265
2266 // Search for Distribute guides
2267 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2268 // current search comparison, with counters for tag and title,
2269 // used later to improve ranking
2270 var s = DISTRIBUTE_RESOURCES[i];
2271 s.matched_tag = 0;
2272 s.matched_title = 0;
2273 var matched = false;
2274
2275 // Check if query matches any tags; work backwards toward 1 to assist ranking
2276 for (var j = s.keywords.length - 1; j >= 0; j--) {
2277 // it matches a tag
2278 if (s.keywords[j].toLowerCase().match(textRegex)) {
2279 matched = true;
2280 s.matched_tag = j + 1; // add 1 to index position
2281 }
2282 }
2283 // Check if query matches the doc title, but only for current language
2284 if (s.lang == currentLang) {
2285 // if query matches the doc title
2286 if (s.title.toLowerCase().match(textRegex)) {
2287 matched = true;
2288 s.matched_title = 1;
2289 }
2290 }
2291 if (matched) {
2292 gDocsMatches[matchedCountDocs] = s;
2293 matchedCountDocs++;
2294 }
2295 }
2296
2297
2298 // Search for Google guides
2299 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2300 // current search comparison, with counters for tag and title,
2301 // used later to improve ranking
2302 var s = GOOGLE_RESOURCES[i];
2303 s.matched_tag = 0;
2304 s.matched_title = 0;
2305 var matched = false;
2306
2307 // Check if query matches any tags; work backwards toward 1 to assist ranking
2308 for (var j = s.keywords.length - 1; j >= 0; j--) {
2309 // it matches a tag
2310 if (s.keywords[j].toLowerCase().match(textRegex)) {
2311 matched = true;
2312 s.matched_tag = j + 1; // add 1 to index position
2313 }
2314 }
2315 // Check if query matches the doc title, but only for current language
2316 if (s.lang == currentLang) {
2317 // if query matches the doc title
2318 if (s.title.toLowerCase().match(textRegex)) {
2319 matched = true;
2320 s.matched_title = 1;
2321 }
2322 }
2323 if (matched) {
2324 gDocsMatches[matchedCountDocs] = s;
2325 matchedCountDocs++;
2326 }
2327 }
2328
2329
2330 // Search for Samples
2331 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2332 // current search comparison, with counters for tag and title,
2333 // used later to improve ranking
2334 var s = SAMPLES_RESOURCES[i];
2335 s.matched_tag = 0;
2336 s.matched_title = 0;
2337 var matched = false;
2338 // Check if query matches any tags; work backwards toward 1 to assist ranking
2339 for (var j = s.keywords.length - 1; j >= 0; j--) {
2340 // it matches a tag
2341 if (s.keywords[j].toLowerCase().match(textRegex)) {
2342 matched = true;
2343 s.matched_tag = j + 1; // add 1 to index position
2344 }
2345 }
2346 // Check if query matches the doc title, but only for current language
2347 if (s.lang == currentLang) {
2348 // if query matches the doc title.t
2349 if (s.title.toLowerCase().match(textRegex)) {
2350 matched = true;
2351 s.matched_title = 1;
2352 }
2353 }
2354 if (matched) {
2355 gDocsMatches[matchedCountDocs] = s;
2356 matchedCountDocs++;
2357 }
2358 }
2359
2360 // Rank/sort all the matched pages
2361 rank_autocomplete_doc_results(text, gDocsMatches);
2362 }
2363
2364 // draw the suggestions
2365 sync_selection_table(toroot);
2366 return true; // allow the event to bubble up to the search api
2367 }
2368}
2369
2370/* Order the jd doc result list based on match quality */
2371function rank_autocomplete_doc_results(query, matches) {
2372 query = query || '';
2373 if (!matches || !matches.length)
2374 return;
2375
2376 var _resultScoreFn = function(match) {
2377 var score = 1.0;
2378
2379 // if the query matched a tag
2380 if (match.matched_tag > 0) {
2381 // multiply score by factor relative to position in tags list (max of 3)
2382 score *= 3 / match.matched_tag;
2383
2384 // if it also matched the title
2385 if (match.matched_title > 0) {
2386 score *= 2;
2387 }
2388 } else if (match.matched_title > 0) {
2389 score *= 3;
2390 }
2391
2392 return score;
2393 };
2394
2395 for (var i=0; i<matches.length; i++) {
2396 matches[i].__resultScore = _resultScoreFn(matches[i]);
2397 }
2398
2399 matches.sort(function(a,b){
2400 var n = b.__resultScore - a.__resultScore;
2401 if (n == 0) // lexicographical sort if scores are the same
2402 n = (a.label < b.label) ? -1 : 1;
2403 return n;
2404 });
2405}
2406
2407/* Order the result list based on match quality */
2408function rank_autocomplete_api_results(query, matches) {
2409 query = query || '';
2410 if (!matches || !matches.length)
2411 return;
2412
2413 // helper function that gets the last occurence index of the given regex
2414 // in the given string, or -1 if not found
2415 var _lastSearch = function(s, re) {
2416 if (s == '')
2417 return -1;
2418 var l = -1;
2419 var tmp;
2420 while ((tmp = s.search(re)) >= 0) {
2421 if (l < 0) l = 0;
2422 l += tmp;
2423 s = s.substr(tmp + 1);
2424 }
2425 return l;
2426 };
2427
2428 // helper function that counts the occurrences of a given character in
2429 // a given string
2430 var _countChar = function(s, c) {
2431 var n = 0;
2432 for (var i=0; i<s.length; i++)
2433 if (s.charAt(i) == c) ++n;
2434 return n;
2435 };
2436
2437 var queryLower = query.toLowerCase();
2438 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2439 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2440 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2441
2442 var _resultScoreFn = function(result) {
2443 // scores are calculated based on exact and prefix matches,
2444 // and then number of path separators (dots) from the last
2445 // match (i.e. favoring classes and deep package names)
2446 var score = 1.0;
2447 var labelLower = result.label.toLowerCase();
2448 var t;
2449 t = _lastSearch(labelLower, partExactAlnumRE);
2450 if (t >= 0) {
2451 // exact part match
2452 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2453 score *= 200 / (partsAfter + 1);
2454 } else {
2455 t = _lastSearch(labelLower, partPrefixAlnumRE);
2456 if (t >= 0) {
2457 // part prefix match
2458 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2459 score *= 20 / (partsAfter + 1);
2460 }
2461 }
2462
2463 return score;
2464 };
2465
2466 for (var i=0; i<matches.length; i++) {
2467 // if the API is deprecated, default score is 0; otherwise, perform scoring
2468 if (matches[i].deprecated == "true") {
2469 matches[i].__resultScore = 0;
2470 } else {
2471 matches[i].__resultScore = _resultScoreFn(matches[i]);
2472 }
2473 }
2474
2475 matches.sort(function(a,b){
2476 var n = b.__resultScore - a.__resultScore;
2477 if (n == 0) // lexicographical sort if scores are the same
2478 n = (a.label < b.label) ? -1 : 1;
2479 return n;
2480 });
2481}
2482
2483/* Add emphasis to part of string that matches query */
2484function highlight_autocomplete_result_labels(query) {
2485 query = query || '';
2486 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
2487 return;
2488
2489 var queryLower = query.toLowerCase();
2490 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2491 var queryRE = new RegExp(
2492 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2493 for (var i=0; i<gMatches.length; i++) {
2494 gMatches[i].__hilabel = gMatches[i].label.replace(
2495 queryRE, '<b>$1</b>');
2496 }
2497 for (var i=0; i<gGoogleMatches.length; i++) {
2498 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2499 queryRE, '<b>$1</b>');
2500 }
2501}
2502
2503function search_focus_changed(obj, focused)
2504{
2505 if (!focused) {
2506 if(obj.value == ""){
Dirk Dougherty032a4942015-05-04 18:17:33 -07002507 $("#search-close").addClass("hide");
Dirk Dougherty541b4942014-02-14 18:31:53 -08002508 }
2509 $(".suggest-card").hide();
2510 }
2511}
2512
2513function submit_search() {
2514 var query = document.getElementById('search_autocomplete').value;
2515 location.hash = 'q=' + query;
2516 loadSearchResults();
Scott Maind6a8e662014-04-12 16:40:48 -07002517 $("#searchResults").slideDown('slow', setStickyTop);
Dirk Dougherty541b4942014-02-14 18:31:53 -08002518 return false;
2519}
2520
2521
2522function hideResults() {
Scott Maind6a8e662014-04-12 16:40:48 -07002523 $("#searchResults").slideUp('fast', setStickyTop);
Dirk Dougherty032a4942015-05-04 18:17:33 -07002524 $("#search-close").addClass("hide");
Dirk Dougherty541b4942014-02-14 18:31:53 -08002525 location.hash = '';
2526
2527 $("#search_autocomplete").val("").blur();
2528
2529 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2530 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
2531
2532 // forcefully regain key-up event control (previously jacked by search api)
2533 $("#search_autocomplete").keyup(function(event) {
2534 return search_changed(event, false, toRoot);
2535 });
2536
2537 return false;
2538}
2539
2540
2541
2542/* ########################################################## */
2543/* ################ CUSTOM SEARCH ENGINE ################## */
2544/* ########################################################## */
2545
2546var searchControl;
2547google.load('search', '1', {"callback" : function() {
2548 searchControl = new google.search.SearchControl();
2549 } });
2550
2551function loadSearchResults() {
2552 document.getElementById("search_autocomplete").style.color = "#000";
2553
2554 searchControl = new google.search.SearchControl();
2555
2556 // use our existing search form and use tabs when multiple searchers are used
2557 drawOptions = new google.search.DrawOptions();
2558 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2559 drawOptions.setInput(document.getElementById("search_autocomplete"));
2560
2561 // configure search result options
2562 searchOptions = new google.search.SearcherOptions();
2563 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2564
2565 // configure each of the searchers, for each tab
2566 devSiteSearcher = new google.search.WebSearch();
2567 devSiteSearcher.setUserDefinedLabel("All");
2568 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2569
2570 designSearcher = new google.search.WebSearch();
2571 designSearcher.setUserDefinedLabel("Design");
2572 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2573
2574 trainingSearcher = new google.search.WebSearch();
2575 trainingSearcher.setUserDefinedLabel("Training");
2576 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2577
2578 guidesSearcher = new google.search.WebSearch();
2579 guidesSearcher.setUserDefinedLabel("Guides");
2580 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2581
2582 referenceSearcher = new google.search.WebSearch();
2583 referenceSearcher.setUserDefinedLabel("Reference");
2584 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2585
2586 googleSearcher = new google.search.WebSearch();
2587 googleSearcher.setUserDefinedLabel("Google Services");
2588 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2589
2590 blogSearcher = new google.search.WebSearch();
2591 blogSearcher.setUserDefinedLabel("Blog");
2592 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2593
2594 // add each searcher to the search control
2595 searchControl.addSearcher(devSiteSearcher, searchOptions);
2596 searchControl.addSearcher(designSearcher, searchOptions);
2597 searchControl.addSearcher(trainingSearcher, searchOptions);
2598 searchControl.addSearcher(guidesSearcher, searchOptions);
2599 searchControl.addSearcher(referenceSearcher, searchOptions);
2600 searchControl.addSearcher(googleSearcher, searchOptions);
2601 searchControl.addSearcher(blogSearcher, searchOptions);
2602
2603 // configure result options
2604 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2605 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2606 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2607 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2608
2609 // upon ajax search, refresh the url and search title
2610 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2611 updateResultTitle(query);
2612 var query = document.getElementById('search_autocomplete').value;
2613 location.hash = 'q=' + query;
2614 });
2615
2616 // once search results load, set up click listeners
2617 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2618 addResultClickListeners();
2619 });
2620
2621 // draw the search results box
2622 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2623
2624 // get query and execute the search
2625 searchControl.execute(decodeURI(getQuery(location.hash)));
2626
2627 document.getElementById("search_autocomplete").focus();
2628 addTabListeners();
2629}
2630// End of loadSearchResults
2631
2632
2633google.setOnLoadCallback(function(){
2634 if (location.hash.indexOf("q=") == -1) {
2635 // if there's no query in the url, don't search and make sure results are hidden
2636 $('#searchResults').hide();
2637 return;
2638 } else {
2639 // first time loading search results for this page
Scott Maind6a8e662014-04-12 16:40:48 -07002640 $('#searchResults').slideDown('slow', setStickyTop);
Dirk Dougherty032a4942015-05-04 18:17:33 -07002641 $("#search-close").removeClass("hide");
Dirk Dougherty541b4942014-02-14 18:31:53 -08002642 loadSearchResults();
2643 }
2644}, true);
2645
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002646/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2647 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
2648function offsetScrollForSticky() {
2649 // Ignore if there's no search bar (some special pages have no header)
2650 if ($("#search-container").length < 1) return;
2651
2652 var hash = escape(location.hash.substr(1));
2653 var $matchingElement = $("#"+hash);
2654 // Sanity check that there's an element with that ID on the page
2655 if ($matchingElement.length) {
2656 // If the position of the target element is near the top of the page (<20px, where we expect it
2657 // to be because we need to move it down 60px to become in view), then move it down 60px
2658 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2659 $(window).scrollTop($(window).scrollTop() - 60);
2660 }
2661 }
2662}
2663
Dirk Dougherty541b4942014-02-14 18:31:53 -08002664// when an event on the browser history occurs (back, forward, load) requery hash and do search
2665$(window).hashchange( function(){
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002666 // Ignore if there's no search bar (some special pages have no header)
2667 if ($("#search-container").length < 1) return;
2668
Scott Main4868e9b2014-04-14 19:00:12 -07002669 // If the hash isn't a search query or there's an error in the query,
2670 // then adjust the scroll position to account for sticky header, then exit.
Dirk Dougherty541b4942014-02-14 18:31:53 -08002671 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2672 // If the results pane is open, close it.
2673 if (!$("#searchResults").is(":hidden")) {
2674 hideResults();
2675 }
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002676 offsetScrollForSticky();
Dirk Dougherty541b4942014-02-14 18:31:53 -08002677 return;
2678 }
2679
2680 // Otherwise, we have a search to do
2681 var query = decodeURI(getQuery(location.hash));
2682 searchControl.execute(query);
Scott Maind6a8e662014-04-12 16:40:48 -07002683 $('#searchResults').slideDown('slow', setStickyTop);
Dirk Dougherty541b4942014-02-14 18:31:53 -08002684 $("#search_autocomplete").focus();
Dirk Dougherty032a4942015-05-04 18:17:33 -07002685 $("#search-close").removeClass("hide");
Dirk Dougherty541b4942014-02-14 18:31:53 -08002686
2687 updateResultTitle(query);
2688});
2689
2690function updateResultTitle(query) {
2691 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2692}
2693
2694// forcefully regain key-up event control (previously jacked by search api)
2695$("#search_autocomplete").keyup(function(event) {
2696 return search_changed(event, false, toRoot);
2697});
2698
2699// add event listeners to each tab so we can track the browser history
2700function addTabListeners() {
2701 var tabHeaders = $(".gsc-tabHeader");
2702 for (var i = 0; i < tabHeaders.length; i++) {
2703 $(tabHeaders[i]).attr("id",i).click(function() {
2704 /*
2705 // make a copy of the page numbers for the search left pane
2706 setTimeout(function() {
2707 // remove any residual page numbers
2708 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
2709 // move the page numbers to the left position; make a clone,
2710 // because the element is drawn to the DOM only once
2711 // and because we're going to remove it (previous line),
2712 // we need it to be available to move again as the user navigates
2713 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2714 .clone().appendTo('#searchResults .gsc-tabsArea');
2715 }, 200);
2716 */
2717 });
2718 }
2719 setTimeout(function(){$(tabHeaders[0]).click()},200);
2720}
2721
2722// add analytics tracking events to each result link
2723function addResultClickListeners() {
2724 $("#searchResults a.gs-title").each(function(index, link) {
2725 // When user clicks enter for Google search results, track it
2726 $(link).click(function() {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002727 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'),
2728 'query: ' + $("#search_autocomplete").val().toLowerCase());
Dirk Dougherty541b4942014-02-14 18:31:53 -08002729 });
2730 });
2731}
2732
2733
2734function getQuery(hash) {
2735 var queryParts = hash.split('=');
2736 return queryParts[1];
2737}
2738
2739/* returns the given string with all HTML brackets converted to entities
2740 TODO: move this to the site's JS library */
2741function escapeHTML(string) {
2742 return string.replace(/</g,"&lt;")
2743 .replace(/>/g,"&gt;");
2744}
2745
2746
2747
2748
2749
2750
2751
2752/* ######################################################## */
2753/* ################# JAVADOC REFERENCE ################### */
2754/* ######################################################## */
2755
2756/* Initialize some droiddoc stuff, but only if we're in the reference */
2757if (location.pathname.indexOf("/reference") == 0) {
2758 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2759 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2760 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
2761 $(document).ready(function() {
2762 // init available apis based on user pref
2763 changeApiLevel();
2764 initSidenavHeightResize()
2765 });
2766 }
2767}
2768
2769var API_LEVEL_COOKIE = "api_level";
2770var minLevel = 1;
2771var maxLevel = 1;
2772
2773/******* SIDENAV DIMENSIONS ************/
2774
2775 function initSidenavHeightResize() {
2776 // Change the drag bar size to nicely fit the scrollbar positions
2777 var $dragBar = $(".ui-resizable-s");
2778 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
2779
2780 $( "#resize-packages-nav" ).resizable({
2781 containment: "#nav-panels",
2782 handles: "s",
2783 alsoResize: "#packages-nav",
2784 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2785 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2786 });
2787
2788 }
2789
2790function updateSidenavFixedWidth() {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002791 if (!sticky) return;
Dirk Dougherty541b4942014-02-14 18:31:53 -08002792 $('#devdoc-nav').css({
2793 'width' : $('#side-nav').css('width'),
2794 'margin' : $('#side-nav').css('margin')
2795 });
2796 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
2797
2798 initSidenavHeightResize();
2799}
2800
2801function updateSidenavFullscreenWidth() {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002802 if (!sticky) return;
Dirk Dougherty541b4942014-02-14 18:31:53 -08002803 $('#devdoc-nav').css({
2804 'width' : $('#side-nav').css('width'),
2805 'margin' : $('#side-nav').css('margin')
2806 });
2807 $('#devdoc-nav .totop').css({'left': 'inherit'});
2808
2809 initSidenavHeightResize();
2810}
2811
2812function buildApiLevelSelector() {
2813 maxLevel = SINCE_DATA.length;
2814 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2815 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2816
2817 minLevel = parseInt($("#doc-api-level").attr("class"));
2818 // Handle provisional api levels; the provisional level will always be the highest possible level
2819 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2820 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2821 if (isNaN(minLevel) && minLevel.length) {
2822 minLevel = maxLevel;
2823 }
2824 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2825 for (var i = maxLevel-1; i >= 0; i--) {
2826 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2827 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2828 select.append(option);
2829 }
2830
2831 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2832 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2833 selectedLevelItem.setAttribute('selected',true);
2834}
2835
2836function changeApiLevel() {
2837 maxLevel = SINCE_DATA.length;
2838 var selectedLevel = maxLevel;
2839
2840 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2841 toggleVisisbleApis(selectedLevel, "body");
2842
Dirk Doughertyff233cc2015-05-04 14:37:05 -07002843 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Dirk Dougherty541b4942014-02-14 18:31:53 -08002844
2845 if (selectedLevel < minLevel) {
2846 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
2847 $("#naMessage").show().html("<div><p><strong>This " + thing
2848 + " requires API level " + minLevel + " or higher.</strong></p>"
2849 + "<p>This document is hidden because your selected API level for the documentation is "
2850 + selectedLevel + ". You can change the documentation API level with the selector "
2851 + "above the left navigation.</p>"
2852 + "<p>For more information about specifying the API level your app requires, "
2853 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2854 + ">Supporting Different Platform Versions</a>.</p>"
2855 + "<input type='button' value='OK, make this page visible' "
2856 + "title='Change the API level to " + minLevel + "' "
2857 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2858 + "</div>");
2859 } else {
2860 $("#naMessage").hide();
2861 }
2862}
2863
2864function toggleVisisbleApis(selectedLevel, context) {
2865 var apis = $(".api",context);
2866 apis.each(function(i) {
2867 var obj = $(this);
2868 var className = obj.attr("class");
2869 var apiLevelIndex = className.lastIndexOf("-")+1;
2870 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2871 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2872 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2873 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2874 return;
2875 }
2876 apiLevel = parseInt(apiLevel);
2877
2878 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2879 var selectedLevelNum = parseInt(selectedLevel)
2880 var apiLevelNum = parseInt(apiLevel);
2881 if (isNaN(apiLevelNum)) {
2882 apiLevelNum = maxLevel;
2883 }
2884
2885 // Grey things out that aren't available and give a tooltip title
2886 if (apiLevelNum > selectedLevelNum) {
2887 obj.addClass("absent").attr("title","Requires API Level \""
2888 + apiLevel + "\" or higher. To reveal, change the target API level "
2889 + "above the left navigation.");
2890 }
2891 else obj.removeClass("absent").removeAttr("title");
2892 });
2893}
2894
2895
2896
2897
2898/* ################# SIDENAV TREE VIEW ################### */
2899
2900function new_node(me, mom, text, link, children_data, api_level)
2901{
2902 var node = new Object();
2903 node.children = Array();
2904 node.children_data = children_data;
2905 node.depth = mom.depth + 1;
2906
2907 node.li = document.createElement("li");
2908 mom.get_children_ul().appendChild(node.li);
2909
2910 node.label_div = document.createElement("div");
2911 node.label_div.className = "label";
2912 if (api_level != null) {
2913 $(node.label_div).addClass("api");
2914 $(node.label_div).addClass("api-level-"+api_level);
2915 }
2916 node.li.appendChild(node.label_div);
2917
2918 if (children_data != null) {
2919 node.expand_toggle = document.createElement("a");
2920 node.expand_toggle.href = "javascript:void(0)";
2921 node.expand_toggle.onclick = function() {
2922 if (node.expanded) {
2923 $(node.get_children_ul()).slideUp("fast");
2924 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2925 node.expanded = false;
2926 } else {
2927 expand_node(me, node);
2928 }
2929 };
2930 node.label_div.appendChild(node.expand_toggle);
2931
2932 node.plus_img = document.createElement("img");
2933 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2934 node.plus_img.className = "plus";
2935 node.plus_img.width = "8";
2936 node.plus_img.border = "0";
2937 node.expand_toggle.appendChild(node.plus_img);
2938
2939 node.expanded = false;
2940 }
2941
2942 var a = document.createElement("a");
2943 node.label_div.appendChild(a);
2944 node.label = document.createTextNode(text);
2945 a.appendChild(node.label);
2946 if (link) {
2947 a.href = me.toroot + link;
2948 } else {
2949 if (children_data != null) {
2950 a.className = "nolink";
2951 a.href = "javascript:void(0)";
2952 a.onclick = node.expand_toggle.onclick;
2953 // This next line shouldn't be necessary. I'll buy a beer for the first
2954 // person who figures out how to remove this line and have the link
2955 // toggle shut on the first try. --joeo@android.com
2956 node.expanded = false;
2957 }
2958 }
2959
2960
2961 node.children_ul = null;
2962 node.get_children_ul = function() {
2963 if (!node.children_ul) {
2964 node.children_ul = document.createElement("ul");
2965 node.children_ul.className = "children_ul";
2966 node.children_ul.style.display = "none";
2967 node.li.appendChild(node.children_ul);
2968 }
2969 return node.children_ul;
2970 };
2971
2972 return node;
2973}
2974
2975
2976
2977
2978function expand_node(me, node)
2979{
2980 if (node.children_data && !node.expanded) {
2981 if (node.children_visited) {
2982 $(node.get_children_ul()).slideDown("fast");
2983 } else {
2984 get_node(me, node);
2985 if ($(node.label_div).hasClass("absent")) {
2986 $(node.get_children_ul()).addClass("absent");
2987 }
2988 $(node.get_children_ul()).slideDown("fast");
2989 }
2990 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2991 node.expanded = true;
2992
2993 // perform api level toggling because new nodes are new to the DOM
2994 var selectedLevel = $("#apiLevelSelector option:selected").val();
2995 toggleVisisbleApis(selectedLevel, "#side-nav");
2996 }
2997}
2998
2999function get_node(me, mom)
3000{
3001 mom.children_visited = true;
3002 for (var i in mom.children_data) {
3003 var node_data = mom.children_data[i];
3004 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
3005 node_data[2], node_data[3]);
3006 }
3007}
3008
3009function this_page_relative(toroot)
3010{
3011 var full = document.location.pathname;
3012 var file = "";
3013 if (toroot.substr(0, 1) == "/") {
3014 if (full.substr(0, toroot.length) == toroot) {
3015 return full.substr(toroot.length);
3016 } else {
3017 // the file isn't under toroot. Fail.
3018 return null;
3019 }
3020 } else {
3021 if (toroot != "./") {
3022 toroot = "./" + toroot;
3023 }
3024 do {
3025 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3026 var pos = full.lastIndexOf("/");
3027 file = full.substr(pos) + file;
3028 full = full.substr(0, pos);
3029 toroot = toroot.substr(0, toroot.length-3);
3030 }
3031 } while (toroot != "" && toroot != "/");
3032 return file.substr(1);
3033 }
3034}
3035
3036function find_page(url, data)
3037{
3038 var nodes = data;
3039 var result = null;
3040 for (var i in nodes) {
3041 var d = nodes[i];
3042 if (d[1] == url) {
3043 return new Array(i);
3044 }
3045 else if (d[2] != null) {
3046 result = find_page(url, d[2]);
3047 if (result != null) {
3048 return (new Array(i).concat(result));
3049 }
3050 }
3051 }
3052 return null;
3053}
3054
3055function init_default_navtree(toroot) {
3056 // load json file for navtree data
3057 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3058 // when the file is loaded, initialize the tree
3059 if(jqxhr.status === 200) {
3060 init_navtree("tree-list", toroot, NAVTREE_DATA);
3061 }
3062 });
3063
3064 // perform api level toggling because because the whole tree is new to the DOM
3065 var selectedLevel = $("#apiLevelSelector option:selected").val();
3066 toggleVisisbleApis(selectedLevel, "#side-nav");
3067}
3068
3069function init_navtree(navtree_id, toroot, root_nodes)
3070{
3071 var me = new Object();
3072 me.toroot = toroot;
3073 me.node = new Object();
3074
3075 me.node.li = document.getElementById(navtree_id);
3076 me.node.children_data = root_nodes;
3077 me.node.children = new Array();
3078 me.node.children_ul = document.createElement("ul");
3079 me.node.get_children_ul = function() { return me.node.children_ul; };
3080 //me.node.children_ul.className = "children_ul";
3081 me.node.li.appendChild(me.node.children_ul);
3082 me.node.depth = 0;
3083
3084 get_node(me, me.node);
3085
3086 me.this_page = this_page_relative(toroot);
3087 me.breadcrumbs = find_page(me.this_page, root_nodes);
3088 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3089 var mom = me.node;
3090 for (var i in me.breadcrumbs) {
3091 var j = me.breadcrumbs[i];
3092 mom = mom.children[j];
3093 expand_node(me, mom);
3094 }
3095 mom.label_div.className = mom.label_div.className + " selected";
3096 addLoadEvent(function() {
3097 scrollIntoView("nav-tree");
3098 });
3099 }
3100}
3101
3102
3103
3104
3105
3106
3107
3108
3109/* TODO: eliminate redundancy with non-google functions */
3110function init_google_navtree(navtree_id, toroot, root_nodes)
3111{
3112 var me = new Object();
3113 me.toroot = toroot;
3114 me.node = new Object();
3115
3116 me.node.li = document.getElementById(navtree_id);
3117 me.node.children_data = root_nodes;
3118 me.node.children = new Array();
3119 me.node.children_ul = document.createElement("ul");
3120 me.node.get_children_ul = function() { return me.node.children_ul; };
3121 //me.node.children_ul.className = "children_ul";
3122 me.node.li.appendChild(me.node.children_ul);
3123 me.node.depth = 0;
3124
3125 get_google_node(me, me.node);
3126}
3127
3128function new_google_node(me, mom, text, link, children_data, api_level)
3129{
3130 var node = new Object();
3131 var child;
3132 node.children = Array();
3133 node.children_data = children_data;
3134 node.depth = mom.depth + 1;
3135 node.get_children_ul = function() {
3136 if (!node.children_ul) {
3137 node.children_ul = document.createElement("ul");
3138 node.children_ul.className = "tree-list-children";
3139 node.li.appendChild(node.children_ul);
3140 }
3141 return node.children_ul;
3142 };
3143 node.li = document.createElement("li");
3144
3145 mom.get_children_ul().appendChild(node.li);
3146
3147
3148 if(link) {
3149 child = document.createElement("a");
3150
3151 }
3152 else {
3153 child = document.createElement("span");
3154 child.className = "tree-list-subtitle";
3155
3156 }
3157 if (children_data != null) {
3158 node.li.className="nav-section";
3159 node.label_div = document.createElement("div");
3160 node.label_div.className = "nav-section-header-ref";
3161 node.li.appendChild(node.label_div);
3162 get_google_node(me, node);
3163 node.label_div.appendChild(child);
3164 }
3165 else {
3166 node.li.appendChild(child);
3167 }
3168 if(link) {
3169 child.href = me.toroot + link;
3170 }
3171 node.label = document.createTextNode(text);
3172 child.appendChild(node.label);
3173
3174 node.children_ul = null;
3175
3176 return node;
3177}
3178
3179function get_google_node(me, mom)
3180{
3181 mom.children_visited = true;
3182 var linkText;
3183 for (var i in mom.children_data) {
3184 var node_data = mom.children_data[i];
3185 linkText = node_data[0];
3186
3187 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3188 linkText = linkText.substr(19, linkText.length);
3189 }
3190 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3191 node_data[2], node_data[3]);
3192 }
3193}
3194
3195
3196
3197
3198
3199
3200/****** NEW version of script to build google and sample navs dynamically ******/
3201// TODO: update Google reference docs to tolerate this new implementation
3202
3203var NODE_NAME = 0;
3204var NODE_HREF = 1;
3205var NODE_GROUP = 2;
3206var NODE_TAGS = 3;
3207var NODE_CHILDREN = 4;
3208
3209function init_google_navtree2(navtree_id, data)
3210{
3211 var $containerUl = $("#"+navtree_id);
3212 for (var i in data) {
3213 var node_data = data[i];
3214 $containerUl.append(new_google_node2(node_data));
3215 }
3216
3217 // Make all third-generation list items 'sticky' to prevent them from collapsing
3218 $containerUl.find('li li li.nav-section').addClass('sticky');
3219
3220 initExpandableNavItems("#"+navtree_id);
3221}
3222
3223function new_google_node2(node_data)
3224{
3225 var linkText = node_data[NODE_NAME];
3226 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3227 linkText = linkText.substr(19, linkText.length);
3228 }
3229 var $li = $('<li>');
3230 var $a;
3231 if (node_data[NODE_HREF] != null) {
3232 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3233 + linkText + '</a>');
3234 } else {
3235 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3236 + linkText + '/</a>');
3237 }
3238 var $childUl = $('<ul>');
3239 if (node_data[NODE_CHILDREN] != null) {
3240 $li.addClass("nav-section");
3241 $a = $('<div class="nav-section-header">').append($a);
3242 if (node_data[NODE_HREF] == null) $a.addClass('empty');
3243
3244 for (var i in node_data[NODE_CHILDREN]) {
3245 var child_node_data = node_data[NODE_CHILDREN][i];
3246 $childUl.append(new_google_node2(child_node_data));
3247 }
3248 $li.append($childUl);
3249 }
3250 $li.prepend($a);
3251
3252 return $li;
3253}
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265function showGoogleRefTree() {
3266 init_default_google_navtree(toRoot);
3267 init_default_gcm_navtree(toRoot);
3268}
3269
3270function init_default_google_navtree(toroot) {
3271 // load json file for navtree data
3272 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3273 // when the file is loaded, initialize the tree
3274 if(jqxhr.status === 200) {
3275 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3276 highlightSidenav();
3277 resizeNav();
3278 }
3279 });
3280}
3281
3282function init_default_gcm_navtree(toroot) {
3283 // load json file for navtree data
3284 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3285 // when the file is loaded, initialize the tree
3286 if(jqxhr.status === 200) {
3287 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3288 highlightSidenav();
3289 resizeNav();
3290 }
3291 });
3292}
3293
3294function showSamplesRefTree() {
3295 init_default_samples_navtree(toRoot);
3296}
3297
3298function init_default_samples_navtree(toroot) {
3299 // load json file for navtree data
3300 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3301 // when the file is loaded, initialize the tree
3302 if(jqxhr.status === 200) {
3303 // hack to remove the "about the samples" link then put it back in
3304 // after we nuke the list to remove the dummy static list of samples
3305 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3306 $("#nav.samples-nav").empty();
3307 $("#nav.samples-nav").append($firstLi);
3308
3309 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
3310 highlightSidenav();
3311 resizeNav();
3312 if ($("#jd-content #samples").length) {
3313 showSamples();
3314 }
3315 }
3316 });
3317}
3318
3319/* TOGGLE INHERITED MEMBERS */
3320
3321/* Toggle an inherited class (arrow toggle)
3322 * @param linkObj The link that was clicked.
3323 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3324 * 'null' to simply toggle.
3325 */
3326function toggleInherited(linkObj, expand) {
3327 var base = linkObj.getAttribute("id");
3328 var list = document.getElementById(base + "-list");
3329 var summary = document.getElementById(base + "-summary");
3330 var trigger = document.getElementById(base + "-trigger");
3331 var a = $(linkObj);
3332 if ( (expand == null && a.hasClass("closed")) || expand ) {
3333 list.style.display = "none";
3334 summary.style.display = "block";
3335 trigger.src = toRoot + "assets/images/triangle-opened.png";
3336 a.removeClass("closed");
3337 a.addClass("opened");
3338 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3339 list.style.display = "block";
3340 summary.style.display = "none";
3341 trigger.src = toRoot + "assets/images/triangle-closed.png";
3342 a.removeClass("opened");
3343 a.addClass("closed");
3344 }
3345 return false;
3346}
3347
3348/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3349 * @param linkObj The link that was clicked.
3350 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3351 * 'null' to simply toggle.
3352 */
3353function toggleAllInherited(linkObj, expand) {
3354 var a = $(linkObj);
3355 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3356 var expandos = $(".jd-expando-trigger", table);
3357 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3358 expandos.each(function(i) {
3359 toggleInherited(this, true);
3360 });
3361 a.text("[Collapse]");
3362 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3363 expandos.each(function(i) {
3364 toggleInherited(this, false);
3365 });
3366 a.text("[Expand]");
3367 }
3368 return false;
3369}
3370
3371/* Toggle all inherited members in the class (link in the class title)
3372 */
3373function toggleAllClassInherited() {
3374 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3375 var toggles = $(".toggle-all", $("#body-content"));
3376 if (a.text() == "[Expand All]") {
3377 toggles.each(function(i) {
3378 toggleAllInherited(this, true);
3379 });
3380 a.text("[Collapse All]");
3381 } else {
3382 toggles.each(function(i) {
3383 toggleAllInherited(this, false);
3384 });
3385 a.text("[Expand All]");
3386 }
3387 return false;
3388}
3389
3390/* Expand all inherited members in the class. Used when initiating page search */
3391function ensureAllInheritedExpanded() {
3392 var toggles = $(".toggle-all", $("#body-content"));
3393 toggles.each(function(i) {
3394 toggleAllInherited(this, true);
3395 });
3396 $("#toggleAllClassInherited").text("[Collapse All]");
3397}
3398
3399
3400/* HANDLE KEY EVENTS
3401 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3402 */
3403var agent = navigator['userAgent'].toLowerCase();
3404var mac = agent.indexOf("macintosh") != -1;
3405
3406$(document).keydown( function(e) {
3407var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3408 if (control && e.which == 70) { // 70 is "F"
3409 ensureAllInheritedExpanded();
3410 }
3411});
3412
3413
3414
3415
3416
3417
3418/* On-demand functions */
3419
3420/** Move sample code line numbers out of PRE block and into non-copyable column */
3421function initCodeLineNumbers() {
3422 var numbers = $("#codesample-block a.number");
3423 if (numbers.length) {
3424 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3425 }
3426
3427 $(document).ready(function() {
3428 // select entire line when clicked
3429 $("span.code-line").click(function() {
3430 if (!shifted) {
3431 selectText(this);
3432 }
3433 });
3434 // invoke line link on double click
3435 $(".code-line").dblclick(function() {
3436 document.location.hash = $(this).attr('id');
3437 });
3438 // highlight the line when hovering on the number
3439 $("#codesample-line-numbers a.number").mouseover(function() {
3440 var id = $(this).attr('href');
3441 $(id).css('background','#e7e7e7');
3442 });
3443 $("#codesample-line-numbers a.number").mouseout(function() {
3444 var id = $(this).attr('href');
3445 $(id).css('background','none');
3446 });
3447 });
3448}
3449
3450// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3451var shifted = false;
3452$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3453
3454// courtesy of jasonedelman.com
3455function selectText(element) {
3456 var doc = document
3457 , range, selection
3458 ;
3459 if (doc.body.createTextRange) { //ms
3460 range = doc.body.createTextRange();
3461 range.moveToElementText(element);
3462 range.select();
3463 } else if (window.getSelection) { //all others
3464 selection = window.getSelection();
3465 range = doc.createRange();
3466 range.selectNodeContents(element);
3467 selection.removeAllRanges();
3468 selection.addRange(range);
3469 }
3470}
3471
3472
3473
3474
3475/** Display links and other information about samples that match the
3476 group specified by the URL */
3477function showSamples() {
3478 var group = $("#samples").attr('class');
3479 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3480
3481 var $ul = $("<ul>");
3482 $selectedLi = $("#nav li.selected");
3483
3484 $selectedLi.children("ul").children("li").each(function() {
3485 var $li = $("<li>").append($(this).find("a").first().clone());
3486 $ul.append($li);
3487 });
3488
3489 $("#samples").append($ul);
3490
3491}
Dirk Dougherty08032402014-02-15 10:14:35 -08003492
3493
3494
3495/* ########################################################## */
3496/* ################### RESOURCE CARDS ##################### */
3497/* ########################################################## */
3498
3499/** Handle resource queries, collections, and grids (sections). Requires
3500 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3501
3502(function() {
3503 // Prevent the same resource from being loaded more than once per page.
3504 var addedPageResources = {};
3505
3506 $(document).ready(function() {
Dirk Dougherty032a4942015-05-04 18:17:33 -07003507 // Need to initialize hero carousel before other sections for dedupe
3508 // to work correctly.
3509 $('[data-carousel-query]').dacCarouselQuery();
3510
Dirk Dougherty08032402014-02-15 10:14:35 -08003511 $('.resource-widget').each(function() {
3512 initResourceWidget(this);
3513 });
3514
Dirk Dougherty318fb972014-04-08 18:46:53 -07003515 /* Pass the line height to ellipsisfade() to adjust the height of the
3516 text container to show the max number of lines possible, without
3517 showing lines that are cut off. This works with the css ellipsis
3518 classes to fade last text line and apply an ellipsis char. */
3519
Dirk Dougherty032a4942015-05-04 18:17:33 -07003520 //card text currently uses 20px line height.
3521 var lineHeight = 20;
Dirk Dougherty318fb972014-04-08 18:46:53 -07003522 $('.card-info .text').ellipsisfade(lineHeight);
Dirk Dougherty08032402014-02-15 10:14:35 -08003523 });
3524
3525 /*
3526 Three types of resource layouts:
3527 Flow - Uses a fixed row-height flow using float left style.
Dirk Dougherty318fb972014-04-08 18:46:53 -07003528 Carousel - Single card slideshow all same dimension absolute.
Dirk Dougherty08032402014-02-15 10:14:35 -08003529 Stack - Uses fixed columns and flexible element height.
3530 */
3531 function initResourceWidget(widget) {
3532 var $widget = $(widget);
3533 var isFlow = $widget.hasClass('resource-flow-layout'),
3534 isCarousel = $widget.hasClass('resource-carousel-layout'),
3535 isStack = $widget.hasClass('resource-stack-layout');
3536
Dirk Dougherty032a4942015-05-04 18:17:33 -07003537 // remove illegal col-x class which is not relevant anymore thanks to responsive styles.
Dirk Dougherty08032402014-02-15 10:14:35 -08003538 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
Dirk Dougherty032a4942015-05-04 18:17:33 -07003539 if (m && !$widget.is('.cols > *')) {
3540 $widget.removeClass('col-' + m[1]);
Dirk Dougherty08032402014-02-15 10:14:35 -08003541 }
3542
3543 var opts = {
3544 cardSizes: ($widget.data('cardsizes') || '').split(','),
3545 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3546 itemsPerPage: $widget.data('itemsperpage'),
3547 sortOrder: $widget.data('sortorder'),
3548 query: $widget.data('query'),
3549 section: $widget.data('section'),
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003550 /* Added by LFL 6/6/14 */
3551 resourceStyle: $widget.data('resourcestyle') || 'card',
3552 stackSort: $widget.data('stacksort') || 'true'
Dirk Dougherty08032402014-02-15 10:14:35 -08003553 };
3554
3555 // run the search for the set of resources to show
3556
3557 var resources = buildResourceList(opts);
3558
3559 if (isFlow) {
3560 drawResourcesFlowWidget($widget, opts, resources);
3561 } else if (isCarousel) {
3562 drawResourcesCarouselWidget($widget, opts, resources);
3563 } else if (isStack) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003564 /* Looks like this got removed and is not used, so repurposing for the
3565 homepage style layout.
3566 Modified by LFL 6/6/14
3567 */
3568 //var sections = buildSectionList(opts);
Dirk Dougherty08032402014-02-15 10:14:35 -08003569 opts['numStacks'] = $widget.data('numstacks');
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003570 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Dougherty08032402014-02-15 10:14:35 -08003571 }
3572 }
3573
3574 /* Initializes a Resource Carousel Widget */
3575 function drawResourcesCarouselWidget($widget, opts, resources) {
3576 $widget.empty();
Dirk Dougherty032a4942015-05-04 18:17:33 -07003577 var plusone = false; // stop showing plusone buttons on cards
Dirk Dougherty08032402014-02-15 10:14:35 -08003578
3579 $widget.addClass('resource-card slideshow-container')
3580 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3581 .append($('<a>').addClass('slideshow-next').text('Next'));
3582
3583 var css = { 'width': $widget.width() + 'px',
3584 'height': $widget.height() + 'px' };
3585
3586 var $ul = $('<ul>');
3587
3588 for (var i = 0; i < resources.length; ++i) {
Dirk Dougherty08032402014-02-15 10:14:35 -08003589 var $card = $('<a>')
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003590 .attr('href', cleanUrl(resources[i].url))
Dirk Dougherty318fb972014-04-08 18:46:53 -07003591 .decorateResourceCard(resources[i],plusone);
Dirk Dougherty08032402014-02-15 10:14:35 -08003592
3593 $('<li>').css(css)
3594 .append($card)
3595 .appendTo($ul);
3596 }
3597
3598 $('<div>').addClass('frame')
3599 .append($ul)
3600 .appendTo($widget);
3601
3602 $widget.dacSlideshow({
3603 auto: true,
3604 btnPrev: '.slideshow-prev',
3605 btnNext: '.slideshow-next'
3606 });
3607 };
3608
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003609 /* Initializes a Resource Card Stack Widget (column-based layout)
3610 Modified by LFL 6/6/14
3611 */
Dirk Dougherty08032402014-02-15 10:14:35 -08003612 function drawResourcesStackWidget($widget, opts, resources, sections) {
3613 // Don't empty widget, grab all items inside since they will be the first
3614 // items stacked, followed by the resource query
Dirk Dougherty032a4942015-05-04 18:17:33 -07003615 var plusone = false; // stop showing plusone buttons on cards
Dirk Dougherty08032402014-02-15 10:14:35 -08003616 var cards = $widget.find('.resource-card').detach().toArray();
3617 var numStacks = opts.numStacks || 1;
3618 var $stacks = [];
3619 var urlString;
3620
3621 for (var i = 0; i < numStacks; ++i) {
3622 $stacks[i] = $('<div>').addClass('resource-card-stack')
3623 .appendTo($widget);
3624 }
3625
3626 var sectionResources = [];
3627
3628 // Extract any subsections that are actually resource cards
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003629 if (sections) {
3630 for (var i = 0; i < sections.length; ++i) {
3631 if (!sections[i].sections || !sections[i].sections.length) {
3632 // Render it as a resource card
3633 sectionResources.push(
3634 $('<a>')
3635 .addClass('resource-card section-card')
3636 .attr('href', cleanUrl(sections[i].resource.url))
3637 .decorateResourceCard(sections[i].resource,plusone)[0]
3638 );
Dirk Dougherty08032402014-02-15 10:14:35 -08003639
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003640 } else {
3641 cards.push(
3642 $('<div>')
3643 .addClass('resource-card section-card-menu')
3644 .decorateResourceSection(sections[i],plusone)[0]
3645 );
3646 }
Dirk Dougherty08032402014-02-15 10:14:35 -08003647 }
3648 }
3649
3650 cards = cards.concat(sectionResources);
3651
3652 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003653 var $card = createResourceElement(resources[i], opts);
3654
3655 if (opts.resourceStyle.indexOf('related') > -1) {
3656 $card.addClass('related-card');
3657 }
Dirk Dougherty08032402014-02-15 10:14:35 -08003658
3659 cards.push($card[0]);
3660 }
3661
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003662 if (opts.stackSort != 'false') {
3663 for (var i = 0; i < cards.length; ++i) {
3664 // Find the stack with the shortest height, but give preference to
3665 // left to right order.
3666 var minHeight = $stacks[0].height();
3667 var minIndex = 0;
Dirk Dougherty08032402014-02-15 10:14:35 -08003668
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003669 for (var j = 1; j < numStacks; ++j) {
3670 var height = $stacks[j].height();
3671 if (height < minHeight - 45) {
3672 minHeight = height;
3673 minIndex = j;
3674 }
Dirk Dougherty08032402014-02-15 10:14:35 -08003675 }
Dirk Dougherty08032402014-02-15 10:14:35 -08003676
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003677 $stacks[minIndex].append($(cards[i]));
3678 }
Dirk Dougherty08032402014-02-15 10:14:35 -08003679 }
3680
3681 };
3682
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003683 /*
3684 Create a resource card using the given resource object and a list of html
3685 configured options. Returns a jquery object containing the element.
3686 */
3687 function createResourceElement(resource, opts, plusone) {
3688 var $el;
3689
3690 // The difference here is that generic cards are not entirely clickable
3691 // so its a div instead of an a tag, also the generic one is not given
3692 // the resource-card class so it appears with a transparent background
3693 // and can be styled in whatever way the css setup.
3694 if (opts.resourceStyle == 'generic') {
3695 $el = $('<div>')
3696 .addClass('resource')
3697 .attr('href', cleanUrl(resource.url))
3698 .decorateResource(resource, opts);
3699 } else {
3700 var cls = 'resource resource-card';
3701
3702 $el = $('<a>')
3703 .addClass(cls)
3704 .attr('href', cleanUrl(resource.url))
3705 .decorateResourceCard(resource, plusone);
3706 }
3707
3708 return $el;
3709 }
Dirk Dougherty032a4942015-05-04 18:17:33 -07003710
3711 function createResponsiveFlowColumn(cardSize) {
3712 var cardWidth = parseInt(cardSize.match(/(\d+)/)[1], 10);
3713 var column = $('<div>').addClass('col-' + (cardWidth / 3) + 'of6');
3714 if (cardWidth < 9) {
3715 column.addClass('col-tablet-1of2');
3716 } else if (cardWidth > 9 && cardWidth < 18) {
3717 column.addClass('col-tablet-1of1');
3718 }
3719 if (cardWidth < 18) {
3720 column.addClass('col-mobile-1of1')
3721 }
3722 return column;
3723 }
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003724
Dirk Dougherty08032402014-02-15 10:14:35 -08003725 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3726 function drawResourcesFlowWidget($widget, opts, resources) {
Dirk Dougherty032a4942015-05-04 18:17:33 -07003727 $widget.empty().addClass('cols');
Dirk Dougherty08032402014-02-15 10:14:35 -08003728 var cardSizes = opts.cardSizes || ['6x6'];
3729 var i = 0, j = 0;
Dirk Dougherty032a4942015-05-04 18:17:33 -07003730 var plusone = false; // stop showing plusone buttons on cards
Dirk Dougherty08032402014-02-15 10:14:35 -08003731
3732 while (i < resources.length) {
3733 var cardSize = cardSizes[j++ % cardSizes.length];
3734 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Dougherty032a4942015-05-04 18:17:33 -07003735
3736 var column = createResponsiveFlowColumn(cardSize).appendTo($widget);
Dirk Dougherty08032402014-02-15 10:14:35 -08003737
3738 // A stack has a third dimension which is the number of stacked items
3739 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3740 var stackCount = 0;
3741 var $stackDiv = null;
3742
3743 if (isStack) {
3744 // Create a stack container which should have the dimensions defined
3745 // by the product of the items inside.
3746 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
Dirk Dougherty032a4942015-05-04 18:17:33 -07003747 + 'x' + isStack[2] * isStack[3]) .appendTo(column);
Dirk Dougherty08032402014-02-15 10:14:35 -08003748 }
3749
3750 // Build each stack item or just a single item
3751 do {
3752 var resource = resources[i];
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003753
3754 var $card = createResourceElement(resources[i], opts, plusone);
3755
3756 $card.addClass('resource-card-' + cardSize +
3757 ' resource-card-' + resource.type);
Dirk Dougherty08032402014-02-15 10:14:35 -08003758
3759 if (isStack) {
3760 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3761 if (++stackCount == parseInt(isStack[3])) {
3762 $card.addClass('resource-card-row-stack-last');
3763 stackCount = 0;
3764 }
3765 } else {
3766 stackCount = 0;
3767 }
3768
Dirk Dougherty032a4942015-05-04 18:17:33 -07003769 $card.appendTo($stackDiv || column);
Dirk Dougherty08032402014-02-15 10:14:35 -08003770
3771 } while (++i < resources.length && stackCount > 0);
3772 }
3773 }
3774
3775 /* Build a site map of resources using a section as a root. */
3776 function buildSectionList(opts) {
3777 if (opts.section && SECTION_BY_ID[opts.section]) {
3778 return SECTION_BY_ID[opts.section].sections || [];
3779 }
3780 return [];
3781 }
3782
3783 function buildResourceList(opts) {
Dirk Dougherty032a4942015-05-04 18:17:33 -07003784 return $.queryResources(opts);
3785 }
3786
3787 $.queryResources = function(opts) {
Dirk Dougherty08032402014-02-15 10:14:35 -08003788 var maxResults = opts.maxResults || 100;
3789
3790 var query = opts.query || '';
3791 var expressions = parseResourceQuery(query);
3792 var addedResourceIndices = {};
3793 var results = [];
3794
3795 for (var i = 0; i < expressions.length; i++) {
3796 var clauses = expressions[i];
3797
3798 // build initial set of resources from first clause
3799 var firstClause = clauses[0];
3800 var resources = [];
3801 switch (firstClause.attr) {
3802 case 'type':
3803 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3804 break;
3805 case 'lang':
3806 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3807 break;
3808 case 'tag':
3809 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3810 break;
3811 case 'collection':
3812 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3813 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3814 break;
3815 case 'section':
3816 var urls = SITE_MAP[firstClause.value].sections || [];
3817 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3818 break;
3819 }
Scott Main20cf2a92014-04-02 21:57:20 -07003820 // console.log(firstClause.attr + ':' + firstClause.value);
Dirk Dougherty08032402014-02-15 10:14:35 -08003821 resources = resources || [];
3822
3823 // use additional clauses to filter corpus
3824 if (clauses.length > 1) {
3825 var otherClauses = clauses.slice(1);
3826 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3827 }
3828
3829 // filter out resources already added
3830 if (i > 1) {
3831 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3832 }
3833
3834 // add to list of already added indices
3835 for (var j = 0; j < resources.length; j++) {
Dirk Dougherty032a4942015-05-04 18:17:33 -07003836 if (resources[j]) {
3837 addedResourceIndices[resources[j].index] = 1;
3838 }
Dirk Dougherty08032402014-02-15 10:14:35 -08003839 }
3840
3841 // concat to final results list
3842 results = results.concat(resources);
3843 }
3844
3845 if (opts.sortOrder && results.length) {
3846 var attr = opts.sortOrder;
3847
3848 if (opts.sortOrder == 'random') {
3849 var i = results.length, j, temp;
3850 while (--i) {
3851 j = Math.floor(Math.random() * (i + 1));
3852 temp = results[i];
3853 results[i] = results[j];
3854 results[j] = temp;
3855 }
3856 } else {
3857 var desc = attr.charAt(0) == '-';
3858 if (desc) {
3859 attr = attr.substring(1);
3860 }
3861 results = results.sort(function(x,y) {
3862 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3863 });
3864 }
3865 }
3866
3867 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3868 results = results.slice(0, maxResults);
3869
3870 for (var j = 0; j < results.length; ++j) {
3871 addedPageResources[results[j].index] = 1;
3872 }
3873
3874 return results;
3875 }
3876
3877
3878 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3879 return function(resource) {
Dirk Dougherty032a4942015-05-04 18:17:33 -07003880 return resource && !addedResourceIndices[resource.index];
Dirk Dougherty08032402014-02-15 10:14:35 -08003881 };
3882 }
3883
3884
3885 function getResourceMatchesClausesFilter(clauses) {
3886 return function(resource) {
3887 return doesResourceMatchClauses(resource, clauses);
3888 };
3889 }
3890
3891
3892 function doesResourceMatchClauses(resource, clauses) {
3893 for (var i = 0; i < clauses.length; i++) {
3894 var map;
3895 switch (clauses[i].attr) {
3896 case 'type':
3897 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3898 break;
3899 case 'lang':
3900 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3901 break;
3902 case 'tag':
3903 map = IS_RESOURCE_TAGGED[clauses[i].value];
3904 break;
3905 }
3906
3907 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3908 return clauses[i].negative;
3909 }
3910 }
3911 return true;
3912 }
3913
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003914 function cleanUrl(url)
3915 {
3916 if (url && url.indexOf('//') === -1) {
3917 url = toRoot + url;
3918 }
3919
3920 return url;
3921 }
3922
Dirk Dougherty08032402014-02-15 10:14:35 -08003923
3924 function parseResourceQuery(query) {
3925 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3926 var expressions = [];
3927 var expressionStrs = query.split(',') || [];
3928 for (var i = 0; i < expressionStrs.length; i++) {
3929 var expr = expressionStrs[i] || '';
3930
3931 // Break expression into clauses (clause e.g. 'tag:foo')
3932 var clauses = [];
3933 var clauseStrs = expr.split(/(?=[\+\-])/);
3934 for (var j = 0; j < clauseStrs.length; j++) {
3935 var clauseStr = clauseStrs[j] || '';
3936
3937 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3938 var parts = clauseStr.split(':');
3939 var clause = {};
3940
3941 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3942 if (clause.attr) {
3943 if (clause.attr.charAt(0) == '+') {
3944 clause.attr = clause.attr.substring(1);
3945 } else if (clause.attr.charAt(0) == '-') {
3946 clause.negative = true;
3947 clause.attr = clause.attr.substring(1);
3948 }
3949 }
3950
3951 if (parts.length > 1) {
3952 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3953 }
3954
3955 clauses.push(clause);
3956 }
3957
3958 if (!clauses.length) {
3959 continue;
3960 }
3961
3962 expressions.push(clauses);
3963 }
3964
3965 return expressions;
3966 }
3967})();
3968
3969(function($) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07003970
3971 /*
3972 Utility method for creating dom for the description area of a card.
3973 Used in decorateResourceCard and decorateResource.
3974 */
3975 function buildResourceCardDescription(resource, plusone) {
3976 var $description = $('<div>').addClass('description ellipsis');
3977
3978 $description.append($('<div>').addClass('text').html(resource.summary));
3979
3980 if (resource.cta) {
3981 $description.append($('<a>').addClass('cta').html(resource.cta));
3982 }
3983
3984 if (plusone) {
3985 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
3986 "//developer.android.com/" + resource.url;
3987
3988 $description.append($('<div>').addClass('util')
3989 .append($('<div>').addClass('g-plusone')
3990 .attr('data-size', 'small')
3991 .attr('data-align', 'right')
3992 .attr('data-href', plusurl)));
3993 }
3994
3995 return $description;
3996 }
3997
3998
Dirk Dougherty08032402014-02-15 10:14:35 -08003999 /* Simple jquery function to create dom for a standard resource card */
Dirk Dougherty318fb972014-04-08 18:46:53 -07004000 $.fn.decorateResourceCard = function(resource,plusone) {
Dirk Dougherty08032402014-02-15 10:14:35 -08004001 var section = resource.group || resource.type;
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004002 var imgUrl = resource.image ||
4003 'assets/images/resource-card-default-android.jpg';
4004
4005 if (imgUrl.indexOf('//') === -1) {
4006 imgUrl = toRoot + imgUrl;
Dirk Dougherty08032402014-02-15 10:14:35 -08004007 }
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004008
4009 $('<div>').addClass('card-bg')
4010 .css('background-image', 'url(' + (imgUrl || toRoot +
4011 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Dougherty08032402014-02-15 10:14:35 -08004012 .appendTo(this);
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004013
4014 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
4015 .append($('<div>').addClass('section').text(section))
4016 .append($('<div>').addClass('title').html(resource.title))
4017 .append(buildResourceCardDescription(resource, plusone))
4018 .appendTo(this);
Dirk Dougherty08032402014-02-15 10:14:35 -08004019
4020 return this;
4021 };
4022
4023 /* Simple jquery function to create dom for a resource section card (menu) */
Dirk Dougherty318fb972014-04-08 18:46:53 -07004024 $.fn.decorateResourceSection = function(section,plusone) {
Dirk Dougherty08032402014-02-15 10:14:35 -08004025 var resource = section.resource;
4026 //keep url clean for matching and offline mode handling
4027 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
4028 var $base = $('<a>')
4029 .addClass('card-bg')
4030 .attr('href', resource.url)
4031 .append($('<div>').addClass('card-section-icon')
4032 .append($('<div>').addClass('icon'))
4033 .append($('<div>').addClass('section').html(resource.title)))
4034 .appendTo(this);
4035
4036 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
4037
4038 if (section.sections && section.sections.length) {
4039 // Recurse the section sub-tree to find a resource image.
4040 var stack = [section];
4041
4042 while (stack.length) {
4043 if (stack[0].resource.image) {
4044 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4045 break;
4046 }
4047
4048 if (stack[0].sections) {
4049 stack = stack.concat(stack[0].sections);
4050 }
4051
4052 stack.shift();
4053 }
4054
4055 var $ul = $('<ul>')
4056 .appendTo($cardInfo);
4057
4058 var max = section.sections.length > 3 ? 3 : section.sections.length;
4059
4060 for (var i = 0; i < max; ++i) {
4061
4062 var subResource = section.sections[i];
Dirk Dougherty318fb972014-04-08 18:46:53 -07004063 if (!plusone) {
4064 $('<li>')
4065 .append($('<a>').attr('href', subResource.url)
4066 .append($('<div>').addClass('title').html(subResource.title))
4067 .append($('<div>').addClass('description ellipsis')
4068 .append($('<div>').addClass('text').html(subResource.summary))
4069 .append($('<div>').addClass('util'))))
4070 .appendTo($ul);
4071 } else {
4072 $('<li>')
4073 .append($('<a>').attr('href', subResource.url)
4074 .append($('<div>').addClass('title').html(subResource.title))
4075 .append($('<div>').addClass('description ellipsis')
4076 .append($('<div>').addClass('text').html(subResource.summary))
4077 .append($('<div>').addClass('util')
4078 .append($('<div>').addClass('g-plusone')
4079 .attr('data-size', 'small')
4080 .attr('data-align', 'right')
4081 .attr('data-href', resource.url)))))
4082 .appendTo($ul);
4083 }
Dirk Dougherty08032402014-02-15 10:14:35 -08004084 }
4085
4086 // Add a more row
4087 if (max < section.sections.length) {
4088 $('<li>')
4089 .append($('<a>').attr('href', resource.url)
4090 .append($('<div>')
4091 .addClass('title')
4092 .text('More')))
4093 .appendTo($ul);
4094 }
4095 } else {
4096 // No sub-resources, just render description?
4097 }
4098
4099 return this;
4100 };
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004101
4102
4103
4104
4105 /* Render other types of resource styles that are not cards. */
4106 $.fn.decorateResource = function(resource, opts) {
4107 var imgUrl = resource.image ||
4108 'assets/images/resource-card-default-android.jpg';
4109 var linkUrl = resource.url;
4110
4111 if (imgUrl.indexOf('//') === -1) {
4112 imgUrl = toRoot + imgUrl;
4113 }
4114
4115 if (linkUrl && linkUrl.indexOf('//') === -1) {
4116 linkUrl = toRoot + linkUrl;
4117 }
4118
4119 $(this).append(
4120 $('<div>').addClass('image')
4121 .css('background-image', 'url(' + imgUrl + ')'),
4122 $('<div>').addClass('info').append(
4123 $('<h4>').addClass('title').html(resource.title),
4124 $('<p>').addClass('summary').html(resource.summary),
4125 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4126 )
4127 );
4128
4129 return this;
4130 };
Dirk Dougherty08032402014-02-15 10:14:35 -08004131})(jQuery);
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004132
4133
Dirk Dougherty318fb972014-04-08 18:46:53 -07004134/* Calculate the vertical area remaining */
Dirk Dougherty08032402014-02-15 10:14:35 -08004135(function($) {
Dirk Dougherty318fb972014-04-08 18:46:53 -07004136 $.fn.ellipsisfade= function(lineHeight) {
Dirk Dougherty08032402014-02-15 10:14:35 -08004137 this.each(function() {
4138 // get element text
4139 var $this = $(this);
Dirk Dougherty318fb972014-04-08 18:46:53 -07004140 var remainingHeight = $this.parent().parent().height();
4141 $this.parent().siblings().each(function ()
4142 {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004143 if ($(this).is(":visible")) {
Dirk Dougherty032a4942015-05-04 18:17:33 -07004144 var h = $(this).outerHeight(true);
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004145 remainingHeight = remainingHeight - h;
4146 }
Dirk Dougherty318fb972014-04-08 18:46:53 -07004147 });
Dirk Dougherty08032402014-02-15 10:14:35 -08004148
Dirk Dougherty318fb972014-04-08 18:46:53 -07004149 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4150 $this.parent().css({'height': adjustedRemainingHeight});
4151 $this.css({'height': "auto"});
Dirk Dougherty08032402014-02-15 10:14:35 -08004152 });
4153
4154 return this;
4155 };
Scott Main20cf2a92014-04-02 21:57:20 -07004156}) (jQuery);
Dirk Doughertyff233cc2015-05-04 14:37:05 -07004157
4158/*
4159 Fullscreen Carousel
4160
4161 The following allows for an area at the top of the page that takes over the
4162 entire browser height except for its top offset and an optional bottom
4163 padding specified as a data attribute.
4164
4165 HTML:
4166
4167 <div class="fullscreen-carousel">
4168 <div class="fullscreen-carousel-content">
4169 <!-- content here -->
4170 </div>
4171 <div class="fullscreen-carousel-content">
4172 <!-- content here -->
4173 </div>
4174
4175 etc ...
4176
4177 </div>
4178
4179 Control over how the carousel takes over the screen can mostly be defined in
4180 a css file. Setting min-height on the .fullscreen-carousel-content elements
4181 will prevent them from shrinking to far vertically when the browser is very
4182 short, and setting max-height on the .fullscreen-carousel itself will prevent
4183 the area from becoming to long in the case that the browser is stretched very
4184 tall.
4185
4186 There is limited functionality for having multiple sections since that request
4187 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4188 scroll between multiple content areas.
4189*/
4190
4191(function() {
4192 $(document).ready(function() {
4193 $('.fullscreen-carousel').each(function() {
4194 initWidget(this);
4195 });
4196 });
4197
4198 function initWidget(widget) {
4199 var $widget = $(widget);
4200
4201 var topOffset = $widget.offset().top;
4202 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4203 var maxHeight = 0;
4204 var minHeight = 0;
4205 var $content = $widget.find('.fullscreen-carousel-content');
4206 var $nextArrow = $widget.find('.next-arrow');
4207 var $prevArrow = $widget.find('.prev-arrow');
4208 var $curSection = $($content[0]);
4209
4210 if ($content.length <= 1) {
4211 $nextArrow.hide();
4212 $prevArrow.hide();
4213 } else {
4214 $nextArrow.click(function() {
4215 var index = ($content.index($curSection) + 1);
4216 $curSection.hide();
4217 $curSection = $($content[index >= $content.length ? 0 : index]);
4218 $curSection.show();
4219 });
4220
4221 $prevArrow.click(function() {
4222 var index = ($content.index($curSection) - 1);
4223 $curSection.hide();
4224 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4225 $curSection.show();
4226 });
4227 }
4228
4229 // Just hide all content sections except first.
4230 $content.each(function(index) {
4231 if ($(this).height() > minHeight) minHeight = $(this).height();
4232 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4233 });
4234
4235 // Register for changes to window size, and trigger.
4236 $(window).resize(resizeWidget);
4237 resizeWidget();
4238
4239 function resizeWidget() {
4240 var height = $(window).height() - topOffset - padBottom;
4241 $widget.width($(window).width());
4242 $widget.height(height < minHeight ? minHeight :
4243 (maxHeight && height > maxHeight ? maxHeight : height));
4244 }
4245 }
4246})();
4247
4248
4249
4250
4251
4252/*
4253 Tab Carousel
4254
4255 The following allows tab widgets to be installed via the html below. Each
4256 tab content section should have a data-tab attribute matching one of the
4257 nav items'. Also each tab content section should have a width matching the
4258 tab carousel.
4259
4260 HTML:
4261
4262 <div class="tab-carousel">
4263 <ul class="tab-nav">
4264 <li><a href="#" data-tab="handsets">Handsets</a>
4265 <li><a href="#" data-tab="wearable">Wearable</a>
4266 <li><a href="#" data-tab="tv">TV</a>
4267 </ul>
4268
4269 <div class="tab-carousel-content">
4270 <div data-tab="handsets">
4271 <!--Full width content here-->
4272 </div>
4273
4274 <div data-tab="wearable">
4275 <!--Full width content here-->
4276 </div>
4277
4278 <div data-tab="tv">
4279 <!--Full width content here-->
4280 </div>
4281 </div>
4282 </div>
4283
4284*/
4285(function() {
4286 $(document).ready(function() {
4287 $('.tab-carousel').each(function() {
4288 initWidget(this);
4289 });
4290 });
4291
4292 function initWidget(widget) {
4293 var $widget = $(widget);
4294 var $nav = $widget.find('.tab-nav');
4295 var $anchors = $nav.find('[data-tab]');
4296 var $li = $nav.find('li');
4297 var $contentContainer = $widget.find('.tab-carousel-content');
4298 var $tabs = $contentContainer.find('[data-tab]');
4299 var $curTab = $($tabs[0]); // Current tab is first tab.
4300 var width = $widget.width();
4301
4302 // Setup nav interactivity.
4303 $anchors.click(function(evt) {
4304 evt.preventDefault();
4305 var query = '[data-tab=' + $(this).data('tab') + ']';
4306 transitionWidget($tabs.filter(query));
4307 });
4308
4309 // Add highlight for navigation on first item.
4310 var $highlight = $('<div>').addClass('highlight')
4311 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4312 .appendTo($nav);
4313
4314 // Store height since we will change contents to absolute.
4315 $contentContainer.height($contentContainer.height());
4316
4317 // Absolutely position tabs so they're ready for transition.
4318 $tabs.each(function(index) {
4319 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4320 });
4321
4322 function transitionWidget($toTab) {
4323 if (!$curTab.is($toTab)) {
4324 var curIndex = $tabs.index($curTab[0]);
4325 var toIndex = $tabs.index($toTab[0]);
4326 var dir = toIndex > curIndex ? 1 : -1;
4327
4328 // Animate content sections.
4329 $toTab.css({left:(width * dir) + 'px'});
4330 $curTab.animate({left:(width * -dir) + 'px'});
4331 $toTab.animate({left:'0'});
4332
4333 // Animate navigation highlight.
4334 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
4335 width:$($li[toIndex]).outerWidth() + 'px'})
4336
4337 // Store new current section.
4338 $curTab = $toTab;
4339 }
4340 }
4341 }
4342})();
Dirk Dougherty032a4942015-05-04 18:17:33 -07004343
4344(function($) {
4345 'use strict';
4346
4347 /**
4348 * Toggle Floating Label state.
4349 * @param {HTMLElement} el - The DOM element.
4350 * @param options
4351 * @constructor
4352 */
4353 function FloatingLabel(el, options) {
4354 this.el = $(el);
4355 this.options = $.extend({}, FloatingLabel.DEFAULTS_, options);
4356 this.group = this.el.closest('.dac-form-input-group');
4357 this.input = this.group.find('.dac-form-input');
4358
4359 this.checkValue_ = this.checkValue_.bind(this);
4360 this.checkValue_();
4361
4362 this.input.on('focus', function() {
4363 this.group.addClass('dac-focused');
4364 }.bind(this));
4365 this.input.on('blur', function() {
4366 this.group.removeClass('dac-focused');
4367 this.checkValue_();
4368 }.bind(this));
4369 this.input.on('keyup', this.checkValue_);
4370 }
4371
4372 /**
4373 * The label is moved out of the textbox when it has a value.
4374 */
4375 FloatingLabel.prototype.checkValue_ = function() {
4376 if (this.input.val().length) {
4377 this.group.addClass('dac-has-value');
4378 } else {
4379 this.group.removeClass('dac-has-value');
4380 }
4381 };
4382
4383 /**
4384 * jQuery plugin
4385 * @param {object} options - Override default options.
4386 */
4387 $.fn.dacFloatingLabel = function(options) {
4388 return this.each(function() {
4389 new FloatingLabel(this, options);
4390 });
4391 };
4392
4393 $(document).on('ready.aranja', function() {
4394 $('.dac-form-floatlabel').each(function() {
4395 $(this).dacFloatingLabel($(this).data());
4396 });
4397 });
4398})(jQuery);
4399
4400/* global toRoot, CAROUSEL_OVERRIDE */
4401(function($) {
4402 // Ordering matters
4403 var TAG_MAP = [
4404 {from: 'developerstory', to: 'Android Developer Story'},
4405 {from: 'googleplay', to: 'Google Play'}
4406 ];
4407
4408 function DacCarouselQuery(el) {
4409 this.el = $(el);
4410
4411 var opts = this.el.data();
4412 opts.maxResults = parseInt(opts.maxResults || '100', 10);
4413 opts.query = opts.carouselQuery;
4414 var resources = $.queryResources(opts);
4415
4416 this.el.empty();
4417 $(resources).map(function() {
4418 var resource = $.extend({}, this, CAROUSEL_OVERRIDE[this.url]);
4419 var slide = $('<article class="dac-expand dac-hero">');
4420 var image = cleanUrl(resource.heroImage || resource.image);
4421 var fullBleed = image && !resource.heroColor;
4422
4423 // Configure background
4424 slide.css({
4425 backgroundImage: fullBleed ? 'url(' + image + ')' : '',
4426 backgroundColor: resource.heroColor || ''
4427 });
4428
4429 // Should copy be inverted
4430 slide.toggleClass('dac-invert', resource.heroInvert || fullBleed);
4431 slide.toggleClass('dac-darken', fullBleed);
4432
4433 var cols = $('<div class="cols dac-hero-content">');
4434
4435 // inline image column
4436 var rightCol = $('<div class="col-1of2 col-push-1of2 dac-hero-figure">')
4437 .appendTo(cols);
4438
4439 if (!fullBleed && image) {
4440 rightCol.append($('<img>').attr('src', image));
4441 }
4442
4443 // info column
4444 $('<div class="col-1of2 col-pull-1of2">')
4445 .append($('<div class="dac-hero-tag">').text(formatTag(resource)))
4446 .append($('<h1 class="dac-hero-title">').text(formatTitle(resource)))
4447 .append($('<p class="dac-hero-description">').text(resource.summary))
4448 .append($('<a class="dac-hero-cta">')
4449 .text(formatCTA(resource))
4450 .attr('href', cleanUrl(resource.url))
4451 .prepend($('<span class="dac-sprite dac-auto-chevron">'))
4452 )
4453 .appendTo(cols);
4454
4455 slide.append(cols.wrap('<div class="wrap">').parent());
4456 return slide[0];
4457 }).prependTo(this.el);
4458
4459 // Pagination element.
4460 this.el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>');
4461
4462 this.el.dacCarousel();
4463 }
4464
4465 function cleanUrl(url) {
4466 if (url && url.indexOf('//') === -1) {
4467 url = toRoot + url;
4468 }
4469 return url;
4470 }
4471
4472 function formatTag(resource) {
4473 // Hmm, need a better more scalable solution for this.
4474 for (var i = 0, mapping; mapping = TAG_MAP[i]; i++) {
4475 if (resource.tags.indexOf(mapping.from) > -1) {
4476 return mapping.to;
4477 }
4478 }
4479 return resource.type;
4480 }
4481
4482 function formatTitle(resource) {
4483 return resource.title.replace(/android developer story: /i, '');
4484 }
4485
4486 function formatCTA(resource) {
4487 return resource.type === 'youtube' ? 'Watch the video' : 'Learn more';
4488 }
4489
4490 // jQuery plugin
4491 $.fn.dacCarouselQuery = function() {
4492 return this.each(function() {
4493 var el = $(this);
4494 var data = el.data('dac.carouselQuery');
4495
4496 if (!data) { el.data('dac.carouselQuery', (data = new DacCarouselQuery(el))); }
4497 });
4498 };
4499
4500 // Data API
4501 $(function() {
4502 $('[data-carousel-query]').dacCarouselQuery();
4503 });
4504})(jQuery);
4505
4506(function($) {
4507 /**
4508 * A CSS based carousel, inspired by SequenceJS.
4509 * @param {jQuery} el
4510 * @param {object} options
4511 * @constructor
4512 */
4513 function DacCarousel(el, options) {
4514 this.el = $(el);
4515 this.options = options = $.extend({}, DacCarousel.OPTIONS, this.el.data(), options || {});
4516 this.frames = this.el.find(options.frameSelector);
4517 this.count = this.frames.size();
4518 this.current = options.start;
4519
4520 this.initPagination();
4521 this.initEvents();
4522 this.initFrame();
4523 }
4524
4525 DacCarousel.OPTIONS = {
4526 auto: true,
4527 autoTime: 10000,
4528 autoMinTime: 5000,
4529 btnPrev: '[data-carousel-prev]',
4530 btnNext: '[data-carousel-next]',
4531 frameSelector: 'article',
4532 loop: true,
4533 start: 0,
4534 pagination: '[data-carousel-pagination]'
4535 };
4536
4537 DacCarousel.prototype.initPagination = function() {
4538 this.pagination = $([]);
4539 if (!this.options.pagination) { return; }
4540
4541 var pagination = $('<ul class="dac-pagination">');
4542 var parent = this.el;
4543 if (typeof this.options.pagination === 'string') { parent = this.el.find(this.options.pagination); }
4544
4545 if (this.count > 1) {
4546 for (var i = 0; i < this.count; i++) {
4547 var li = $('<li class="dac-pagination-item">').text(i);
4548 if (i === this.options.start) { li.addClass('active'); }
4549 li.click(this.go.bind(this, i));
4550
4551 pagination.append(li);
4552 }
4553 this.pagination = pagination.children();
4554 parent.append(pagination);
4555 }
4556 };
4557
4558 DacCarousel.prototype.initEvents = function() {
4559 var that = this;
4560
4561 this.el.hover(function() {
4562 that.pauseRotateTimer();
4563 }, function() {
4564 that.startRotateTimer();
4565 });
4566
4567 $(this.options.btnPrev).click(function(e) {
4568 e.preventDefault();
4569 that.prev();
4570 });
4571
4572 $(this.options.btnNext).click(function(e) {
4573 e.preventDefault();
4574 that.next();
4575 });
4576 };
4577
4578 DacCarousel.prototype.initFrame = function() {
4579 this.frames.removeClass('active').eq(this.options.start).addClass('active');
4580 };
4581
4582 DacCarousel.prototype.startRotateTimer = function() {
4583 if (!this.options.auto || this.rotateTimer) { return; }
4584 this.rotateTimer = setTimeout(this.next.bind(this), this.options.autoTime);
4585 };
4586
4587 DacCarousel.prototype.pauseRotateTimer = function() {
4588 clearTimeout(this.rotateTimer);
4589 this.rotateTimer = null;
4590 };
4591
4592 DacCarousel.prototype.prev = function() {
4593 this.go(this.current - 1);
4594 };
4595
4596 DacCarousel.prototype.next = function() {
4597 this.go(this.current + 1);
4598 };
4599
4600 DacCarousel.prototype.go = function(next) {
4601 // Figure out what the next slide is.
4602 while (this.count > 0 && next >= this.count) { next -= this.count; }
4603 while (next < 0) { next += this.count; }
4604
4605 // Cancel if we're already on that slide.
4606 if (next === this.current) { return; }
4607
4608 // Prepare next slide.
4609 this.frames.eq(next).removeClass('out');
4610
4611 // Recalculate styles before starting slide transition.
4612 var that = this;
4613 resolveStyles(this.el[0], function() {
4614 // Update pagination
4615 that.pagination.removeClass('active').eq(next).addClass('active');
4616
4617 // Transition out current frame
4618 that.frames.eq(that.current).toggleClass('active out');
4619
4620 // Transition in a new frame
4621 that.frames.eq(next).toggleClass('active');
4622
4623 that.current = next;
4624 });
4625 };
4626
4627 // Helper
4628 function resolveStyles(el, callback) {
4629 /*jshint expr:true*/
4630 el.offsetTop;
4631 callback();
4632 }
4633
4634 // jQuery plugin
4635 $.fn.dacCarousel = function() {
4636 this.each(function() {
4637 var $el = $(this);
4638 $el.data('dac-carousel', new DacCarousel(this));
4639 });
4640 return this;
4641 };
4642
4643 // Data API
4644 $(function() {
4645 $('[data-carousel]').dacCarousel();
4646 });
4647})(jQuery);
4648
4649(function($) {
4650 'use strict';
4651
4652 /**
4653 * Toggle the visabilty of the mobile navigation.
4654 * @param {HTMLElement} el - The DOM element.
4655 * @param options
4656 * @constructor
4657 */
4658 function ToggleModal(el, options) {
4659 this.el = $(el);
4660 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
4661 this.el.on('click', this.clickHandler_.bind(this));
4662 }
4663
4664 ToggleModal.DEFAULTS_ = {
4665 toggleClass: 'dac-modal-open'
4666 };
4667
4668 /**
4669 * The actual toggle logic.
4670 * @param event
4671 * @private
4672 */
4673 ToggleModal.prototype.clickHandler_ = function(event) {
4674 event.preventDefault();
4675 //TODO: Toggle a class on the modal itself
4676 $('body').toggleClass(this.options.toggleClass);
4677 $('.dac-modal-dimmer').toggleClass('dac-active');
4678 $('.dac-modal-window').toggleClass('dac-active');
4679 };
4680
4681 /**
4682 * jQuery plugin
4683 * @param {object} options - Override default options.
4684 */
4685 $.fn.dacToggleModal = function(options) {
4686 return this.each(function() {
4687 new ToggleModal(this, options);
4688 });
4689 };
4690
4691 /**
4692 * Data Attribute API
4693 */
4694 $(document).on('ready.aranja', function() {
4695 $('[data-modal-toogle]').each(function() {
4696 $(this).dacToggleModal($(this).data());
4697 });
4698 });
4699})(jQuery);
4700
4701(function($) {
4702 'use strict';
4703
4704 /**
4705 * Toggle the visabilty of the mobile navigation.
4706 * @param {HTMLElement} el - The DOM element.
4707 * @param options
4708 * @constructor
4709 */
4710 function ToggleNav(el, options) {
4711 this.el = $(el);
4712 this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
4713 this.options.target = [this.options.navigation];
4714
4715 if (this.options.body) {this.options.target.push('body')}
4716 if (this.options.dimmer) {this.options.target.push(this.options.dimmer)}
4717
4718 this.el.on('click', this.clickHandler_.bind(this));
4719 }
4720
4721 /**
4722 * ToggleNav Default Settings
4723 * @type {{body: boolean, dimmer: string, navigation: string, toggleClass: string}}
4724 * @private
4725 */
4726 ToggleNav.DEFAULTS_ = {
4727 body: true,
4728 dimmer: '.dac-nav-dimmer',
4729 navigation: '[data-dac-nav]',
4730 toggleClass: 'dac-nav-open'
4731 };
4732
4733 /**
4734 * The actual toggle logic.
4735 * @param event
4736 * @private
4737 */
4738 ToggleNav.prototype.clickHandler_ = function(event) {
4739 event.preventDefault();
4740 $(this.options.target.join(', ')).toggleClass(this.options.toggleClass);
4741 };
4742
4743 /**
4744 * jQuery plugin
4745 * @param {object} options - Override default options.
4746 */
4747 $.fn.dacToggleMobileNav = function(options) {
4748 return this.each(function() {
4749 new ToggleNav(this, options);
4750 });
4751 };
4752
4753 /**
4754 * Data Attribute API
4755 */
4756 $(window).on('load.aranja', function() {
4757 $('[data-dac-toggle-nav]').each(function() {
4758 $(this).dacToggleMobileNav($(this).data());
4759 });
4760 });
4761})(jQuery);
4762
4763(function($) {
4764 'use strict';
4765
4766 /**
4767 * Submit the newsletter form to a Google Form.
4768 * @param {HTMLElement} el - The Form DOM element.
4769 * @constructor
4770 */
4771 function NewsletterForm(el) {
4772 this.el = $(el);
4773 this.url = this.el.attr('action');
4774 this.el.on('submit', this.submitHandler_.bind(this));
4775 }
4776
4777 /**
4778 * Close the modal when the form is sent.
4779 * @private
4780 */
4781 NewsletterForm.prototype.submitHandler_ = function() {
4782 //TODO: Close the modal with an event and let modal.js handle this
4783 $('body').removeClass('dac-modal-open');
4784 $('.dac-modal-dimmer').removeClass('dac-active');
4785 $('.dac-modal-window').removeClass('dac-active');
4786 };
4787
4788 /**
4789 * jQuery plugin
4790 * @param {object} options - Override default options.
4791 */
4792 $.fn.dacNewsletterForm = function(options) {
4793 return this.each(function() {
4794 new NewsletterForm(this, options);
4795 });
4796 };
4797
4798 /**
4799 * Data Attribute API
4800 */
4801 $(document).on('ready.aranja', function() {
4802 $('[data-newsletter-form]').each(function() {
4803 $(this).dacNewsletterForm();
4804 });
4805 });
4806})(jQuery);