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