blob: 5ed947c7cd812834003ecd07acd2bb2d31aa297b [file] [log] [blame]
Dirk Dougherty541b4942014-02-14 18:31:53 -08001var cookie_namespace = 'android_developer';
Dirk Dougherty541b4942014-02-14 18:31:53 -08002var isMobile = false; // true if mobile, so we can adjust some layout
3var mPagePath; // initialized in ready() function
4
5var basePath = getBaseUri(location.pathname);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00006var SITE_ROOT = toRoot + basePath.substring(1, basePath.indexOf("/", 1));
Dirk Dougherty541b4942014-02-14 18:31:53 -08007
smain@google.comeeeb9b82016-08-29 18:12:27 -07008// TODO(akassay) generate this var in the reference doc build.
9var API_LEVELS = ['1', '2', '3', '4', '5', '6', '7', '8', '9',
10 '10', '11', '12', '13', '14', '15', '16',
11 '17', '18', '19', '20', '21', '22', '23', '24'];
12var METADATA = METADATA || {};
13var RESERVED_METADATA_CATEGORY_NAMES = ['extras', 'carousel', 'collections',
14 'searchHeroCollections'];
15
Dirk Dougherty541b4942014-02-14 18:31:53 -080016// Ensure that all ajax getScript() requests allow caching
17$.ajaxSetup({
18 cache: true
19});
20
21/****** ON LOAD SET UP STUFF *********/
22
Dirk Dougherty541b4942014-02-14 18:31:53 -080023$(document).ready(function() {
24
Dirk Dougherty541b4942014-02-14 18:31:53 -080025 // prep nav expandos
smain@google.comeeeb9b82016-08-29 18:12:27 -070026 var pagePath = location.href.replace(location.hash, '');
Dirk Dougherty541b4942014-02-14 18:31:53 -080027 // account for intl docs by removing the intl/*/ path
28 if (pagePath.indexOf("/intl/") == 0) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +000029 pagePath = pagePath.substr(pagePath.indexOf("/", 6)); // start after intl/ to get last /
Dirk Dougherty541b4942014-02-14 18:31:53 -080030 }
31
32 if (pagePath.indexOf(SITE_ROOT) == 0) {
33 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
34 pagePath += 'index.html';
35 }
36 }
37
38 // Need a copy of the pagePath before it gets changed in the next block;
39 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
40 var pagePathOriginal = pagePath;
41 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
42 // If running locally, SITE_ROOT will be a relative path, so account for that by
43 // finding the relative URL to this page. This will allow us to find links on the page
44 // leading back to this page.
45 var pathParts = pagePath.split('/');
46 var relativePagePathParts = [];
47 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
48 for (var i = 0; i < upDirs; i++) {
49 relativePagePathParts.push('..');
50 }
51 for (var i = 0; i < upDirs; i++) {
52 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
53 }
54 relativePagePathParts.push(pathParts[pathParts.length - 1]);
55 pagePath = relativePagePathParts.join('/');
56 } else {
57 // Otherwise the page path is already an absolute URL
58 }
59
Dirk Dougherty541b4942014-02-14 18:31:53 -080060 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
61 // and highlight the sidenav
62 mPagePath = pagePath;
smain@google.comeeeb9b82016-08-29 18:12:27 -070063
64 // Check for params and remove them.
65 mPagePath = mPagePath.split('?')[0];
Dirk Dougherty541b4942014-02-14 18:31:53 -080066 highlightSidenav();
67
68 // set up prev/next links if they exist
69 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
70 var $selListItem;
71 if ($selNavLink.length) {
72 $selListItem = $selNavLink.closest('li');
73
74 // set up prev links
75 var $prevLink = [];
76 var $prevListItem = $selListItem.prev('li');
77
78 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
79false; // navigate across topic boundaries only in design docs
80 if ($prevListItem.length) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -070081 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Dirk Dougherty541b4942014-02-14 18:31:53 -080082 // jump to last topic of previous section
83 $prevLink = $prevListItem.find('a:last');
84 } else if (!$selListItem.hasClass('nav-section')) {
85 // jump to previous topic in this section
86 $prevLink = $prevListItem.find('a:eq(0)');
87 }
88 } else {
89 // jump to this section's index page (if it exists)
90 var $parentListItem = $selListItem.parents('li');
91 $prevLink = $selListItem.parents('li').find('a');
92
93 // except if cross boundaries aren't allowed, and we're at the top of a section already
94 // (and there's another parent)
Amanda Kassay8bac6eb2016-05-26 17:58:23 +000095 if (!crossBoundaries && $parentListItem.hasClass('nav-section') &&
96 $selListItem.hasClass('nav-section')) {
Dirk Dougherty541b4942014-02-14 18:31:53 -080097 $prevLink = [];
98 }
99 }
100
101 // set up next links
102 var $nextLink = [];
103 var startClass = false;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800104 var isCrossingBoundary = false;
105
106 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
107 // we're on an index page, jump to the first topic
108 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
109
110 // if there aren't any children, go to the next section (required for About pages)
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000111 if ($nextLink.length == 0) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800112 $nextLink = $selListItem.next('li').find('a');
113 } else if ($('.topic-start-link').length) {
114 // as long as there's a child link and there is a "topic start link" (we're on a landing)
115 // then set the landing page "start link" text to be the first doc title
116 $('.topic-start-link').text($nextLink.text().toUpperCase());
117 }
118
119 // If the selected page has a description, then it's a class or article homepage
120 if ($selListItem.find('a[description]').length) {
121 // this means we're on a class landing page
122 startClass = true;
123 }
124 } else {
125 // jump to the next topic in this section (if it exists)
126 $nextLink = $selListItem.next('li').find('a:eq(0)');
127 if ($nextLink.length == 0) {
128 isCrossingBoundary = true;
129 // no more topics in this section, jump to the first topic in the next section
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700130 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800131 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
132 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
133 if ($nextLink.length == 0) {
134 // if that doesn't work, we're at the end of the list, so disable NEXT link
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000135 $('.next-page-link').attr('href', '').addClass("disabled")
Dirk Dougherty541b4942014-02-14 18:31:53 -0800136 .click(function() { return false; });
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700137 // and completely hide the one in the footer
138 $('.content-footer .next-page-link').hide();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800139 }
140 }
141 }
142 }
143
144 if (startClass) {
145 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
146
147 // if there's no training bar (below the start button),
148 // then we need to add a bottom border to button
149 if (!$("#tb").length) {
150 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
151 }
152 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
153 $('.content-footer.next-class').show();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000154 $('.next-page-link').attr('href', '')
Dirk Dougherty541b4942014-02-14 18:31:53 -0800155 .removeClass("hide").addClass("disabled")
156 .click(function() { return false; });
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700157 // and completely hide the one in the footer
158 $('.content-footer .next-page-link').hide();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000159 $('.content-footer .prev-page-link').hide();
160
Dirk Dougherty541b4942014-02-14 18:31:53 -0800161 if ($nextLink.length) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000162 $('.next-class-link').attr('href', $nextLink.attr('href'))
163 .removeClass("hide");
164
165 $('.content-footer .next-class-link').append($nextLink.html());
166
Dirk Dougherty541b4942014-02-14 18:31:53 -0800167 $('.next-class-link').find('.new').empty();
168 }
169 } else {
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700170 $('.next-page-link').attr('href', $nextLink.attr('href'))
171 .removeClass("hide");
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000172 // for the footer link, also add the previous and next page titles
smain@google.comeeeb9b82016-08-29 18:12:27 -0700173 if ($prevLink.length) {
174 $('.content-footer .prev-page-link').append($prevLink.html());
175 }
176 if ($nextLink.length) {
177 $('.content-footer .next-page-link').append($nextLink.html());
178 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800179 }
180
181 if (!startClass && $prevLink.length) {
182 var prevHref = $prevLink.attr('href');
183 if (prevHref == SITE_ROOT + 'index.html') {
184 // Don't show Previous when it leads to the homepage
185 } else {
186 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
187 }
188 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800189 }
190
Dirk Dougherty541b4942014-02-14 18:31:53 -0800191 // Set up the course landing pages for Training with class names and descriptions
192 if ($('body.trainingcourse').length) {
193 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700194
195 // create an array for all the class descriptions
196 var $classDescriptions = new Array($classLinks.length);
197 var lang = getLangPref();
198 $classLinks.each(function(index) {
199 var langDescr = $(this).attr(lang + "-description");
200 if (typeof langDescr !== 'undefined' && langDescr !== false) {
201 // if there's a class description in the selected language, use that
202 $classDescriptions[index] = langDescr;
203 } else {
204 // otherwise, use the default english description
205 $classDescriptions[index] = $(this).attr("description");
206 }
207 });
Dirk Dougherty541b4942014-02-14 18:31:53 -0800208
209 var $olClasses = $('<ol class="class-list"></ol>');
210 var $liClass;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800211 var $h2Title;
212 var $pSummary;
213 var $olLessons;
214 var $liLesson;
215 $classLinks.each(function(index) {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -0800216 $liClass = $('<li class="clearfix"></li>');
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000217 $h2Title = $('<a class="title" href="' + $(this).attr('href') + '"><h2 class="norule">' + $(this).html() + '</h2><span></span></a>');
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700218 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800219
220 $olLessons = $('<ol class="lesson-list"></ol>');
221
222 $lessons = $(this).closest('li').find('ul li a');
223
224 if ($lessons.length) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800225 $lessons.each(function(index) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000226 $olLessons.append('<li><a href="' + $(this).attr('href') + '">' + $(this).html() + '</a></li>');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800227 });
228 } else {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800229 $pSummary.addClass('article');
230 }
231
Dirk Dougherty0dc81b92015-12-08 14:49:52 -0800232 $liClass.append($h2Title).append($pSummary).append($olLessons);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800233 $olClasses.append($liClass);
234 });
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000235 $('#classes').append($olClasses);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800236 }
237
238 // Set up expand/collapse behavior
239 initExpandableNavItems("#nav");
240
Dirk Dougherty541b4942014-02-14 18:31:53 -0800241 // Set up play-on-hover <video> tags.
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000242 $('video.play-on-hover').bind('click', function() {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800243 $(this).get(0).load(); // in case the video isn't seekable
244 $(this).get(0).play();
245 });
246
smain@google.comeeeb9b82016-08-29 18:12:27 -0700247 // Set up play-on-click for <video> tags with a "video-wrapper".
248 $('.video-wrapper > video').bind('click', function() {
249 this.play();
250 $(this.parentElement).addClass('playing');
251 });
252
Dirk Dougherty541b4942014-02-14 18:31:53 -0800253 // Set up tooltips
254 var TOOLTIP_MARGIN = 10;
255 $('acronym,.tooltip-link').each(function() {
256 var $target = $(this);
257 var $tooltip = $('<div>')
258 .addClass('tooltip-box')
259 .append($target.attr('title'))
260 .hide()
261 .appendTo('body');
262 $target.removeAttr('title');
263
264 $target.hover(function() {
265 // in
266 var targetRect = $target.offset();
267 targetRect.width = $target.width();
268 targetRect.height = $target.height();
269
270 $tooltip.css({
271 left: targetRect.left,
272 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
273 });
274 $tooltip.addClass('below');
275 $tooltip.show();
276 }, function() {
277 // out
278 $tooltip.hide();
279 });
280 });
281
282 // Set up <h2> deeplinks
283 $('h2').click(function() {
284 var id = $(this).attr('id');
285 if (id) {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -0800286 if (history && history.replaceState) {
287 // Change url without scrolling.
288 history.replaceState({}, '', '#' + id);
289 } else {
290 document.location.hash = id;
291 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800292 }
293 });
294
295 //Loads the +1 button
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000296 //var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
297 //po.src = 'https://apis.google.com/js/plusone.js';
298 //var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800299});
300// END of the onload event
301
Dirk Dougherty541b4942014-02-14 18:31:53 -0800302function initExpandableNavItems(rootTag) {
smain@google.comeeeb9b82016-08-29 18:12:27 -0700303 var toggleIcon = $(
304 rootTag + ' li.nav-section .nav-section-header .toggle-icon, ' +
305 rootTag + ' li.nav-section .nav-section-header a[href="#"]');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800306
smain@google.comeeeb9b82016-08-29 18:12:27 -0700307 toggleIcon.on('click keypress', function(e) {
308 if (e.type == 'keypress' && e.which == 13 || e.type == 'click') {
309 doNavToggle(this);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800310 }
311 });
312
313 // Stop expand/collapse behavior when clicking on nav section links
314 // (since we're navigating away from the page)
315 // This selector captures the first instance of <a>, but not those with "#" as the href.
316 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
317 window.location.href = $(this).attr('href');
318 return false;
319 });
320}
321
smain@google.comeeeb9b82016-08-29 18:12:27 -0700322function doNavToggle(el) {
323 var section = $(el).closest('li.nav-section');
324 if (section.hasClass('expanded')) {
325 /* hide me and descendants */
326 section.find('ul').slideUp(250, function() {
327 // remove 'expanded' class from my section and any children
328 section.closest('li').removeClass('expanded');
329 $('li.nav-section', section).removeClass('expanded');
330 });
331 } else {
332 /* show me */
333 // first hide all other siblings
334 var $others = $('li.nav-section.expanded', $(el).closest('ul')).not('.sticky');
335 $others.removeClass('expanded').children('ul').slideUp(250);
336
337 // now expand me
338 section.closest('li').addClass('expanded');
339 section.children('ul').slideDown(250);
340 }
341}
342
Dirk Dougherty541b4942014-02-14 18:31:53 -0800343/** Highlight the current page in sidenav, expanding children as appropriate */
344function highlightSidenav() {
345 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
346 if ($("ul#nav li.selected").length) {
347 unHighlightSidenav();
348 }
349 // look for URL in sidenav, including the hash
350 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
351
352 // If the selNavLink is still empty, look for it without the hash
353 if ($selNavLink.length == 0) {
354 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
355 }
356
357 var $selListItem;
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000358 var breadcrumb = [];
359
Dirk Dougherty541b4942014-02-14 18:31:53 -0800360 if ($selNavLink.length) {
361 // Find this page's <li> in sidenav and set selected
362 $selListItem = $selNavLink.closest('li');
363 $selListItem.addClass('selected');
364
365 // Traverse up the tree and expand all parent nav-sections
366 $selNavLink.parents('li.nav-section').each(function() {
367 $(this).addClass('expanded');
368 $(this).children('ul').show();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000369
370 var link = $(this).find('a').first();
371
372 if (!$(this).is($selListItem)) {
373 breadcrumb.unshift(link)
374 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800375 });
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000376
377 $('#nav').scrollIntoView($selNavLink);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800378 }
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000379
380 breadcrumb.forEach(function(link) {
381 link.dacCrumbs();
382 });
Dirk Dougherty541b4942014-02-14 18:31:53 -0800383}
384
385function unHighlightSidenav() {
386 $("ul#nav li.selected").removeClass("selected");
387 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
388}
389
Dirk Dougherty541b4942014-02-14 18:31:53 -0800390var agent = navigator['userAgent'].toLowerCase();
391// If a mobile phone, set flag and do mobile setup
392if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
393 (agent.indexOf("blackberry") != -1) ||
394 (agent.indexOf("webos") != -1) ||
395 (agent.indexOf("mini") != -1)) { // opera mini browsers
396 isMobile = true;
397}
398
Dirk Dougherty541b4942014-02-14 18:31:53 -0800399$(document).ready(function() {
400 $("pre:not(.no-pretty-print)").addClass("prettyprint");
401 prettyPrint();
402});
403
Dirk Dougherty541b4942014-02-14 18:31:53 -0800404/* Show popup dialogs */
405function showDialog(id) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000406 $dialog = $("#" + id);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800407 $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>');
408 $dialog.wrapInner('<div/>');
409 $dialog.removeClass("hide");
410}
411
Dirk Dougherty541b4942014-02-14 18:31:53 -0800412/* ######### COOKIES! ########## */
413
414function readCookie(cookie) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000415 var myCookie = cookie_namespace + "_" + cookie + "=";
Dirk Dougherty541b4942014-02-14 18:31:53 -0800416 if (document.cookie) {
417 var index = document.cookie.indexOf(myCookie);
418 if (index != -1) {
419 var valStart = index + myCookie.length;
420 var valEnd = document.cookie.indexOf(";", valStart);
421 if (valEnd == -1) {
422 valEnd = document.cookie.length;
423 }
424 var val = document.cookie.substring(valStart, valEnd);
425 return val;
426 }
427 }
428 return 0;
429}
430
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700431function writeCookie(cookie, val, section) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000432 if (val == undefined) return;
433 section = section == null ? "_" : "_" + section + "_";
434 var age = 2 * 365 * 24 * 60 * 60; // set max-age to 2 years
435 var cookieValue = cookie_namespace + section + cookie + "=" + val +
436 "; max-age=" + age + "; path=/";
Dirk Dougherty541b4942014-02-14 18:31:53 -0800437 document.cookie = cookieValue;
438}
439
440/* ######### END COOKIES! ########## */
441
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700442/*
443 * Manages secion card states and nav resize to conclude loading
Dirk Dougherty08032402014-02-15 10:14:35 -0800444 */
Dirk Dougherty08032402014-02-15 10:14:35 -0800445(function() {
446 $(document).ready(function() {
447
Dirk Dougherty08032402014-02-15 10:14:35 -0800448 // Stack hover states
449 $('.section-card-menu').each(function(index, el) {
450 var height = $(el).height();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000451 $(el).css({height:height + 'px', position:'relative'});
Dirk Dougherty08032402014-02-15 10:14:35 -0800452 var $cardInfo = $(el).find('.card-info');
453
454 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
455 });
456
Dirk Dougherty08032402014-02-15 10:14:35 -0800457 });
458
459})();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800460
Dirk Dougherty541b4942014-02-14 18:31:53 -0800461/* MISC LIBRARY FUNCTIONS */
462
Dirk Dougherty541b4942014-02-14 18:31:53 -0800463function toggle(obj, slide) {
464 var ul = $("ul:first", obj);
465 var li = ul.parent();
466 if (li.hasClass("closed")) {
467 if (slide) {
468 ul.slideDown("fast");
469 } else {
470 ul.show();
471 }
472 li.removeClass("closed");
473 li.addClass("open");
474 $(".toggle-img", li).attr("title", "hide pages");
475 } else {
476 ul.slideUp("fast");
477 li.removeClass("open");
478 li.addClass("closed");
479 $(".toggle-img", li).attr("title", "show pages");
480 }
481}
482
Dirk Dougherty541b4942014-02-14 18:31:53 -0800483function buildToggleLists() {
484 $(".toggle-list").each(
485 function(i) {
486 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
487 $(this).addClass("closed");
488 });
489}
490
Dirk Dougherty541b4942014-02-14 18:31:53 -0800491function hideNestedItems(list, toggle) {
492 $list = $(list);
493 // hide nested lists
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000494 if ($list.hasClass('showing')) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800495 $("li ol", $list).hide('fast');
496 $list.removeClass('showing');
497 // show nested lists
498 } else {
499 $("li ol", $list).show('fast');
500 $list.addClass('showing');
501 }
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000502 $(".more,.less", $(toggle)).toggle();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800503}
504
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700505/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
506function setupIdeDocToggle() {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000507 $("select.ide").change(function() {
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700508 var selected = $(this).find("option:selected").attr("value");
509 $(".select-ide").hide();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000510 $(".select-ide." + selected).show();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800511
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700512 $("select.ide").val(selected);
513 });
514}
Dirk Dougherty541b4942014-02-14 18:31:53 -0800515
Dirk Dougherty541b4942014-02-14 18:31:53 -0800516/* Used to hide and reveal supplemental content, such as long code samples.
517 See the companion CSS in android-developer-docs.css */
518function toggleContent(obj) {
519 var div = $(obj).closest(".toggle-content");
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000520 var toggleMe = $(".toggle-content-toggleme:eq(0)", div);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800521 if (div.hasClass("closed")) { // if it's closed, open it
522 toggleMe.slideDown();
523 $(".toggle-content-text:eq(0)", obj).toggle();
524 div.removeClass("closed").addClass("open");
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000525 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot +
smain@google.comeeeb9b82016-08-29 18:12:27 -0700526 "assets/images/styles/disclosure_up.png");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800527 } else { // if it's open, close it
528 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
529 $(".toggle-content-text:eq(0)", obj).toggle();
530 div.removeClass("open").addClass("closed");
531 div.find(".toggle-content").removeClass("open").addClass("closed")
532 .find(".toggle-content-toggleme").hide();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000533 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot +
smain@google.comeeeb9b82016-08-29 18:12:27 -0700534 "assets/images/styles/disclosure_down.png");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800535 });
536 }
537 return false;
538}
539
Dirk Dougherty541b4942014-02-14 18:31:53 -0800540/* New version of expandable content */
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000541function toggleExpandable(link, id) {
542 if ($(id).is(':visible')) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800543 $(id).slideUp();
544 $(link).removeClass('expanded');
545 } else {
546 $(id).slideDown();
547 $(link).addClass('expanded');
548 }
549}
550
551function hideExpandable(ids) {
552 $(ids).slideUp();
553 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
554}
555
Dirk Dougherty541b4942014-02-14 18:31:53 -0800556/*
557 * Slideshow 1.0
558 * Used on /index.html and /develop/index.html for carousel
559 *
560 * Sample usage:
561 * HTML -
562 * <div class="slideshow-container">
563 * <a href="" class="slideshow-prev">Prev</a>
564 * <a href="" class="slideshow-next">Next</a>
565 * <ul>
566 * <li class="item"><img src="images/marquee1.jpg"></li>
567 * <li class="item"><img src="images/marquee2.jpg"></li>
568 * <li class="item"><img src="images/marquee3.jpg"></li>
569 * <li class="item"><img src="images/marquee4.jpg"></li>
570 * </ul>
571 * </div>
572 *
573 * <script type="text/javascript">
574 * $('.slideshow-container').dacSlideshow({
575 * auto: true,
576 * btnPrev: '.slideshow-prev',
577 * btnNext: '.slideshow-next'
578 * });
579 * </script>
580 *
581 * Options:
582 * btnPrev: optional identifier for previous button
583 * btnNext: optional identifier for next button
584 * btnPause: optional identifier for pause button
585 * auto: whether or not to auto-proceed
586 * speed: animation speed
587 * autoTime: time between auto-rotation
588 * easing: easing function for transition
589 * start: item to select by default
590 * scroll: direction to scroll in
591 * pagination: whether or not to include dotted pagination
592 *
593 */
594
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000595(function($) {
596 $.fn.dacSlideshow = function(o) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800597
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000598 //Options - see above
599 o = $.extend({
600 btnPrev: null,
601 btnNext: null,
602 btnPause: null,
603 auto: true,
604 speed: 500,
605 autoTime: 12000,
606 easing: null,
607 start: 0,
608 scroll: 1,
609 pagination: true
Dirk Dougherty541b4942014-02-14 18:31:53 -0800610
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000611 }, o || {});
Dirk Dougherty541b4942014-02-14 18:31:53 -0800612
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000613 //Set up a carousel for each
614 return this.each(function() {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800615
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000616 var running = false;
617 var animCss = o.vertical ? "top" : "left";
618 var sizeCss = o.vertical ? "height" : "width";
619 var div = $(this);
620 var ul = $("ul", div);
621 var tLi = $("li", ul);
622 var tl = tLi.size();
623 var timer = null;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800624
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000625 var li = $("li", ul);
626 var itemLength = li.size();
627 var curr = o.start;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800628
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000629 li.css({float: o.vertical ? "none" : "left"});
630 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
631 div.css({position: "relative", "z-index": "2", left: "0px"});
Dirk Dougherty541b4942014-02-14 18:31:53 -0800632
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000633 var liSize = o.vertical ? height(li) : width(li);
634 var ulSize = liSize * itemLength;
635 var divSize = liSize;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800636
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000637 li.css({width: li.width(), height: li.height()});
638 ul.css(sizeCss, ulSize + "px").css(animCss, -(curr * liSize));
Dirk Dougherty541b4942014-02-14 18:31:53 -0800639
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000640 div.css(sizeCss, divSize + "px");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800641
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000642 //Pagination
643 if (o.pagination) {
644 var pagination = $("<div class='pagination'></div>");
645 var pag_ul = $("<ul></ul>");
646 if (tl > 1) {
647 for (var i = 0; i < tl; i++) {
648 var li = $("<li>" + i + "</li>");
649 pag_ul.append(li);
650 if (i == o.start) li.addClass('active');
651 li.click(function() {
652 go(parseInt($(this).text()));
653 })
654 }
655 pagination.append(pag_ul);
656 div.append(pagination);
657 }
658 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800659
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000660 //Previous button
661 if (o.btnPrev)
Dirk Dougherty541b4942014-02-14 18:31:53 -0800662 $(o.btnPrev).click(function(e) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000663 e.preventDefault();
664 return go(curr - o.scroll);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800665 });
666
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000667 //Next button
668 if (o.btnNext)
Dirk Dougherty541b4942014-02-14 18:31:53 -0800669 $(o.btnNext).click(function(e) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000670 e.preventDefault();
671 return go(curr + o.scroll);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800672 });
673
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000674 //Pause button
675 if (o.btnPause)
Dirk Dougherty541b4942014-02-14 18:31:53 -0800676 $(o.btnPause).click(function(e) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000677 e.preventDefault();
678 if ($(this).hasClass('paused')) {
679 startRotateTimer();
680 } else {
681 pauseRotateTimer();
682 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800683 });
684
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000685 //Auto rotation
686 if (o.auto) startRotateTimer();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800687
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000688 function startRotateTimer() {
689 clearInterval(timer);
690 timer = setInterval(function() {
691 if (curr == tl - 1) {
692 go(0);
693 } else {
694 go(curr + o.scroll);
695 }
696 }, o.autoTime);
697 $(o.btnPause).removeClass('paused');
698 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800699
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000700 function pauseRotateTimer() {
701 clearInterval(timer);
702 $(o.btnPause).addClass('paused');
703 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800704
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000705 //Go to an item
706 function go(to) {
707 if (!running) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800708
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000709 if (to < 0) {
710 to = itemLength - 1;
711 } else if (to > itemLength - 1) {
712 to = 0;
713 }
714 curr = to;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800715
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000716 running = true;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800717
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000718 ul.animate(
719 animCss == "left" ? {left: -(curr * liSize)} : {top: -(curr * liSize)} , o.speed, o.easing,
Dirk Dougherty541b4942014-02-14 18:31:53 -0800720 function() {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000721 running = false;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800722 }
723 );
724
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000725 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
726 $((curr - o.scroll < 0 && o.btnPrev) ||
727 (curr + o.scroll > itemLength && o.btnNext) ||
728 []
729 ).addClass("disabled");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800730
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000731 var nav_items = $('li', pagination);
732 nav_items.removeClass('active');
733 nav_items.eq(to).addClass('active');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800734
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000735 }
736 if (o.auto) startRotateTimer();
737 return false;
738 };
739 });
740 };
Dirk Dougherty541b4942014-02-14 18:31:53 -0800741
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000742 function css(el, prop) {
743 return parseInt($.css(el[0], prop)) || 0;
744 };
745 function width(el) {
746 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
747 };
748 function height(el) {
749 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
750 };
Dirk Dougherty541b4942014-02-14 18:31:53 -0800751
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000752})(jQuery);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800753
754/*
755 * dacSlideshow 1.0
756 * Used on develop/index.html for side-sliding tabs
757 *
758 * Sample usage:
759 * HTML -
760 * <div class="slideshow-container">
761 * <a href="" class="slideshow-prev">Prev</a>
762 * <a href="" class="slideshow-next">Next</a>
763 * <ul>
764 * <li class="item"><img src="images/marquee1.jpg"></li>
765 * <li class="item"><img src="images/marquee2.jpg"></li>
766 * <li class="item"><img src="images/marquee3.jpg"></li>
767 * <li class="item"><img src="images/marquee4.jpg"></li>
768 * </ul>
769 * </div>
770 *
771 * <script type="text/javascript">
772 * $('.slideshow-container').dacSlideshow({
773 * auto: true,
774 * btnPrev: '.slideshow-prev',
775 * btnNext: '.slideshow-next'
776 * });
777 * </script>
778 *
779 * Options:
780 * btnPrev: optional identifier for previous button
781 * btnNext: optional identifier for next button
782 * auto: whether or not to auto-proceed
783 * speed: animation speed
784 * autoTime: time between auto-rotation
785 * easing: easing function for transition
786 * start: item to select by default
787 * scroll: direction to scroll in
788 * pagination: whether or not to include dotted pagination
789 *
790 */
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000791(function($) {
792 $.fn.dacTabbedList = function(o) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800793
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000794 //Options - see above
795 o = $.extend({
796 speed : 250,
797 easing: null,
798 nav_id: null,
799 frame_id: null
800 }, o || {});
Dirk Dougherty541b4942014-02-14 18:31:53 -0800801
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000802 //Set up a carousel for each
803 return this.each(function() {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800804
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000805 var curr = 0;
806 var running = false;
807 var animCss = "margin-left";
808 var sizeCss = "width";
809 var div = $(this);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800810
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000811 var nav = $(o.nav_id, div);
812 var nav_li = $("li", nav);
813 var nav_size = nav_li.size();
814 var frame = div.find(o.frame_id);
815 var content_width = $(frame).find('ul').width();
816 //Buttons
817 $(nav_li).click(function(e) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800818 go($(nav_li).index($(this)));
819 })
820
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000821 //Go to an item
822 function go(to) {
823 if (!running) {
824 curr = to;
825 running = true;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800826
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000827 frame.animate({'margin-left' : -(curr * content_width)}, o.speed, o.easing,
Dirk Dougherty541b4942014-02-14 18:31:53 -0800828 function() {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000829 running = false;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800830 }
831 );
832
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000833 nav_li.removeClass('active');
834 nav_li.eq(to).addClass('active');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800835
Dirk Dougherty541b4942014-02-14 18:31:53 -0800836 }
837 return false;
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000838 };
Dirk Dougherty541b4942014-02-14 18:31:53 -0800839 });
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800840 };
Dirk Dougherty541b4942014-02-14 18:31:53 -0800841
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000842 function css(el, prop) {
843 return parseInt($.css(el[0], prop)) || 0;
Amanda Kassay4d5bcbf2016-05-13 12:24:39 -0400844 };
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000845 function width(el) {
846 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
847 };
848 function height(el) {
849 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
850 };
Amanda Kassay4d5bcbf2016-05-13 12:24:39 -0400851
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000852})(jQuery);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800853
854/* ######################################################## */
855/* ################# JAVADOC REFERENCE ################### */
856/* ######################################################## */
857
smain@google.comeeeb9b82016-08-29 18:12:27 -0700858
Dirk Dougherty541b4942014-02-14 18:31:53 -0800859
860var API_LEVEL_COOKIE = "api_level";
861var minLevel = 1;
862var maxLevel = 1;
863
Dirk Dougherty541b4942014-02-14 18:31:53 -0800864function buildApiLevelSelector() {
smain@google.comeeeb9b82016-08-29 18:12:27 -0700865 maxLevel = API_LEVELS.length;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800866 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
867 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
868
869 minLevel = parseInt($("#doc-api-level").attr("class"));
870 // Handle provisional api levels; the provisional level will always be the highest possible level
871 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
872 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
873 if (isNaN(minLevel) && minLevel.length) {
874 minLevel = maxLevel;
875 }
876 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000877 for (var i = maxLevel - 1; i >= 0; i--) {
smain@google.comeeeb9b82016-08-29 18:12:27 -0700878 var option = $("<option />").attr("value", "" + API_LEVELS[i]).append("" + API_LEVELS[i]);
879 // if (API_LEVELS[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
Dirk Dougherty541b4942014-02-14 18:31:53 -0800880 select.append(option);
881 }
882
883 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000884 var selectedLevelItem = $("#apiLevelSelector option[value='" + userApiLevel + "']").get(0);
885 selectedLevelItem.setAttribute('selected', true);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800886}
887
888function changeApiLevel() {
smain@google.comeeeb9b82016-08-29 18:12:27 -0700889 maxLevel = API_LEVELS.length;
890 minLevel = parseInt($('#doc-api-level').attr('class'));
Dirk Dougherty541b4942014-02-14 18:31:53 -0800891 var selectedLevel = maxLevel;
892
893 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
894 toggleVisisbleApis(selectedLevel, "body");
895
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700896 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800897
898 if (selectedLevel < minLevel) {
smain@google.comeeeb9b82016-08-29 18:12:27 -0700899 // Show the API notice dialog, set number values and button event
900 $('#api-unavailable').trigger('modal-open');
901 $('#api-unavailable .selected-level').text(selectedLevel);
902 $('#api-unavailable .api-level').text(minLevel);
903 $('#api-unavailable button.ok').attr('onclick','$("#apiLevelSelector").val("' + minLevel + '");changeApiLevel();');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800904 }
905}
906
907function toggleVisisbleApis(selectedLevel, context) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000908 var apis = $(".api", context);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800909 apis.each(function(i) {
910 var obj = $(this);
911 var className = obj.attr("class");
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000912 var apiLevelIndex = className.lastIndexOf("-") + 1;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800913 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
914 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
915 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
916 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
917 return;
918 }
919 apiLevel = parseInt(apiLevel);
920
921 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
922 var selectedLevelNum = parseInt(selectedLevel)
923 var apiLevelNum = parseInt(apiLevel);
924 if (isNaN(apiLevelNum)) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000925 apiLevelNum = maxLevel;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800926 }
927
928 // Grey things out that aren't available and give a tooltip title
929 if (apiLevelNum > selectedLevelNum) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000930 obj.addClass("absent").attr("title", "Requires API Level \"" +
931 apiLevel + "\" or higher. To reveal, change the target API level " +
932 "above the left navigation.");
933 } else obj.removeClass("absent").removeAttr("title");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800934 });
935}
936
Dirk Dougherty541b4942014-02-14 18:31:53 -0800937/* ################# SIDENAV TREE VIEW ################### */
Dirk Dougherty541b4942014-02-14 18:31:53 -0800938/* TODO: eliminate redundancy with non-google functions */
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000939function init_google_navtree(navtree_id, toroot, root_nodes) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800940 var me = new Object();
941 me.toroot = toroot;
942 me.node = new Object();
943
944 me.node.li = document.getElementById(navtree_id);
Dirk Dougherty0dc81b92015-12-08 14:49:52 -0800945 if (!me.node.li) {
946 return;
947 }
948
Dirk Dougherty541b4942014-02-14 18:31:53 -0800949 me.node.children_data = root_nodes;
950 me.node.children = new Array();
951 me.node.children_ul = document.createElement("ul");
952 me.node.get_children_ul = function() { return me.node.children_ul; };
953 //me.node.children_ul.className = "children_ul";
954 me.node.li.appendChild(me.node.children_ul);
955 me.node.depth = 0;
956
957 get_google_node(me, me.node);
958}
959
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000960function new_google_node(me, mom, text, link, children_data, api_level) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800961 var node = new Object();
962 var child;
963 node.children = Array();
964 node.children_data = children_data;
965 node.depth = mom.depth + 1;
966 node.get_children_ul = function() {
967 if (!node.children_ul) {
968 node.children_ul = document.createElement("ul");
969 node.children_ul.className = "tree-list-children";
970 node.li.appendChild(node.children_ul);
971 }
972 return node.children_ul;
973 };
974 node.li = document.createElement("li");
975
976 mom.get_children_ul().appendChild(node.li);
977
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000978 if (link) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800979 child = document.createElement("a");
980
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000981 } else {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800982 child = document.createElement("span");
983 child.className = "tree-list-subtitle";
984
985 }
986 if (children_data != null) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000987 node.li.className = "nav-section";
Dirk Dougherty541b4942014-02-14 18:31:53 -0800988 node.label_div = document.createElement("div");
989 node.label_div.className = "nav-section-header-ref";
990 node.li.appendChild(node.label_div);
991 get_google_node(me, node);
992 node.label_div.appendChild(child);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000993 } else {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800994 node.li.appendChild(child);
995 }
Amanda Kassay8bac6eb2016-05-26 17:58:23 +0000996 if (link) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800997 child.href = me.toroot + link;
998 }
999 node.label = document.createTextNode(text);
1000 child.appendChild(node.label);
1001
1002 node.children_ul = null;
1003
1004 return node;
1005}
1006
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001007function get_google_node(me, mom) {
Dirk Dougherty541b4942014-02-14 18:31:53 -08001008 mom.children_visited = true;
1009 var linkText;
1010 for (var i in mom.children_data) {
1011 var node_data = mom.children_data[i];
1012 linkText = node_data[0];
1013
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001014 if (linkText.match("^" + "com.google.android") == "com.google.android") {
Dirk Dougherty541b4942014-02-14 18:31:53 -08001015 linkText = linkText.substr(19, linkText.length);
1016 }
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001017 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
1018 node_data[2], node_data[3]);
Dirk Dougherty541b4942014-02-14 18:31:53 -08001019 }
1020}
1021
Dirk Dougherty541b4942014-02-14 18:31:53 -08001022/****** NEW version of script to build google and sample navs dynamically ******/
1023// TODO: update Google reference docs to tolerate this new implementation
1024
1025var NODE_NAME = 0;
1026var NODE_HREF = 1;
1027var NODE_GROUP = 2;
1028var NODE_TAGS = 3;
1029var NODE_CHILDREN = 4;
1030
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001031function init_google_navtree2(navtree_id, data) {
1032 var $containerUl = $("#" + navtree_id);
Dirk Dougherty541b4942014-02-14 18:31:53 -08001033 for (var i in data) {
1034 var node_data = data[i];
1035 $containerUl.append(new_google_node2(node_data));
1036 }
1037
1038 // Make all third-generation list items 'sticky' to prevent them from collapsing
1039 $containerUl.find('li li li.nav-section').addClass('sticky');
1040
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001041 initExpandableNavItems("#" + navtree_id);
Dirk Dougherty541b4942014-02-14 18:31:53 -08001042}
1043
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001044function new_google_node2(node_data) {
Dirk Dougherty541b4942014-02-14 18:31:53 -08001045 var linkText = node_data[NODE_NAME];
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001046 if (linkText.match("^" + "com.google.android") == "com.google.android") {
Dirk Dougherty541b4942014-02-14 18:31:53 -08001047 linkText = linkText.substr(19, linkText.length);
1048 }
1049 var $li = $('<li>');
1050 var $a;
1051 if (node_data[NODE_HREF] != null) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001052 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >' +
1053 linkText + '</a>');
Dirk Dougherty541b4942014-02-14 18:31:53 -08001054 } else {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001055 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >' +
1056 linkText + '/</a>');
Dirk Dougherty541b4942014-02-14 18:31:53 -08001057 }
1058 var $childUl = $('<ul>');
1059 if (node_data[NODE_CHILDREN] != null) {
1060 $li.addClass("nav-section");
1061 $a = $('<div class="nav-section-header">').append($a);
1062 if (node_data[NODE_HREF] == null) $a.addClass('empty');
1063
1064 for (var i in node_data[NODE_CHILDREN]) {
1065 var child_node_data = node_data[NODE_CHILDREN][i];
1066 $childUl.append(new_google_node2(child_node_data));
1067 }
1068 $li.append($childUl);
1069 }
1070 $li.prepend($a);
1071
1072 return $li;
1073}
1074
Dirk Dougherty541b4942014-02-14 18:31:53 -08001075function showGoogleRefTree() {
1076 init_default_google_navtree(toRoot);
1077 init_default_gcm_navtree(toRoot);
1078}
1079
1080function init_default_google_navtree(toroot) {
1081 // load json file for navtree data
1082 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001083 // when the file is loaded, initialize the tree
1084 if (jqxhr.status === 200) {
1085 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
1086 highlightSidenav();
1087 }
Dirk Dougherty541b4942014-02-14 18:31:53 -08001088 });
1089}
1090
1091function init_default_gcm_navtree(toroot) {
1092 // load json file for navtree data
1093 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001094 // when the file is loaded, initialize the tree
1095 if (jqxhr.status === 200) {
1096 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
1097 highlightSidenav();
1098 }
Dirk Dougherty541b4942014-02-14 18:31:53 -08001099 });
1100}
1101
1102/* TOGGLE INHERITED MEMBERS */
1103
1104/* Toggle an inherited class (arrow toggle)
1105 * @param linkObj The link that was clicked.
1106 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
1107 * 'null' to simply toggle.
1108 */
1109function toggleInherited(linkObj, expand) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001110 var base = linkObj.getAttribute("id");
1111 var list = document.getElementById(base + "-list");
1112 var summary = document.getElementById(base + "-summary");
1113 var trigger = document.getElementById(base + "-trigger");
1114 var a = $(linkObj);
1115 if ((expand == null && a.hasClass("closed")) || expand) {
1116 list.style.display = "none";
1117 summary.style.display = "block";
1118 trigger.src = toRoot + "assets/images/styles/disclosure_up.png";
1119 a.removeClass("closed");
1120 a.addClass("opened");
1121 } else if ((expand == null && a.hasClass("opened")) || (expand == false)) {
1122 list.style.display = "block";
1123 summary.style.display = "none";
1124 trigger.src = toRoot + "assets/images/styles/disclosure_down.png";
1125 a.removeClass("opened");
1126 a.addClass("closed");
1127 }
1128 return false;
Dirk Dougherty541b4942014-02-14 18:31:53 -08001129}
1130
1131/* Toggle all inherited classes in a single table (e.g. all inherited methods)
1132 * @param linkObj The link that was clicked.
1133 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
1134 * 'null' to simply toggle.
1135 */
1136function toggleAllInherited(linkObj, expand) {
1137 var a = $(linkObj);
1138 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
1139 var expandos = $(".jd-expando-trigger", table);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001140 if ((expand == null && a.text() == "[Expand]") || expand) {
Dirk Dougherty541b4942014-02-14 18:31:53 -08001141 expandos.each(function(i) {
1142 toggleInherited(this, true);
1143 });
1144 a.text("[Collapse]");
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001145 } else if ((expand == null && a.text() == "[Collapse]") || (expand == false)) {
Dirk Dougherty541b4942014-02-14 18:31:53 -08001146 expandos.each(function(i) {
1147 toggleInherited(this, false);
1148 });
1149 a.text("[Expand]");
1150 }
1151 return false;
1152}
1153
1154/* Toggle all inherited members in the class (link in the class title)
1155 */
1156function toggleAllClassInherited() {
1157 var a = $("#toggleAllClassInherited"); // get toggle link from class title
1158 var toggles = $(".toggle-all", $("#body-content"));
1159 if (a.text() == "[Expand All]") {
1160 toggles.each(function(i) {
1161 toggleAllInherited(this, true);
1162 });
1163 a.text("[Collapse All]");
1164 } else {
1165 toggles.each(function(i) {
1166 toggleAllInherited(this, false);
1167 });
1168 a.text("[Expand All]");
1169 }
1170 return false;
1171}
1172
1173/* Expand all inherited members in the class. Used when initiating page search */
1174function ensureAllInheritedExpanded() {
1175 var toggles = $(".toggle-all", $("#body-content"));
1176 toggles.each(function(i) {
1177 toggleAllInherited(this, true);
1178 });
1179 $("#toggleAllClassInherited").text("[Collapse All]");
1180}
1181
Dirk Dougherty541b4942014-02-14 18:31:53 -08001182/* HANDLE KEY EVENTS
1183 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
1184 */
1185var agent = navigator['userAgent'].toLowerCase();
1186var mac = agent.indexOf("macintosh") != -1;
1187
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001188$(document).keydown(function(e) {
1189 var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
Dirk Dougherty541b4942014-02-14 18:31:53 -08001190 if (control && e.which == 70) { // 70 is "F"
1191 ensureAllInheritedExpanded();
1192 }
1193});
1194
Dirk Dougherty541b4942014-02-14 18:31:53 -08001195/* On-demand functions */
1196
1197/** Move sample code line numbers out of PRE block and into non-copyable column */
1198function initCodeLineNumbers() {
1199 var numbers = $("#codesample-block a.number");
1200 if (numbers.length) {
1201 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
1202 }
1203
1204 $(document).ready(function() {
1205 // select entire line when clicked
1206 $("span.code-line").click(function() {
1207 if (!shifted) {
1208 selectText(this);
1209 }
1210 });
1211 // invoke line link on double click
1212 $(".code-line").dblclick(function() {
1213 document.location.hash = $(this).attr('id');
1214 });
1215 // highlight the line when hovering on the number
1216 $("#codesample-line-numbers a.number").mouseover(function() {
1217 var id = $(this).attr('href');
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001218 $(id).css('background', '#e7e7e7');
Dirk Dougherty541b4942014-02-14 18:31:53 -08001219 });
1220 $("#codesample-line-numbers a.number").mouseout(function() {
1221 var id = $(this).attr('href');
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001222 $(id).css('background', 'none');
Dirk Dougherty541b4942014-02-14 18:31:53 -08001223 });
1224 });
1225}
1226
1227// create SHIFT key binder to avoid the selectText method when selecting multiple lines
1228var shifted = false;
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001229$(document).bind('keyup keydown', function(e) {
1230 shifted = e.shiftKey; return true;
1231});
Dirk Dougherty541b4942014-02-14 18:31:53 -08001232
1233// courtesy of jasonedelman.com
1234function selectText(element) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001235 var doc = document ,
1236 range, selection
1237 ;
1238 if (doc.body.createTextRange) { //ms
1239 range = doc.body.createTextRange();
1240 range.moveToElementText(element);
1241 range.select();
1242 } else if (window.getSelection) { //all others
1243 selection = window.getSelection();
1244 range = doc.createRange();
1245 range.selectNodeContents(element);
1246 selection.removeAllRanges();
1247 selection.addRange(range);
1248 }
Dirk Dougherty541b4942014-02-14 18:31:53 -08001249}
1250
Dirk Dougherty541b4942014-02-14 18:31:53 -08001251/** Display links and other information about samples that match the
1252 group specified by the URL */
1253function showSamples() {
1254 var group = $("#samples").attr('class');
1255 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
1256
1257 var $ul = $("<ul>");
1258 $selectedLi = $("#nav li.selected");
1259
1260 $selectedLi.children("ul").children("li").each(function() {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001261 var $li = $("<li>").append($(this).find("a").first().clone());
smain@google.comeeeb9b82016-08-29 18:12:27 -07001262 var $samplesLink = $li.find("a");
1263 if ($samplesLink.text().endsWith('/')) {
1264 $samplesLink.text($samplesLink.text().slice(0,-1));
1265 }
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001266 $ul.append($li);
Dirk Dougherty541b4942014-02-14 18:31:53 -08001267 });
1268
1269 $("#samples").append($ul);
1270
1271}
Dirk Dougherty08032402014-02-15 10:14:35 -08001272
Dirk Dougherty08032402014-02-15 10:14:35 -08001273/* ########################################################## */
1274/* ################### RESOURCE CARDS ##################### */
1275/* ########################################################## */
1276
1277/** Handle resource queries, collections, and grids (sections). Requires
1278 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
1279
1280(function() {
Dirk Dougherty08032402014-02-15 10:14:35 -08001281 $(document).ready(function() {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001282 // Need to initialize hero carousel before other sections for dedupe
1283 // to work correctly.
1284 $('[data-carousel-query]').dacCarouselQuery();
1285
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001286 // Iterate over all instances and initialize a resource widget.
1287 $('.resource-widget').resourceWidget();
Dirk Dougherty08032402014-02-15 10:14:35 -08001288 });
1289
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001290 $.fn.widgetOptions = function() {
1291 return {
1292 cardSizes: (this.data('cardsizes') || '').split(','),
1293 maxResults: parseInt(this.data('maxresults'), 10) || Infinity,
1294 initialResults: this.data('initialResults'),
1295 itemsPerPage: this.data('itemsPerPage'),
1296 sortOrder: this.data('sortorder'),
1297 query: this.data('query'),
1298 section: this.data('section'),
Amanda Kassay4d5bcbf2016-05-13 12:24:39 -04001299 /* Added by LFL 6/6/14 */
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001300 resourceStyle: this.data('resourcestyle') || 'card',
1301 stackSort: this.data('stacksort') || 'true',
1302 // For filter based resources
1303 allowDuplicates: this.data('allow-duplicates') || 'false'
Amanda Kassay4d5bcbf2016-05-13 12:24:39 -04001304 };
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001305 };
Amanda Kassay4d5bcbf2016-05-13 12:24:39 -04001306
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001307 $.fn.deprecateOldGridStyles = function() {
1308 var m = this.get(0).className.match(/\bcol-(\d+)\b/);
1309 if (m && !this.is('.cols > *')) {
1310 this.removeClass('col-' + m[1]);
1311 }
1312 return this;
1313 }
Amanda Kassay4d5bcbf2016-05-13 12:24:39 -04001314
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001315 /*
1316 * Three types of resource layouts:
1317 * Flow - Uses a fixed row-height flow using float left style.
1318 * Carousel - Single card slideshow all same dimension absolute.
1319 * Stack - Uses fixed columns and flexible element height.
1320 */
1321 function initResourceWidget(widget, resources, opts) {
1322 var $widget = $(widget).deprecateOldGridStyles();
1323 var isFlow = $widget.hasClass('resource-flow-layout');
1324 var isCarousel = $widget.hasClass('resource-carousel-layout');
1325 var isStack = $widget.hasClass('resource-stack-layout');
1326
1327 opts = opts || $widget.widgetOptions();
1328 resources = resources || metadata.query(opts);
1329
1330 if (opts.maxResults !== undefined) {
1331 resources = resources.slice(0, opts.maxResults);
1332 }
Amanda Kassay4d5bcbf2016-05-13 12:24:39 -04001333
Dirk Dougherty08032402014-02-15 10:14:35 -08001334 if (isFlow) {
1335 drawResourcesFlowWidget($widget, opts, resources);
1336 } else if (isCarousel) {
1337 drawResourcesCarouselWidget($widget, opts, resources);
1338 } else if (isStack) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001339 opts.numStacks = $widget.data('numstacks');
1340 drawResourcesStackWidget($widget, opts, resources);
Dirk Dougherty08032402014-02-15 10:14:35 -08001341 }
1342 }
1343
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001344 $.fn.resourceWidget = function(resources, options) {
1345 return this.each(function() {
1346 initResourceWidget(this, resources, options);
1347 });
1348 };
1349
Dirk Dougherty08032402014-02-15 10:14:35 -08001350 /* Initializes a Resource Carousel Widget */
1351 function drawResourcesCarouselWidget($widget, opts, resources) {
1352 $widget.empty();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001353 var plusone = false; // stop showing plusone buttons on cards
Dirk Dougherty08032402014-02-15 10:14:35 -08001354
1355 $widget.addClass('resource-card slideshow-container')
1356 .append($('<a>').addClass('slideshow-prev').text('Prev'))
1357 .append($('<a>').addClass('slideshow-next').text('Next'));
1358
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001359 var css = {'width': $widget.width() + 'px',
1360 'height': $widget.height() + 'px'};
Dirk Dougherty08032402014-02-15 10:14:35 -08001361
1362 var $ul = $('<ul>');
1363
1364 for (var i = 0; i < resources.length; ++i) {
Dirk Dougherty08032402014-02-15 10:14:35 -08001365 var $card = $('<a>')
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001366 .attr('href', cleanUrl(resources[i].url))
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001367 .decorateResourceCard(resources[i], plusone);
Dirk Dougherty08032402014-02-15 10:14:35 -08001368
1369 $('<li>').css(css)
1370 .append($card)
1371 .appendTo($ul);
1372 }
1373
1374 $('<div>').addClass('frame')
1375 .append($ul)
1376 .appendTo($widget);
1377
1378 $widget.dacSlideshow({
1379 auto: true,
1380 btnPrev: '.slideshow-prev',
1381 btnNext: '.slideshow-next'
1382 });
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001383 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001384
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001385 /* Initializes a Resource Card Stack Widget (column-based layout)
1386 Modified by LFL 6/6/14
1387 */
Dirk Dougherty08032402014-02-15 10:14:35 -08001388 function drawResourcesStackWidget($widget, opts, resources, sections) {
1389 // Don't empty widget, grab all items inside since they will be the first
1390 // items stacked, followed by the resource query
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001391 var plusone = false; // stop showing plusone buttons on cards
Dirk Dougherty08032402014-02-15 10:14:35 -08001392 var cards = $widget.find('.resource-card').detach().toArray();
1393 var numStacks = opts.numStacks || 1;
1394 var $stacks = [];
Dirk Dougherty08032402014-02-15 10:14:35 -08001395
1396 for (var i = 0; i < numStacks; ++i) {
1397 $stacks[i] = $('<div>').addClass('resource-card-stack')
1398 .appendTo($widget);
1399 }
1400
1401 var sectionResources = [];
1402
1403 // Extract any subsections that are actually resource cards
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001404 if (sections) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001405 for (i = 0; i < sections.length; ++i) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001406 if (!sections[i].sections || !sections[i].sections.length) {
1407 // Render it as a resource card
1408 sectionResources.push(
1409 $('<a>')
1410 .addClass('resource-card section-card')
1411 .attr('href', cleanUrl(sections[i].resource.url))
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001412 .decorateResourceCard(sections[i].resource, plusone)[0]
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001413 );
Dirk Dougherty08032402014-02-15 10:14:35 -08001414
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001415 } else {
1416 cards.push(
1417 $('<div>')
1418 .addClass('resource-card section-card-menu')
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001419 .decorateResourceSection(sections[i], plusone)[0]
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001420 );
1421 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001422 }
1423 }
1424
1425 cards = cards.concat(sectionResources);
1426
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001427 for (i = 0; i < resources.length; ++i) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001428 var $card = createResourceElement(resources[i], opts);
1429
1430 if (opts.resourceStyle.indexOf('related') > -1) {
1431 $card.addClass('related-card');
1432 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001433
1434 cards.push($card[0]);
1435 }
1436
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001437 if (opts.stackSort !== 'false') {
1438 for (i = 0; i < cards.length; ++i) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001439 // Find the stack with the shortest height, but give preference to
1440 // left to right order.
1441 var minHeight = $stacks[0].height();
1442 var minIndex = 0;
Dirk Dougherty08032402014-02-15 10:14:35 -08001443
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001444 for (var j = 1; j < numStacks; ++j) {
1445 var height = $stacks[j].height();
1446 if (height < minHeight - 45) {
1447 minHeight = height;
1448 minIndex = j;
1449 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001450 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001451
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001452 $stacks[minIndex].append($(cards[i]));
1453 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001454 }
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001455 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001456
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001457 /*
1458 Create a resource card using the given resource object and a list of html
1459 configured options. Returns a jquery object containing the element.
1460 */
1461 function createResourceElement(resource, opts, plusone) {
1462 var $el;
1463
1464 // The difference here is that generic cards are not entirely clickable
1465 // so its a div instead of an a tag, also the generic one is not given
1466 // the resource-card class so it appears with a transparent background
1467 // and can be styled in whatever way the css setup.
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001468 if (opts.resourceStyle === 'generic') {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001469 $el = $('<div>')
1470 .addClass('resource')
1471 .attr('href', cleanUrl(resource.url))
1472 .decorateResource(resource, opts);
1473 } else {
1474 var cls = 'resource resource-card';
1475
1476 $el = $('<a>')
1477 .addClass(cls)
1478 .attr('href', cleanUrl(resource.url))
1479 .decorateResourceCard(resource, plusone);
1480 }
1481
1482 return $el;
1483 }
1484
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001485 function createResponsiveFlowColumn(cardSize) {
1486 var cardWidth = parseInt(cardSize.match(/(\d+)/)[1], 10);
1487 var column = $('<div>').addClass('col-' + (cardWidth / 3) + 'of6');
1488 if (cardWidth < 9) {
1489 column.addClass('col-tablet-1of2');
1490 } else if (cardWidth > 9 && cardWidth < 18) {
1491 column.addClass('col-tablet-1of1');
1492 }
1493 if (cardWidth < 18) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001494 column.addClass('col-mobile-1of1');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001495 }
1496 return column;
1497 }
1498
Dirk Dougherty08032402014-02-15 10:14:35 -08001499 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
1500 function drawResourcesFlowWidget($widget, opts, resources) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001501 // We'll be doing our own modifications to opts.
1502 opts = $.extend({}, opts);
1503
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001504 $widget.empty().addClass('cols');
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001505 if (opts.itemsPerPage) {
1506 $('<div class="col-1of1 dac-section-links dac-text-center">')
1507 .append(
1508 $('<div class="dac-section-link dac-show-less" data-toggle="show-less">Less<i class="dac-sprite dac-auto-unfold-less"></i></div>'),
1509 $('<div class="dac-section-link dac-show-more" data-toggle="show-more">More<i class="dac-sprite dac-auto-unfold-more"></i></div>')
1510 )
1511 .appendTo($widget);
1512 }
1513
1514 $widget.data('options.resourceflow', opts);
1515 $widget.data('resources.resourceflow', resources);
1516
1517 drawResourceFlowPage($widget, opts, resources);
1518 }
1519
1520 function drawResourceFlowPage($widget, opts, resources) {
1521 var cardSizes = opts.cardSizes || ['6x6']; // 2015-08-09: dynamic card sizes are deprecated
1522 var i = opts.currentIndex || 0;
1523 var j = 0;
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001524 var plusone = false; // stop showing plusone buttons on cards
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001525 var firstPage = i === 0;
1526 var initialResults = opts.initialResults || opts.itemsPerPage || resources.length;
1527 var max = firstPage ? initialResults : i + opts.itemsPerPage;
1528 max = Math.min(resources.length, max);
Dirk Dougherty08032402014-02-15 10:14:35 -08001529
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001530 var page = $('<div class="resource-flow-page">');
1531 if (opts.itemsPerPage) {
1532 $widget.find('.dac-section-links').before(page);
1533 } else {
1534 $widget.append(page);
1535 }
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001536
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001537 while (i < max) {
Dirk Dougherty08032402014-02-15 10:14:35 -08001538 var cardSize = cardSizes[j++ % cardSizes.length];
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001539 cardSize = cardSize.replace(/^\s+|\s+$/, '');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001540
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001541 var column = createResponsiveFlowColumn(cardSize).appendTo(page);
Dirk Dougherty08032402014-02-15 10:14:35 -08001542
1543 // A stack has a third dimension which is the number of stacked items
1544 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
1545 var stackCount = 0;
1546 var $stackDiv = null;
1547
1548 if (isStack) {
1549 // Create a stack container which should have the dimensions defined
1550 // by the product of the items inside.
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001551 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1] +
1552 'x' + isStack[2] * isStack[3]) .appendTo(column);
Dirk Dougherty08032402014-02-15 10:14:35 -08001553 }
1554
1555 // Build each stack item or just a single item
1556 do {
1557 var resource = resources[i];
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001558
1559 var $card = createResourceElement(resources[i], opts, plusone);
1560
1561 $card.addClass('resource-card-' + cardSize +
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001562 ' resource-card-' + resource.type.toLowerCase());
Dirk Dougherty08032402014-02-15 10:14:35 -08001563
1564 if (isStack) {
1565 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001566 if (++stackCount === parseInt(isStack[3])) {
Dirk Dougherty08032402014-02-15 10:14:35 -08001567 $card.addClass('resource-card-row-stack-last');
1568 stackCount = 0;
1569 }
1570 } else {
1571 stackCount = 0;
1572 }
1573
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001574 $card.appendTo($stackDiv || column);
Dirk Dougherty08032402014-02-15 10:14:35 -08001575
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001576 } while (++i < max && stackCount > 0);
1577
1578 // Record number of pages viewed in analytics.
1579 if (!firstPage) {
1580 var clicks = Math.ceil((i - initialResults) / opts.itemsPerPage);
smain@google.comeeeb9b82016-08-29 18:12:27 -07001581 devsite.analytics.trackAnalyticsEvent('event',
1582 'Cards', 'Click More', clicks);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001583 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001584 }
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001585
1586 opts.currentIndex = i;
1587 $widget.toggleClass('dac-has-more', i < resources.length);
1588 $widget.toggleClass('dac-has-less', !firstPage);
1589
1590 $widget.trigger('dac:domchange');
1591 if (opts.onRenderPage) {
1592 opts.onRenderPage(page);
1593 }
1594 }
1595
1596 function drawResourceFlowReset($widget, opts, resources) {
1597 $widget.find('.resource-flow-page')
1598 .slice(1)
1599 .remove();
1600 $widget.toggleClass('dac-has-more', true);
1601 $widget.toggleClass('dac-has-less', false);
1602
1603 opts.currentIndex = Math.min(opts.initialResults, resources.length);
smain@google.comeeeb9b82016-08-29 18:12:27 -07001604 devsite.analytics.trackAnalyticsEvent('event', 'Cards', 'Click Less');
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001605 }
1606
1607 /* A decorator for event functions which finds the surrounding widget and it's options */
1608 function wrapWithWidget(func) {
1609 return function(e) {
1610 if (e) e.preventDefault();
1611
1612 var $widget = $(this).closest('.resource-flow-layout');
1613 var opts = $widget.data('options.resourceflow');
1614 var resources = $widget.data('resources.resourceflow');
1615 func($widget, opts, resources);
1616 };
Dirk Dougherty08032402014-02-15 10:14:35 -08001617 }
1618
1619 /* Build a site map of resources using a section as a root. */
1620 function buildSectionList(opts) {
1621 if (opts.section && SECTION_BY_ID[opts.section]) {
1622 return SECTION_BY_ID[opts.section].sections || [];
1623 }
1624 return [];
1625 }
1626
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001627 function cleanUrl(url) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001628 if (url && url.indexOf('//') === -1) {
1629 url = toRoot + url;
1630 }
1631
1632 return url;
1633 }
1634
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001635 // Delegated events for resources.
1636 $(document).on('click', '.resource-flow-layout [data-toggle="show-more"]', wrapWithWidget(drawResourceFlowPage));
1637 $(document).on('click', '.resource-flow-layout [data-toggle="show-less"]', wrapWithWidget(drawResourceFlowReset));
Dirk Dougherty08032402014-02-15 10:14:35 -08001638})();
1639
1640(function($) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001641 // A mapping from category and type values to new values or human presentable strings.
1642 var SECTION_MAP = {
1643 googleplay: 'google play'
1644 };
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001645
1646 /*
1647 Utility method for creating dom for the description area of a card.
1648 Used in decorateResourceCard and decorateResource.
1649 */
1650 function buildResourceCardDescription(resource, plusone) {
1651 var $description = $('<div>').addClass('description ellipsis');
1652
1653 $description.append($('<div>').addClass('text').html(resource.summary));
1654
1655 if (resource.cta) {
1656 $description.append($('<a>').addClass('cta').html(resource.cta));
1657 }
1658
1659 if (plusone) {
1660 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
1661 "//developer.android.com/" + resource.url;
1662
1663 $description.append($('<div>').addClass('util')
1664 .append($('<div>').addClass('g-plusone')
1665 .attr('data-size', 'small')
1666 .attr('data-align', 'right')
1667 .attr('data-href', plusurl)));
1668 }
1669
1670 return $description;
1671 }
1672
Dirk Dougherty08032402014-02-15 10:14:35 -08001673 /* Simple jquery function to create dom for a standard resource card */
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001674 $.fn.decorateResourceCard = function(resource, plusone) {
1675 var section = resource.category || resource.type;
1676 section = (SECTION_MAP[section] || section).toLowerCase();
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001677 var imgUrl = resource.image ||
1678 'assets/images/resource-card-default-android.jpg';
1679
1680 if (imgUrl.indexOf('//') === -1) {
1681 imgUrl = toRoot + imgUrl;
Dirk Dougherty08032402014-02-15 10:14:35 -08001682 }
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001683
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001684 if (resource.type === 'youtube' || resource.type === 'video') {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001685 $('<div>').addClass('play-button')
1686 .append($('<i class="dac-sprite dac-play-white">'))
1687 .appendTo(this);
1688 }
1689
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001690 $('<div>').addClass('card-bg')
1691 .css('background-image', 'url(' + (imgUrl || toRoot +
1692 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Dougherty08032402014-02-15 10:14:35 -08001693 .appendTo(this);
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001694
1695 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
1696 .append($('<div>').addClass('section').text(section))
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001697 .append($('<div>').addClass('title' + (resource.title_highlighted ? ' highlighted' : ''))
1698 .html(resource.title_highlighted || resource.title))
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001699 .append(buildResourceCardDescription(resource, plusone))
1700 .appendTo(this);
Dirk Dougherty08032402014-02-15 10:14:35 -08001701
1702 return this;
1703 };
1704
1705 /* Simple jquery function to create dom for a resource section card (menu) */
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001706 $.fn.decorateResourceSection = function(section, plusone) {
Dirk Dougherty08032402014-02-15 10:14:35 -08001707 var resource = section.resource;
1708 //keep url clean for matching and offline mode handling
1709 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
1710 var $base = $('<a>')
1711 .addClass('card-bg')
1712 .attr('href', resource.url)
1713 .append($('<div>').addClass('card-section-icon')
1714 .append($('<div>').addClass('icon'))
1715 .append($('<div>').addClass('section').html(resource.title)))
1716 .appendTo(this);
1717
1718 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
1719
1720 if (section.sections && section.sections.length) {
1721 // Recurse the section sub-tree to find a resource image.
1722 var stack = [section];
1723
1724 while (stack.length) {
1725 if (stack[0].resource.image) {
1726 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
1727 break;
1728 }
1729
1730 if (stack[0].sections) {
1731 stack = stack.concat(stack[0].sections);
1732 }
1733
1734 stack.shift();
1735 }
1736
1737 var $ul = $('<ul>')
1738 .appendTo($cardInfo);
1739
1740 var max = section.sections.length > 3 ? 3 : section.sections.length;
1741
1742 for (var i = 0; i < max; ++i) {
1743
1744 var subResource = section.sections[i];
Dirk Dougherty318fb972014-04-08 18:46:53 -07001745 if (!plusone) {
1746 $('<li>')
1747 .append($('<a>').attr('href', subResource.url)
1748 .append($('<div>').addClass('title').html(subResource.title))
1749 .append($('<div>').addClass('description ellipsis')
1750 .append($('<div>').addClass('text').html(subResource.summary))
1751 .append($('<div>').addClass('util'))))
1752 .appendTo($ul);
1753 } else {
1754 $('<li>')
1755 .append($('<a>').attr('href', subResource.url)
1756 .append($('<div>').addClass('title').html(subResource.title))
1757 .append($('<div>').addClass('description ellipsis')
1758 .append($('<div>').addClass('text').html(subResource.summary))
1759 .append($('<div>').addClass('util')
1760 .append($('<div>').addClass('g-plusone')
1761 .attr('data-size', 'small')
1762 .attr('data-align', 'right')
1763 .attr('data-href', resource.url)))))
1764 .appendTo($ul);
1765 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001766 }
1767
1768 // Add a more row
1769 if (max < section.sections.length) {
1770 $('<li>')
1771 .append($('<a>').attr('href', resource.url)
1772 .append($('<div>')
1773 .addClass('title')
1774 .text('More')))
1775 .appendTo($ul);
1776 }
1777 } else {
1778 // No sub-resources, just render description?
1779 }
1780
1781 return this;
1782 };
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001783
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001784 /* Render other types of resource styles that are not cards. */
1785 $.fn.decorateResource = function(resource, opts) {
1786 var imgUrl = resource.image ||
1787 'assets/images/resource-card-default-android.jpg';
1788 var linkUrl = resource.url;
1789
1790 if (imgUrl.indexOf('//') === -1) {
1791 imgUrl = toRoot + imgUrl;
1792 }
1793
1794 if (linkUrl && linkUrl.indexOf('//') === -1) {
1795 linkUrl = toRoot + linkUrl;
1796 }
1797
1798 $(this).append(
1799 $('<div>').addClass('image')
1800 .css('background-image', 'url(' + imgUrl + ')'),
1801 $('<div>').addClass('info').append(
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00001802 $('<h4>').addClass('title').html(resource.title_highlighted || resource.title),
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001803 $('<p>').addClass('summary').html(resource.summary),
1804 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
1805 )
1806 );
1807
1808 return this;
1809 };
Dirk Dougherty08032402014-02-15 10:14:35 -08001810})(jQuery);
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001811
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001812/*
1813 Fullscreen Carousel
1814
1815 The following allows for an area at the top of the page that takes over the
1816 entire browser height except for its top offset and an optional bottom
1817 padding specified as a data attribute.
1818
1819 HTML:
1820
1821 <div class="fullscreen-carousel">
1822 <div class="fullscreen-carousel-content">
1823 <!-- content here -->
1824 </div>
1825 <div class="fullscreen-carousel-content">
1826 <!-- content here -->
1827 </div>
1828
1829 etc ...
1830
1831 </div>
1832
1833 Control over how the carousel takes over the screen can mostly be defined in
1834 a css file. Setting min-height on the .fullscreen-carousel-content elements
1835 will prevent them from shrinking to far vertically when the browser is very
1836 short, and setting max-height on the .fullscreen-carousel itself will prevent
1837 the area from becoming to long in the case that the browser is stretched very
1838 tall.
1839
1840 There is limited functionality for having multiple sections since that request
1841 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
1842 scroll between multiple content areas.
1843*/
1844
1845(function() {
1846 $(document).ready(function() {
1847 $('.fullscreen-carousel').each(function() {
1848 initWidget(this);
1849 });
1850 });
1851
1852 function initWidget(widget) {
1853 var $widget = $(widget);
1854
1855 var topOffset = $widget.offset().top;
1856 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
1857 var maxHeight = 0;
1858 var minHeight = 0;
1859 var $content = $widget.find('.fullscreen-carousel-content');
1860 var $nextArrow = $widget.find('.next-arrow');
1861 var $prevArrow = $widget.find('.prev-arrow');
1862 var $curSection = $($content[0]);
1863
1864 if ($content.length <= 1) {
1865 $nextArrow.hide();
1866 $prevArrow.hide();
1867 } else {
1868 $nextArrow.click(function() {
1869 var index = ($content.index($curSection) + 1);
1870 $curSection.hide();
1871 $curSection = $($content[index >= $content.length ? 0 : index]);
1872 $curSection.show();
1873 });
1874
1875 $prevArrow.click(function() {
1876 var index = ($content.index($curSection) - 1);
1877 $curSection.hide();
1878 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
1879 $curSection.show();
1880 });
1881 }
1882
1883 // Just hide all content sections except first.
1884 $content.each(function(index) {
1885 if ($(this).height() > minHeight) minHeight = $(this).height();
1886 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
1887 });
1888
1889 // Register for changes to window size, and trigger.
1890 $(window).resize(resizeWidget);
1891 resizeWidget();
1892
1893 function resizeWidget() {
1894 var height = $(window).height() - topOffset - padBottom;
1895 $widget.width($(window).width());
1896 $widget.height(height < minHeight ? minHeight :
1897 (maxHeight && height > maxHeight ? maxHeight : height));
1898 }
1899 }
1900})();
1901
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001902/*
1903 Tab Carousel
1904
1905 The following allows tab widgets to be installed via the html below. Each
1906 tab content section should have a data-tab attribute matching one of the
1907 nav items'. Also each tab content section should have a width matching the
1908 tab carousel.
1909
1910 HTML:
1911
1912 <div class="tab-carousel">
1913 <ul class="tab-nav">
1914 <li><a href="#" data-tab="handsets">Handsets</a>
1915 <li><a href="#" data-tab="wearable">Wearable</a>
1916 <li><a href="#" data-tab="tv">TV</a>
1917 </ul>
1918
1919 <div class="tab-carousel-content">
1920 <div data-tab="handsets">
1921 <!--Full width content here-->
1922 </div>
1923
1924 <div data-tab="wearable">
1925 <!--Full width content here-->
1926 </div>
1927
1928 <div data-tab="tv">
1929 <!--Full width content here-->
1930 </div>
1931 </div>
1932 </div>
1933
1934*/
1935(function() {
1936 $(document).ready(function() {
1937 $('.tab-carousel').each(function() {
1938 initWidget(this);
1939 });
1940 });
1941
1942 function initWidget(widget) {
1943 var $widget = $(widget);
1944 var $nav = $widget.find('.tab-nav');
1945 var $anchors = $nav.find('[data-tab]');
1946 var $li = $nav.find('li');
1947 var $contentContainer = $widget.find('.tab-carousel-content');
1948 var $tabs = $contentContainer.find('[data-tab]');
1949 var $curTab = $($tabs[0]); // Current tab is first tab.
1950 var width = $widget.width();
1951
1952 // Setup nav interactivity.
1953 $anchors.click(function(evt) {
1954 evt.preventDefault();
1955 var query = '[data-tab=' + $(this).data('tab') + ']';
1956 transitionWidget($tabs.filter(query));
1957 });
1958
1959 // Add highlight for navigation on first item.
1960 var $highlight = $('<div>').addClass('highlight')
1961 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
1962 .appendTo($nav);
1963
1964 // Store height since we will change contents to absolute.
1965 $contentContainer.height($contentContainer.height());
1966
1967 // Absolutely position tabs so they're ready for transition.
1968 $tabs.each(function(index) {
1969 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
1970 });
1971
1972 function transitionWidget($toTab) {
1973 if (!$curTab.is($toTab)) {
1974 var curIndex = $tabs.index($curTab[0]);
1975 var toIndex = $tabs.index($toTab[0]);
1976 var dir = toIndex > curIndex ? 1 : -1;
1977
1978 // Animate content sections.
1979 $toTab.css({left:(width * dir) + 'px'});
1980 $curTab.animate({left:(width * -dir) + 'px'});
1981 $toTab.animate({left:'0'});
1982
1983 // Animate navigation highlight.
1984 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
1985 width:$($li[toIndex]).outerWidth() + 'px'})
1986
1987 // Store new current section.
1988 $curTab = $toTab;
1989 }
1990 }
1991 }
1992})();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001993
1994/**
1995 * Auto TOC
1996 *
1997 * Upgrades h2s on the page to have a rule and be toggle-able on mobile.
1998 */
1999(function($) {
2000 var upgraded = false;
2001 var h2Titles;
2002
2003 function initWidget() {
2004 // add HRs below all H2s (except for a few other h2 variants)
2005 // Consider doing this with css instead.
2006 h2Titles = $('h2').not('#qv h2, #tb h2, .sidebox h2, #devdoc-nav h2, h2.norule');
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002007 h2Titles.css({paddingBottom:0}).after('<hr/>');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002008
2009 // Exit early if on older browser.
2010 if (!window.matchMedia) {
2011 return;
2012 }
2013
2014 // Only run logic in mobile layout.
2015 var query = window.matchMedia('(max-width: 719px)');
2016 if (query.matches) {
2017 makeTogglable();
2018 } else {
2019 query.addListener(makeTogglable);
2020 }
2021 }
2022
2023 function makeTogglable() {
2024 // Only run this logic once.
2025 if (upgraded) { return; }
2026 upgraded = true;
2027
2028 // Only make content h2s togglable.
2029 var contentTitles = h2Titles.filter('#jd-content *');
2030
2031 // If there are more than 1
2032 if (contentTitles.size() < 2) {
2033 return;
2034 }
2035
2036 contentTitles.each(function() {
2037 // Find all the relevant nodes.
2038 var $title = $(this);
2039 var $hr = $title.next();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002040 var $contents = allNextUntil($hr[0], 'h2, .next-docs');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002041 var $section = $($title)
2042 .add($hr)
2043 .add($title.prev('a[name]'))
2044 .add($contents);
2045 var $anchor = $section.first().prev();
2046 var anchorMethod = 'after';
2047 if ($anchor.length === 0) {
2048 $anchor = $title.parent();
2049 anchorMethod = 'prepend';
2050 }
2051
2052 // Some h2s are in their own container making it pretty hard to find the end, so skip.
2053 if ($contents.length === 0) {
2054 return;
2055 }
2056
2057 // Remove from DOM before messing with it. DOM is slow!
2058 $section.detach();
2059
2060 // Add mobile-only expand arrows.
2061 $title.prepend('<span class="dac-visible-mobile-inline-block">' +
2062 '<i class="dac-toggle-expand dac-sprite dac-expand-more-black"></i>' +
2063 '<i class="dac-toggle-collapse dac-sprite dac-expand-less-black"></i>' +
2064 '</span>')
2065 .attr('data-toggle', 'section');
2066
2067 // Wrap in magic markup.
2068 $section = $section.wrapAll('<div class="dac-toggle dac-mobile">').parent();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002069
2070 // extra div used for max-height calculation.
2071 $contents.wrapAll('<div class="dac-toggle-content dac-expand"><div>');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002072
2073 // Pre-expand section if requested.
2074 if ($title.hasClass('is-expanded')) {
2075 $section.addClass('is-expanded');
2076 }
2077
2078 // Pre-expand section if targetted by hash.
2079 if (location.hash && $section.find(location.hash).length) {
2080 $section.addClass('is-expanded');
2081 }
2082
2083 // Add it back to the dom.
2084 $anchor[anchorMethod].call($anchor, $section);
2085 });
2086 }
2087
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002088 // Similar to $.fn.nextUntil() except we need all nodes, jQuery skips text nodes.
2089 function allNextUntil(elem, until) {
2090 var matched = [];
2091
2092 while ((elem = elem.nextSibling) && elem.nodeType !== 9) {
2093 if (elem.nodeType === 1 && jQuery(elem).is(until)) {
2094 break;
2095 }
2096 matched.push(elem);
2097 }
2098 return $(matched);
2099 }
2100
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002101 $(function() {
2102 initWidget();
2103 });
2104})(jQuery);
2105
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002106(function($, window) {
2107 'use strict';
2108
2109 // Blogger API info
2110 var apiUrl = 'https://www.googleapis.com/blogger/v3';
2111 var apiKey = 'AIzaSyCFhbGnjW06dYwvRCU8h_zjdpS4PYYbEe8';
2112
2113 // Blog IDs can be found in the markup of the blog posts
2114 var blogs = {
2115 'android-developers': {
2116 id: '6755709643044947179',
2117 title: 'Android Developers Blog'
2118 }
2119 };
2120 var monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
2121 'July', 'August', 'September', 'October', 'November', 'December'];
2122
2123 var BlogReader = (function() {
2124 var reader;
2125
2126 function BlogReader() {
2127 this.doneSetup = false;
2128 }
2129
2130 /**
2131 * Initialize the blog reader and modal.
2132 */
2133 BlogReader.prototype.setup = function() {
2134 $('#jd-content').append(
2135 '<div id="blog-reader" data-modal="blog-reader" class="dac-modal dac-has-small-header">' +
2136 '<div class="dac-modal-container">' +
2137 '<div class="dac-modal-window">' +
2138 '<header class="dac-modal-header">' +
2139 '<div class="dac-modal-header-actions">' +
2140 '<a href="" class="dac-modal-header-open" target="_blank">' +
2141 '<i class="dac-sprite dac-open-in-new"></i>' +
2142 '</a>' +
2143 '<button class="dac-modal-header-close" data-modal-toggle>' +
2144 '</button>' +
2145 '</div>' +
2146 '<h2 class="norule dac-modal-header-title"></h2>' +
2147 '</header>' +
2148 '<div class="dac-modal-content dac-blog-reader">' +
2149 '<time class="dac-blog-reader-date" pubDate></time>' +
2150 '<h3 class="dac-blog-reader-title"></h3>' +
2151 '<div class="dac-blog-reader-text clearfix"></div>' +
2152 '</div>' +
2153 '</div>' +
2154 '</div>' +
2155 '</div>');
2156
2157 this.blogReader = $('#blog-reader').dacModal();
2158
2159 this.doneSetup = true;
2160 };
2161
2162 BlogReader.prototype.openModal_ = function(blog, post) {
2163 var published = new Date(post.published);
Dirk Dougherty2732bb72016-05-26 13:05:10 -07002164 var formattedDate = monthNames[published.getMonth()] + ' ' + published.getDate() + ' ' + published.getFullYear();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002165 this.blogReader.find('.dac-modal-header-open').attr('href', post.url);
2166 this.blogReader.find('.dac-modal-header-title').text(blog.title);
2167 this.blogReader.find('.dac-blog-reader-title').html(post.title);
2168 this.blogReader.find('.dac-blog-reader-date').html(formattedDate);
2169 this.blogReader.find('.dac-blog-reader-text').html(post.content);
2170 this.blogReader.trigger('modal-open');
2171 };
2172
2173 /**
2174 * Show a blog post in a modal
2175 * @param {string} blogName - The name of the Blogspot blog.
2176 * @param {string} postPath - The path to the blog post.
2177 * @param {bool} secondTry - Has it failed once?
2178 */
2179 BlogReader.prototype.showPost = function(blogName, postPath, secondTry) {
2180 var blog = blogs[blogName];
2181 var postUrl = 'https://' + blogName + '.blogspot.com' + postPath;
2182
2183 var url = apiUrl + '/blogs/' + blog.id + '/posts/bypath?path=' + encodeURIComponent(postPath) + '&key=' + apiKey;
2184 $.ajax(url, {timeout: 650}).done(this.openModal_.bind(this, blog)).fail(function(error) {
2185 // Retry once if we get an error
2186 if (error.status === 500 && !secondTry) {
2187 this.showPost(blogName, postPath, true);
2188 } else {
2189 window.location.href = postUrl;
2190 }
2191 }.bind(this));
2192 };
2193
2194 return {
2195 getReader: function() {
2196 if (!reader) {
2197 reader = new BlogReader();
2198 }
2199 return reader;
2200 }
2201 };
2202 })();
2203
2204 var blogReader = BlogReader.getReader();
2205
2206 function wrapLinkWithReader(e) {
2207 var el = $(e.currentTarget);
2208 if (el.hasClass('dac-modal-header-open')) {
2209 return;
2210 }
2211
2212 // Only catch links on blogspot.com
2213 var matches = el.attr('href').match(/https?:\/\/([^\.]*).blogspot.com([^$]*)/);
2214 if (matches && matches.length === 3) {
2215 var blogName = matches[1];
2216 var postPath = matches[2];
2217
2218 // Check if we have information about the blog
2219 if (!blogs[blogName]) {
2220 return;
2221 }
2222
2223 // Setup the first time it's used
2224 if (!blogReader.doneSetup) {
2225 blogReader.setup();
2226 }
2227
2228 e.preventDefault();
2229 blogReader.showPost(blogName, postPath);
2230 }
2231 }
2232
smain@google.comeeeb9b82016-08-29 18:12:27 -07002233 $(document).on('click.blog-reader', 'a.resource-card[href*="blogspot.com/"]',
2234 wrapLinkWithReader);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002235})(jQuery, window);
2236
2237(function($) {
2238 $.fn.debounce = function(func, wait, immediate) {
2239 var timeout;
2240
2241 return function() {
2242 var context = this;
2243 var args = arguments;
2244
2245 var later = function() {
2246 timeout = null;
2247 if (!immediate) {
2248 func.apply(context, args);
2249 }
2250 };
2251
2252 var callNow = immediate && !timeout;
2253 clearTimeout(timeout);
2254 timeout = setTimeout(later, wait);
2255
2256 if (callNow) {
2257 func.apply(context, args);
2258 }
2259 };
2260 };
2261})(jQuery);
2262
2263/* Calculate the vertical area remaining */
2264(function($) {
2265 $.fn.ellipsisfade = function() {
2266 // Only fetch line-height of first element to avoid recalculate style.
2267 // Will be NaN if no elements match, which is ok.
2268 var lineHeight = parseInt(this.css('line-height'), 10);
2269
2270 this.each(function() {
2271 // get element text
2272 var $this = $(this);
2273 var remainingHeight = $this.parent().parent().height();
2274 $this.parent().siblings().each(function() {
2275 var elHeight;
2276 if ($(this).is(':visible')) {
2277 elHeight = $(this).outerHeight(true);
2278 remainingHeight = remainingHeight - elHeight;
2279 }
2280 });
2281
2282 var adjustedRemainingHeight = ((remainingHeight) / lineHeight >> 0) * lineHeight;
2283 $this.parent().css({height: adjustedRemainingHeight});
2284 $this.css({height: 'auto'});
2285 });
2286
2287 return this;
2288 };
2289
2290 /* Pass the line height to ellipsisfade() to adjust the height of the
2291 text container to show the max number of lines possible, without
2292 showing lines that are cut off. This works with the css ellipsis
2293 classes to fade last text line and apply an ellipsis char. */
2294 function updateEllipsis(context) {
2295 if (!(context instanceof jQuery)) {
2296 context = $('html');
2297 }
2298
2299 context.find('.card-info .text').ellipsisfade();
2300 }
2301
2302 $(window).on('resize', $.fn.debounce(updateEllipsis, 500));
2303 $(updateEllipsis);
2304 $('html').on('dac:domchange', function(e) { updateEllipsis($(e.target)); });
2305})(jQuery);
2306
2307/* Filter */
2308(function($) {
2309 'use strict';
2310
2311 /**
2312 * A single filter item content.
2313 * @type {string} - Element template.
2314 * @private
2315 */
2316 var ITEM_STR_ = '<input type="checkbox" value="{{value}}" class="dac-form-checkbox" id="{{id}}">' +
2317 '<label for="{{id}}" class="dac-form-checkbox-button"></label>' +
2318 '<label for="{{id}}" class="dac-form-label">{{name}}</label>';
2319
2320 /**
2321 * Template for a chip element.
2322 * @type {*|HTMLElement}
2323 * @private
2324 */
2325 var CHIP_BASE_ = $('<li class="dac-filter-chip">' +
2326 '<button class="dac-filter-chip-close">' +
2327 '<i class="dac-sprite dac-close-black dac-filter-chip-close-icon"></i>' +
2328 '</button>' +
2329 '</li>');
2330
2331 /**
2332 * Component to handle narrowing down resources.
2333 * @param {HTMLElement} el - The DOM element.
2334 * @param {Object} options
2335 * @constructor
2336 */
2337 function Filter(el, options) {
2338 this.el = $(el);
2339 this.options = $.extend({}, Filter.DEFAULTS_, options);
2340 this.init();
2341 }
2342
2343 Filter.DEFAULTS_ = {
2344 activeClass: 'dac-active',
2345 chipsDataAttr: 'filter-chips',
2346 nameDataAttr: 'filter-name',
2347 countDataAttr: 'filter-count',
2348 tabViewDataAttr: 'tab-view',
2349 valueDataAttr: 'filter-value'
2350 };
2351
2352 /**
2353 * Draw resource cards.
2354 * @param {Array} resources
2355 * @private
2356 */
2357 Filter.prototype.draw_ = function(resources) {
2358 var that = this;
2359
2360 if (resources.length === 0) {
2361 this.containerEl_.html('<p class="dac-filter-message">Nothing matches selected filters.</p>');
2362 return;
2363 }
2364
2365 // Draw resources.
2366 that.containerEl_.resourceWidget(resources, that.data_.options);
2367 };
2368
2369 /**
2370 * Initialize a Filter component.
2371 */
2372 Filter.prototype.init = function() {
2373 this.containerEl_ = $(this.options.filter);
2374
2375 // Setup data settings
2376 this.data_ = {};
2377 this.data_.chips = {};
2378 this.data_.options = this.containerEl_.widgetOptions();
2379 this.data_.all = window.metadata.query(this.data_.options);
2380
2381 // Initialize filter UI
2382 this.initUi();
2383 };
2384
2385 /**
2386 * Generate a chip for a given filter item.
2387 * @param {Object} item - A single filter option (checkbox container).
2388 * @returns {HTMLElement} A new Chip element.
2389 */
2390 Filter.prototype.chipForItem = function(item) {
2391 var chip = CHIP_BASE_.clone();
2392 chip.prepend(this.data_.chips[item.data('filter-value')]);
2393 chip.data('item.dac-filter', item);
2394 item.data('chip.dac-filter', chip);
2395 this.addToItemValue(item, 1);
2396 return chip[0];
2397 };
2398
2399 /**
2400 * Update count of checked filter items.
2401 * @param {Object} item - A single filter option (checkbox container).
2402 * @param {Number} value - Either -1 or 1.
2403 */
2404 Filter.prototype.addToItemValue = function(item, value) {
2405 var tab = item.parent().data(this.options.tabViewDataAttr);
2406 var countEl = this.countEl_.filter('[data-' + this.options.countDataAttr + '="' + tab + '"]');
2407 var count = value + parseInt(countEl.text(), 10);
2408 countEl.text(count);
2409 countEl.toggleClass('dac-disabled', count === 0);
2410 };
2411
2412 /**
2413 * Set event listeners.
2414 * @private
2415 */
2416 Filter.prototype.setEventListeners_ = function() {
2417 this.chipsEl_.on('click.dac-filter', '.dac-filter-chip-close', this.closeChipHandler_.bind(this));
2418 this.tabViewEl_.on('change.dac-filter', ':checkbox', this.toggleCheckboxHandler_.bind(this));
2419 };
2420
2421 /**
2422 * Check filter items that are active by default.
2423 */
2424 Filter.prototype.activateInitialFilters_ = function() {
2425 var id = (new Date()).getTime();
2426 var initiallyCheckedValues = this.data_.options.query.replace(/,\s*/g, '+').split('+');
2427 var chips = document.createDocumentFragment();
2428 var that = this;
2429
2430 this.items_.each(function(i) {
2431 var item = $(this);
2432 var opts = item.data();
2433 that.data_.chips[opts.filterValue] = opts.filterName;
2434
2435 var checkbox = $(ITEM_STR_.replace(/\{\{name\}\}/g, opts.filterName)
2436 .replace(/\{\{value\}\}/g, opts.filterValue)
2437 .replace(/\{\{id\}\}/g, 'filter-' + id + '-' + (i + 1)));
2438
2439 if (initiallyCheckedValues.indexOf(opts.filterValue) > -1) {
2440 checkbox[0].checked = true;
2441 chips.appendChild(that.chipForItem(item));
2442 }
2443
2444 item.append(checkbox);
2445 });
2446
2447 this.chipsEl_.append(chips);
2448 };
2449
2450 /**
2451 * Initialize the Filter view
2452 */
2453 Filter.prototype.initUi = function() {
2454 // Cache DOM elements
2455 this.chipsEl_ = this.el.find('[data-' + this.options.chipsDataAttr + ']');
2456 this.countEl_ = this.el.find('[data-' + this.options.countDataAttr + ']');
2457 this.tabViewEl_ = this.el.find('[data-' + this.options.tabViewDataAttr + ']');
2458 this.items_ = this.el.find('[data-' + this.options.nameDataAttr + ']');
2459
2460 // Setup UI
2461 this.draw_(this.data_.all);
2462 this.activateInitialFilters_();
2463 this.setEventListeners_();
2464 };
2465
2466 /**
2467 * @returns {[types|Array, tags|Array, category|Array]}
2468 */
2469 Filter.prototype.getActiveClauses = function() {
2470 var tags = [];
2471 var types = [];
2472 var categories = [];
2473
2474 this.items_.find(':checked').each(function(i, checkbox) {
2475 // Currently, there is implicit business logic here that `tag` is AND'ed together
2476 // while `type` is OR'ed. So , and + do the same thing here. It would be great to
2477 // reuse the same query engine for filters, but it would need more powerful syntax.
2478 // Probably parenthesis, to support "tag:dog + tag:cat + (type:video, type:blog)"
2479 var expression = $(checkbox).val();
2480 var regex = /(\w+):(\w+)/g;
2481 var match;
2482
2483 while (match = regex.exec(expression)) {
2484 switch (match[1]) {
2485 case 'category':
2486 categories.push(match[2]);
2487 break;
2488 case 'tag':
2489 tags.push(match[2]);
2490 break;
2491 case 'type':
2492 types.push(match[2]);
2493 break;
2494 }
2495 }
2496 });
2497
2498 return [types, tags, categories];
2499 };
2500
2501 /**
2502 * Actual filtering logic.
2503 * @returns {Array}
2504 */
2505 Filter.prototype.filteredResources = function() {
2506 var data = this.getActiveClauses();
2507 var types = data[0];
2508 var tags = data[1];
2509 var categories = data[2];
2510 var resources = [];
2511 var resource = {};
2512 var tag = '';
2513 var shouldAddResource = true;
2514
2515 for (var resourceIndex = 0; resourceIndex < this.data_.all.length; resourceIndex++) {
2516 resource = this.data_.all[resourceIndex];
2517 shouldAddResource = types.indexOf(resource.type) > -1;
2518
2519 if (categories && categories.length > 0) {
2520 shouldAddResource = shouldAddResource && categories.indexOf(resource.category) > -1;
2521 }
2522
2523 for (var tagIndex = 0; shouldAddResource && tagIndex < tags.length; tagIndex++) {
2524 tag = tags[tagIndex];
2525 shouldAddResource = resource.tags.indexOf(tag) > -1;
2526 }
2527
2528 if (shouldAddResource) {
2529 resources.push(resource);
2530 }
2531 }
2532
2533 return resources;
2534 };
2535
2536 /**
2537 * Close Chip Handler
2538 * @param {Event} event - Click event
2539 * @private
2540 */
2541 Filter.prototype.closeChipHandler_ = function(event) {
2542 var chip = $(event.currentTarget).parent();
2543 var checkbox = chip.data('item.dac-filter').find(':first-child')[0];
2544 checkbox.checked = false;
2545 this.changeStateForCheckbox(checkbox);
2546 };
2547
2548 /**
2549 * Handle filter item state change.
2550 * @param {Event} event - Change event
2551 * @private
2552 */
2553 Filter.prototype.toggleCheckboxHandler_ = function(event) {
2554 this.changeStateForCheckbox(event.currentTarget);
2555 };
2556
2557 /**
2558 * Redraw resource view based on new state.
2559 * @param checkbox
2560 */
2561 Filter.prototype.changeStateForCheckbox = function(checkbox) {
2562 var item = $(checkbox).parent();
2563
2564 if (checkbox.checked) {
2565 this.chipsEl_.append(this.chipForItem(item));
smain@google.comeeeb9b82016-08-29 18:12:27 -07002566 devsite.analytics.trackAnalyticsEvent('event',
2567 'Filters', 'Check', $(checkbox).val());
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002568 } else {
2569 item.data('chip.dac-filter').remove();
2570 this.addToItemValue(item, -1);
smain@google.comeeeb9b82016-08-29 18:12:27 -07002571 devsite.analytics.trackAnalyticsEvent('event',
2572 'Filters', 'Uncheck', $(checkbox).val());
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002573 }
2574
2575 this.draw_(this.filteredResources());
2576 };
2577
2578 /**
2579 * jQuery plugin
2580 */
2581 $.fn.dacFilter = function() {
2582 return this.each(function() {
2583 var el = $(this);
2584 new Filter(el, el.data());
2585 });
2586 };
2587
2588 /**
2589 * Data Attribute API
2590 */
2591 $(function() {
2592 $('[data-filter]').dacFilter();
2593 });
2594})(jQuery);
2595
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002596(function($) {
2597 'use strict';
2598
2599 /**
2600 * Toggle Floating Label state.
2601 * @param {HTMLElement} el - The DOM element.
2602 * @param options
2603 * @constructor
2604 */
2605 function FloatingLabel(el, options) {
2606 this.el = $(el);
2607 this.options = $.extend({}, FloatingLabel.DEFAULTS_, options);
2608 this.group = this.el.closest('.dac-form-input-group');
2609 this.input = this.group.find('.dac-form-input');
2610
2611 this.checkValue_ = this.checkValue_.bind(this);
2612 this.checkValue_();
2613
2614 this.input.on('focus', function() {
2615 this.group.addClass('dac-focused');
2616 }.bind(this));
2617 this.input.on('blur', function() {
2618 this.group.removeClass('dac-focused');
2619 this.checkValue_();
2620 }.bind(this));
2621 this.input.on('keyup', this.checkValue_);
2622 }
2623
2624 /**
2625 * The label is moved out of the textbox when it has a value.
2626 */
2627 FloatingLabel.prototype.checkValue_ = function() {
2628 if (this.input.val().length) {
2629 this.group.addClass('dac-has-value');
2630 } else {
2631 this.group.removeClass('dac-has-value');
2632 }
2633 };
2634
2635 /**
2636 * jQuery plugin
2637 * @param {object} options - Override default options.
2638 */
2639 $.fn.dacFloatingLabel = function(options) {
2640 return this.each(function() {
2641 new FloatingLabel(this, options);
2642 });
2643 };
2644
2645 $(document).on('ready.aranja', function() {
2646 $('.dac-form-floatlabel').each(function() {
2647 $(this).dacFloatingLabel($(this).data());
2648 });
2649 });
2650})(jQuery);
2651
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002652(function($) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002653 'use strict';
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002654
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002655 /**
2656 * @param {HTMLElement} el - The DOM element.
2657 * @param {Object} options
2658 * @constructor
2659 */
2660 function Crumbs(selected, options) {
2661 this.options = $.extend({}, Crumbs.DEFAULTS_, options);
2662 this.el = $(this.options.container);
2663
2664 // Do not build breadcrumbs for landing site.
2665 if (!selected || location.pathname === '/index.html' || location.pathname === '/') {
2666 return;
2667 }
2668
2669 // Cache navigation resources
2670 this.selected = $(selected);
2671 this.selectedParent = this.selected.closest('.dac-nav-secondary').siblings('a');
2672
2673 // Build the breadcrumb list.
2674 this.init();
2675 }
2676
2677 Crumbs.DEFAULTS_ = {
2678 container: '.dac-header-crumbs',
2679 crumbItem: $('<li class="dac-header-crumbs-item">'),
2680 linkClass: 'dac-header-crumbs-link'
2681 };
2682
2683 Crumbs.prototype.init = function() {
2684 Crumbs.buildCrumbForLink(this.selected.clone()).appendTo(this.el);
2685
2686 if (this.selectedParent.length) {
2687 Crumbs.buildCrumbForLink(this.selectedParent.clone()).prependTo(this.el);
2688 }
2689
2690 // Reveal the breadcrumbs
2691 this.el.addClass('dac-has-content');
2692 };
2693
2694 /**
2695 * Build a HTML structure for a breadcrumb.
2696 * @param {string} link
2697 * @return {jQuery}
2698 */
2699 Crumbs.buildCrumbForLink = function(link) {
2700 link.find('br').replaceWith(' ');
2701
2702 var crumbLink = $('<a>')
2703 .attr('class', Crumbs.DEFAULTS_.linkClass)
2704 .attr('href', link.attr('href'))
2705 .text(link.text());
2706
2707 return Crumbs.DEFAULTS_.crumbItem.clone().append(crumbLink);
2708 };
2709
2710 /**
2711 * jQuery plugin
2712 */
2713 $.fn.dacCrumbs = function(options) {
2714 return this.each(function() {
2715 new Crumbs(this, options);
2716 });
2717 };
2718})(jQuery);
2719
2720(function($) {
2721 'use strict';
2722
2723 /**
2724 * @param {HTMLElement} el - The DOM element.
2725 * @param {Object} options
2726 * @constructor
2727 */
2728 function SearchInput(el, options) {
Amanda Kassay4d5bcbf2016-05-13 12:24:39 -04002729 this.el = $(el);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002730 this.options = $.extend({}, SearchInput.DEFAULTS_, options);
2731 this.body = $('body');
2732 this.input = this.el.find('input');
2733 this.close = this.el.find(this.options.closeButton);
2734 this.clear = this.el.find(this.options.clearButton);
2735 this.icon = this.el.find('.' + this.options.iconClass);
2736 this.init();
2737 }
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08002738
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002739 SearchInput.DEFAULTS_ = {
2740 activeClass: 'dac-active',
2741 activeIconClass: 'dac-search',
2742 closeButton: '[data-search-close]',
2743 clearButton: '[data-search-clear]',
2744 hiddenClass: 'dac-hidden',
2745 iconClass: 'dac-header-search-icon',
2746 searchModeClass: 'dac-search-mode',
2747 transitionDuration: 250
2748 };
2749
2750 SearchInput.prototype.init = function() {
2751 this.input.on('focus.dac-search', this.setActiveState.bind(this))
2752 .on('input.dac-search', this.checkInputValue.bind(this));
2753 this.close.on('click.dac-search', this.unsetActiveStateHandler_.bind(this));
2754 this.clear.on('click.dac-search', this.clearInput.bind(this));
2755 };
2756
2757 SearchInput.prototype.setActiveState = function() {
2758 var that = this;
2759
2760 this.clear.addClass(this.options.hiddenClass);
2761 this.body.addClass(this.options.searchModeClass);
2762 this.checkInputValue();
2763
2764 // Set icon to black after background has faded to white.
2765 setTimeout(function() {
2766 that.icon.addClass(that.options.activeIconClass);
2767 }, this.options.transitionDuration);
2768 };
2769
2770 SearchInput.prototype.unsetActiveStateHandler_ = function(event) {
2771 event.preventDefault();
2772 this.unsetActiveState();
2773 };
2774
2775 SearchInput.prototype.unsetActiveState = function() {
2776 this.icon.removeClass(this.options.activeIconClass);
2777 this.clear.addClass(this.options.hiddenClass);
2778 this.body.removeClass(this.options.searchModeClass);
2779 };
2780
2781 SearchInput.prototype.clearInput = function(event) {
2782 event.preventDefault();
2783 this.input.val('');
2784 this.clear.addClass(this.options.hiddenClass);
2785 };
2786
2787 SearchInput.prototype.checkInputValue = function() {
2788 if (this.input.val().length) {
2789 this.clear.removeClass(this.options.hiddenClass);
2790 } else {
2791 this.clear.addClass(this.options.hiddenClass);
2792 }
2793 };
2794
2795 /**
2796 * jQuery plugin
2797 * @param {object} options - Override default options.
2798 */
2799 $.fn.dacSearchInput = function() {
2800 return this.each(function() {
2801 var el = $(this);
2802 el.data('search-input.dac', new SearchInput(el, el.data()));
2803 });
2804 };
2805
2806 /**
2807 * Data Attribute API
2808 */
2809 $(function() {
2810 $('[data-search]').dacSearchInput();
2811 });
2812})(jQuery);
2813
2814/* global METADATA */
2815(function($) {
2816 function DacCarouselQuery(el) {
2817 el = $(el);
2818
2819 var opts = el.data();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002820 opts.maxResults = parseInt(opts.maxResults || '100', 10);
2821 opts.query = opts.carouselQuery;
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002822 var resources = window.metadata.query(opts);
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002823
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002824 el.empty();
2825 $(resources).each(function() {
2826 var resource = $.extend({}, this, METADATA.carousel[this.url]);
2827 el.dacHero(resource);
2828 });
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002829
2830 // Pagination element.
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002831 el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002832
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00002833 el.dacCarousel();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002834 }
2835
2836 // jQuery plugin
2837 $.fn.dacCarouselQuery = function() {
2838 return this.each(function() {
2839 var el = $(this);
2840 var data = el.data('dac.carouselQuery');
2841
2842 if (!data) { el.data('dac.carouselQuery', (data = new DacCarouselQuery(el))); }
2843 });
2844 };
2845
2846 // Data API
2847 $(function() {
2848 $('[data-carousel-query]').dacCarouselQuery();
2849 });
2850})(jQuery);
2851
2852(function($) {
2853 /**
2854 * A CSS based carousel, inspired by SequenceJS.
2855 * @param {jQuery} el
2856 * @param {object} options
2857 * @constructor
2858 */
2859 function DacCarousel(el, options) {
2860 this.el = $(el);
2861 this.options = options = $.extend({}, DacCarousel.OPTIONS, this.el.data(), options || {});
2862 this.frames = this.el.find(options.frameSelector);
2863 this.count = this.frames.size();
2864 this.current = options.start;
2865
2866 this.initPagination();
2867 this.initEvents();
2868 this.initFrame();
2869 }
2870
2871 DacCarousel.OPTIONS = {
2872 auto: true,
2873 autoTime: 10000,
2874 autoMinTime: 5000,
2875 btnPrev: '[data-carousel-prev]',
2876 btnNext: '[data-carousel-next]',
2877 frameSelector: 'article',
2878 loop: true,
2879 start: 0,
2880 swipeThreshold: 160,
2881 pagination: '[data-carousel-pagination]'
2882 };
2883
2884 DacCarousel.prototype.initPagination = function() {
2885 this.pagination = $([]);
2886 if (!this.options.pagination) { return; }
2887
2888 var pagination = $('<ul class="dac-pagination">');
2889 var parent = this.el;
2890 if (typeof this.options.pagination === 'string') { parent = this.el.find(this.options.pagination); }
2891
2892 if (this.count > 1) {
2893 for (var i = 0; i < this.count; i++) {
2894 var li = $('<li class="dac-pagination-item">').text(i);
2895 if (i === this.options.start) { li.addClass('active'); }
2896 li.click(this.go.bind(this, i));
2897
2898 pagination.append(li);
2899 }
2900 this.pagination = pagination.children();
2901 parent.append(pagination);
2902 }
2903 };
2904
2905 DacCarousel.prototype.initEvents = function() {
2906 var that = this;
2907
2908 this.touch = {
2909 start: {x: 0, y: 0},
2910 end: {x: 0, y: 0}
2911 };
2912
2913 this.el.on('touchstart', this.touchstart_.bind(this));
2914 this.el.on('touchend', this.touchend_.bind(this));
2915 this.el.on('touchmove', this.touchmove_.bind(this));
2916
2917 this.el.hover(function() {
2918 that.pauseRotateTimer();
2919 }, function() {
2920 that.startRotateTimer();
2921 });
2922
2923 $(this.options.btnPrev).click(function(e) {
2924 e.preventDefault();
2925 that.prev();
2926 });
2927
2928 $(this.options.btnNext).click(function(e) {
2929 e.preventDefault();
2930 that.next();
2931 });
2932 };
2933
2934 DacCarousel.prototype.touchstart_ = function(event) {
2935 var t = event.originalEvent.touches[0];
2936 this.touch.start = {x: t.screenX, y: t.screenY};
2937 };
2938
2939 DacCarousel.prototype.touchend_ = function() {
2940 var deltaX = this.touch.end.x - this.touch.start.x;
2941 var deltaY = Math.abs(this.touch.end.y - this.touch.start.y);
2942 var shouldSwipe = (deltaY < Math.abs(deltaX)) && (Math.abs(deltaX) >= this.options.swipeThreshold);
2943
2944 if (shouldSwipe) {
2945 if (deltaX > 0) {
2946 this.prev();
2947 } else {
2948 this.next();
2949 }
2950 }
2951 };
2952
2953 DacCarousel.prototype.touchmove_ = function(event) {
2954 var t = event.originalEvent.touches[0];
2955 this.touch.end = {x: t.screenX, y: t.screenY};
2956 };
2957
2958 DacCarousel.prototype.initFrame = function() {
2959 this.frames.removeClass('active').eq(this.options.start).addClass('active');
2960 };
2961
2962 DacCarousel.prototype.startRotateTimer = function() {
2963 if (!this.options.auto || this.rotateTimer) { return; }
2964 this.rotateTimer = setTimeout(this.next.bind(this), this.options.autoTime);
2965 };
2966
2967 DacCarousel.prototype.pauseRotateTimer = function() {
2968 clearTimeout(this.rotateTimer);
2969 this.rotateTimer = null;
2970 };
2971
2972 DacCarousel.prototype.prev = function() {
2973 this.go(this.current - 1);
2974 };
2975
2976 DacCarousel.prototype.next = function() {
2977 this.go(this.current + 1);
2978 };
2979
2980 DacCarousel.prototype.go = function(next) {
2981 // Figure out what the next slide is.
2982 while (this.count > 0 && next >= this.count) { next -= this.count; }
2983 while (next < 0) { next += this.count; }
2984
2985 // Cancel if we're already on that slide.
2986 if (next === this.current) { return; }
2987
2988 // Prepare next slide.
2989 this.frames.eq(next).removeClass('out');
2990
2991 // Recalculate styles before starting slide transition.
2992 this.el.resolveStyles();
2993 // Update pagination
2994 this.pagination.removeClass('active').eq(next).addClass('active');
2995
2996 // Transition out current frame
2997 this.frames.eq(this.current).toggleClass('active out');
2998
2999 // Transition in a new frame
3000 this.frames.eq(next).toggleClass('active');
3001
3002 this.current = next;
3003 };
3004
3005 // Helper which resolves new styles for an element, so it can start transitioning
3006 // from the new values.
3007 $.fn.resolveStyles = function() {
3008 /*jshint expr:true*/
3009 this[0] && this[0].offsetTop;
3010 return this;
3011 };
3012
3013 // jQuery plugin
3014 $.fn.dacCarousel = function() {
3015 this.each(function() {
3016 var $el = $(this);
3017 $el.data('dac-carousel', new DacCarousel(this));
3018 });
3019 return this;
3020 };
3021
3022 // Data API
3023 $(function() {
3024 $('[data-carousel]').dacCarousel();
3025 });
3026})(jQuery);
3027
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00003028/* global toRoot */
3029
3030(function($) {
3031 // Ordering matters
3032 var TAG_MAP = [
3033 {from: 'developerstory', to: 'Android Developer Story'},
3034 {from: 'googleplay', to: 'Google Play'}
3035 ];
3036
3037 function DacHero(el, resource, isSearch) {
3038 var slide = $('<article>');
3039 slide.addClass(isSearch ? 'dac-search-hero' : 'dac-expand dac-hero');
3040 var image = cleanUrl(resource.heroImage || resource.image);
3041 var fullBleed = image && !resource.heroColor;
3042
3043 if (!isSearch) {
3044 // Configure background
3045 slide.css({
3046 backgroundImage: fullBleed ? 'url(' + image + ')' : '',
3047 backgroundColor: resource.heroColor || ''
3048 });
3049
3050 // Should copy be inverted
3051 slide.toggleClass('dac-invert', resource.heroInvert || fullBleed);
3052 slide.toggleClass('dac-darken', fullBleed);
3053
3054 // Should be clickable
3055 slide.append($('<a class="dac-hero-carousel-action">').attr('href', cleanUrl(resource.url)));
3056 }
3057
3058 var cols = $('<div class="cols dac-hero-content">');
3059
3060 // inline image column
3061 var rightCol = $('<div class="col-1of2 col-push-1of2 dac-hero-figure">')
3062 .appendTo(cols);
3063
3064 if ((!fullBleed || isSearch) && image) {
3065 rightCol.append($('<img>').attr('src', image));
3066 }
3067
3068 // info column
3069 $('<div class="col-1of2 col-pull-1of2">')
3070 .append($('<div class="dac-hero-tag">').text(formatTag(resource)))
3071 .append($('<h1 class="dac-hero-title">').text(formatTitle(resource)))
3072 .append($('<p class="dac-hero-description">').text(resource.summary))
3073 .append($('<a class="dac-hero-cta">')
3074 .text(formatCTA(resource))
3075 .attr('href', cleanUrl(resource.url))
3076 .prepend($('<span class="dac-sprite dac-auto-chevron">'))
3077 )
3078 .appendTo(cols);
3079
3080 slide.append(cols.wrap('<div class="wrap">').parent());
3081 el.append(slide);
3082 }
3083
3084 function cleanUrl(url) {
3085 if (url && url.indexOf('//') === -1) {
3086 url = toRoot + url;
3087 }
3088 return url;
3089 }
3090
3091 function formatTag(resource) {
3092 // Hmm, need a better more scalable solution for this.
3093 for (var i = 0, mapping; mapping = TAG_MAP[i]; i++) {
3094 if (resource.tags.indexOf(mapping.from) > -1) {
3095 return mapping.to;
3096 }
3097 }
3098 return resource.type;
3099 }
3100
3101 function formatTitle(resource) {
3102 return resource.title.replace(/android developer story: /i, '');
3103 }
3104
3105 function formatCTA(resource) {
3106 return resource.type === 'youtube' ? 'Watch the video' : 'Learn more';
3107 }
3108
3109 // jQuery plugin
3110 $.fn.dacHero = function(resource, isSearch) {
3111 return this.each(function() {
3112 var el = $(this);
3113 return new DacHero(el, resource, isSearch);
3114 });
3115 };
3116})(jQuery);
3117
3118(function($) {
3119 'use strict';
3120
3121 function highlightString(label, query) {
3122 query = query || '';
3123 //query = query.replace('<wbr>', '').replace('.', '\\.');
3124 var queryRE = new RegExp('(' + query + ')', 'ig');
3125 return label.replace(queryRE, '<em>$1</em>');
3126 }
3127
3128 $.fn.highlightMatches = function(query) {
3129 return this.each(function() {
3130 var el = $(this);
3131 var label = el.html();
3132 var highlighted = highlightString(label, query);
3133 el.html(highlighted);
3134 el.addClass('highlighted');
3135 });
3136 };
3137})(jQuery);
3138
3139/**
3140 * History tracking.
3141 * Track visited urls in localStorage.
3142 */
3143(function($) {
3144 var PAGES_TO_STORE_ = 100;
3145 var MIN_NUMBER_OF_PAGES_TO_DISPLAY_ = 6;
3146 var CONTAINER_SELECTOR_ = '.dac-search-results-history-wrap';
3147
3148 /**
3149 * Generate resource cards for visited pages.
3150 * @param {HTMLElement} el
3151 * @constructor
3152 */
3153 function HistoryQuery(el) {
3154 this.el = $(el);
3155
3156 // Only show history component if enough pages have been visited.
3157 if (getVisitedPages().length < MIN_NUMBER_OF_PAGES_TO_DISPLAY_) {
3158 this.el.closest(CONTAINER_SELECTOR_).addClass('dac-hidden');
3159 return;
3160 }
3161
3162 // Rename query
3163 this.el.data('query', this.el.data('history-query'));
3164
3165 // jQuery method to populate cards.
3166 this.el.resourceWidget();
3167 }
3168
3169 /**
3170 * Fetch from localStorage an array of visted pages
3171 * @returns {Array}
3172 */
3173 function getVisitedPages() {
3174 var visited = localStorage.getItem('visited-pages');
3175 return visited ? JSON.parse(visited) : [];
3176 }
3177
3178 /**
3179 * Return a page corresponding to cuurent pathname. If none exists, create one.
3180 * @param {Array} pages
3181 * @param {String} path
3182 * @returns {Object} Page
3183 */
3184 function getPageForPath(pages, path) {
3185 var page;
3186
3187 // Backwards lookup for current page, last pages most likely to be visited again.
3188 for (var i = pages.length - 1; i >= 0; i--) {
3189 if (pages[i].path === path) {
3190 page = pages[i];
3191
3192 // Remove page object from pages list to ensure correct ordering.
3193 pages.splice(i, 1);
3194
3195 return page;
3196 }
3197 }
3198
3199 // If storage limit is exceeded, remove last visited path.
3200 if (pages.length >= PAGES_TO_STORE_) {
3201 pages.shift();
3202 }
3203
3204 return {path: path};
3205 }
3206
3207 /**
3208 * Add current page to back of visited array, increase hit count by 1.
3209 */
3210 function addCurrectPage() {
3211 var path = location.pathname;
3212
3213 // Do not track frontpage visits.
3214 if (path === '/' || path === '/index.html') {return;}
3215
3216 var pages = getVisitedPages();
3217 var page = getPageForPath(pages, path);
3218
3219 // New page visits have no hit count.
3220 page.hit = ~~page.hit + 1;
3221
3222 // Most recently visted pages are located at the end of the visited array.
3223 pages.push(page);
3224
3225 localStorage.setItem('visited-pages', JSON.stringify(pages));
3226 }
3227
3228 /**
3229 * Hit count compare function.
3230 * @param {Object} a - page
3231 * @param {Object} b - page
3232 * @returns {number}
3233 */
3234 function byHit(a, b) {
3235 if (a.hit > b.hit) {
3236 return -1;
3237 } else if (a.hit < b.hit) {
3238 return 1;
3239 }
3240
3241 return 0;
3242 }
3243
3244 /**
3245 * Return a list of visited urls in a given order.
3246 * @param {String} order - (recent|most-visited)
3247 * @returns {Array}
3248 */
3249 $.dacGetVisitedUrls = function(order) {
3250 var pages = getVisitedPages();
3251
3252 if (order === 'recent') {
3253 pages.reverse();
3254 } else {
3255 pages.sort(byHit);
3256 }
3257
3258 return pages.map(function(page) {
3259 return page.path.replace(/^\//, '');
3260 });
3261 };
3262
3263 // jQuery plugin
3264 $.fn.dacHistoryQuery = function() {
3265 return this.each(function() {
3266 var el = $(this);
3267 var data = el.data('dac.recentlyVisited');
3268
3269 if (!data) {
3270 el.data('dac.recentlyVisited', (data = new HistoryQuery(el)));
3271 }
3272 });
3273 };
3274
3275 $(function() {
3276 $('[data-history-query]').dacHistoryQuery();
3277 // Do not block page rendering.
3278 setTimeout(addCurrectPage, 0);
3279 });
3280})(jQuery);
3281
3282/* ############################################ */
3283/* ########## LOCALIZATION ############ */
3284/* ############################################ */
3285/**
3286 * Global helpers.
3287 */
3288function getBaseUri(uri) {
3289 var intlUrl = (uri.substring(0, 6) === '/intl/');
3290 if (intlUrl) {
3291 var base = uri.substring(uri.indexOf('intl/') + 5, uri.length);
3292 base = base.substring(base.indexOf('/') + 1, base.length);
3293 return '/' + base;
3294 } else {
3295 return uri;
3296 }
3297}
3298
3299function changeLangPref(targetLang, submit) {
3300 window.writeCookie('pref_lang', targetLang, null);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00003301 $('#language').find('option[value="' + targetLang + '"]').attr('selected', true);
smain@google.comeeeb9b82016-08-29 18:12:27 -07003302 if (submit) {
3303 $('#setlang').submit();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00003304 }
3305}
3306// Redundant usage to appease jshint.
3307window.changeLangPref = changeLangPref;
3308
3309(function() {
3310 /**
3311 * Whitelisted locales. Should match choices in language dropdown. Repeated here
3312 * as a lot of i18n logic happens before page load and dropdown is ready.
3313 */
3314 var LANGUAGES = [
3315 'en',
3316 'es',
3317 'in',
3318 'ja',
3319 'ko',
3320 'pt-br',
3321 'ru',
3322 'vi',
3323 'zh-cn',
3324 'zh-tw'
3325 ];
3326
3327 /**
3328 * Master list of translated strings for template files.
3329 */
3330 var PHRASES = {
3331 'newsletter': {
3332 'title': 'Get the latest Android developer news and tips that will help you find success on Google Play.',
3333 'requiredHint': '* Required Fields',
3334 'name': 'Full name',
3335 'email': 'Email address',
3336 'company': 'Company / developer name',
3337 'appUrl': 'One of your Play Store app URLs',
3338 'business': {
3339 'label': 'Which best describes your business:',
3340 'apps': 'Apps',
3341 'games': 'Games',
3342 'both': 'Apps & Games'
3343 },
3344 'confirmMailingList': 'Add me to the mailing list for the monthly newsletter and occasional emails about ' +
3345 'development and Google Play opportunities.',
3346 'privacyPolicy': 'I acknowledge that the information provided in this form will be subject to Google\'s ' +
3347 '<a href="https://www.google.com/policies/privacy/" target="_blank">privacy policy</a>.',
3348 'languageVal': 'English',
3349 'successTitle': 'Hooray!',
3350 'successDetails': 'You have successfully signed up for the latest Android developer news and tips.',
3351 'languageValTarget': {
3352 'en': 'English',
3353 'ar': 'Arabic (العربيّة)',
3354 'in': 'Indonesian (Bahasa)',
3355 'fr': 'French (français)',
3356 'de': 'German (Deutsch)',
3357 'ja': 'Japanese (日本語)',
3358 'ko': 'Korean (한국어)',
3359 'ru': 'Russian (Русский)',
3360 'es': 'Spanish (español)',
3361 'th': 'Thai (ภาษาไทย)',
3362 'tr': 'Turkish (Türkçe)',
3363 'vi': 'Vietnamese (tiếng Việt)',
3364 'pt-br': 'Brazilian Portuguese (Português Brasileiro)',
3365 'zh-cn': 'Simplified Chinese (简体中文)',
3366 'zh-tw': 'Traditional Chinese (繁體中文)',
3367 },
3368 'resetLangTitle': "Browse this site in %{targetLang}?",
3369 'resetLangTextIntro': 'You requested a page in %{targetLang}, but your language preference for this site is %{lang}.',
3370 'resetLangTextCta': 'Would you like to change your language preference and browse this site in %{targetLang}? ' +
3371 'If you want to change your language preference later, use the language menu at the bottom of each page.',
3372 'resetLangButtonYes': 'Change Language',
3373 'resetLangButtonNo': 'Not Now'
3374 }
3375 };
3376
3377 /**
3378 * Current locale.
3379 */
3380 var locale = (function() {
3381 var lang = window.readCookie('pref_lang');
3382 if (lang === 0 || LANGUAGES.indexOf(lang) === -1) {
3383 lang = 'en';
3384 }
3385 return lang;
3386 })();
3387 var localeTarget = (function() {
smain@google.comeeeb9b82016-08-29 18:12:27 -07003388 var lang = getQueryVariable('hl');
3389 if (lang === false || LANGUAGES.indexOf(lang) === -1) {
3390 lang = locale;
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00003391 }
smain@google.comeeeb9b82016-08-29 18:12:27 -07003392 return lang;
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00003393 })();
3394
3395 /**
3396 * Global function shims for backwards compatibility
3397 */
3398 window.changeNavLang = function() {
3399 // Already done.
3400 };
3401
3402 window.loadLangPref = function() {
3403 // Languages pref already loaded.
3404 };
3405
3406 window.getLangPref = function() {
3407 return locale;
3408 };
3409
3410 window.getLangTarget = function() {
3411 return localeTarget;
3412 };
3413
3414 // Expose polyglot instance for advanced localization.
3415 var polyglot = window.polyglot = new window.Polyglot({
3416 locale: locale,
3417 phrases: PHRASES
3418 });
3419
3420 // When DOM is ready.
3421 $(function() {
3422 // Mark current locale in language picker.
3423 $('#language').find('option[value="' + locale + '"]').attr('selected', true);
3424
3425 $('html').dacTranslate().on('dac:domchange', function(e) {
3426 $(e.target).dacTranslate();
3427 });
3428 });
3429
3430 $.fn.dacTranslate = function() {
3431 // Translate strings in template markup:
3432
3433 // OLD
3434 // Having all translations in HTML does not scale well and bloats every page.
3435 // Need to migrate this to data-l JS translations below.
3436 if (locale !== 'en') {
3437 var $links = this.find('a[' + locale + '-lang]');
3438 $links.each(function() { // for each link with a translation
3439 var $link = $(this);
3440 // put the desired language from the attribute as the text
3441 $link.text($link.attr(locale + '-lang'));
3442 });
3443 }
3444
3445 // NEW
3446 // A simple declarative api for JS translations. Feel free to extend as appropriate.
3447
3448 // Miscellaneous string compilations
3449 // Build full strings from localized substrings:
3450 var myLocaleTarget = window.getLangTarget();
3451 var myTargetLang = window.polyglot.t("newsletter.languageValTarget." + myLocaleTarget);
3452 var myLang = window.polyglot.t("newsletter.languageVal");
3453 var myTargetLangTitleString = window.polyglot.t("newsletter.resetLangTitle", {targetLang: myTargetLang});
3454 var myResetLangTextIntro = window.polyglot.t("newsletter.resetLangTextIntro", {targetLang: myTargetLang, lang: myLang});
3455 var myResetLangTextCta = window.polyglot.t("newsletter.resetLangTextCta", {targetLang: myTargetLang});
3456 //var myResetLangButtonYes = window.polyglot.t("newsletter.resetLangButtonYes", {targetLang: myTargetLang});
3457
3458 // Inject strings as text values in dialog components:
3459 $("#langform .dac-modal-header-title").text(myTargetLangTitleString);
3460 $("#langform #resetLangText").text(myResetLangTextIntro);
3461 $("#langform #resetLangCta").text(myResetLangTextCta);
3462 //$("#resetLangButtonYes").attr("data-t", window.polyglot.t(myResetLangButtonYes));
3463
3464 // Text: <div data-t="nav.home"></div>
3465 // HTML: <div data-t="privacy" data-t-html></html>
3466 this.find('[data-t]').each(function() {
3467 var el = $(this);
3468 var data = el.data();
3469 if (data.t) {
3470 el[data.tHtml === '' ? 'html' : 'text'](polyglot.t(data.t));
3471 }
3472 });
3473
3474 return this;
3475 };
3476})();
3477/* ########## END LOCALIZATION ############ */
3478
3479// Translations. These should eventually be moved into language-specific files and loaded on demand.
3480// jshint nonbsp:false
3481switch (window.getLangPref()) {
3482 case 'ar':
3483 window.polyglot.extend({
3484 'newsletter': {
3485 'title': 'Google Play. يمكنك الحصول على آخر الأخبار والنصائح من مطوّري تطبيقات Android، مما يساعدك ' +
3486 'على تحقيق النجاح على',
3487 'requiredHint': '* حقول مطلوبة',
3488 'name': '. الاسم بالكامل ',
3489 'email': '. عنوان البريد الإلكتروني ',
3490 'company': '. اسم الشركة / اسم مطوّر البرامج',
3491 'appUrl': '. أحد عناوين URL لتطبيقاتك في متجر Play',
3492 'business': {
3493 'label': '. ما العنصر الذي يوضح طبيعة نشاطك التجاري بدقة؟ ',
3494 'apps': 'التطبيقات',
3495 'games': 'الألعاب',
3496 'both': 'التطبيقات والألعاب'
3497 },
3498 'confirmMailingList': 'إضافتي إلى القائمة البريدية للنشرة الإخبارية الشهرية والرسائل الإلكترونية التي يتم' +
3499 ' إرسالها من حين لآخر بشأن التطوير وفرص Google Play.',
3500 'privacyPolicy': 'أقر بأن المعلومات المقدَّمة في هذا النموذج تخضع لسياسة خصوصية ' +
3501 '<a href="https://www.google.com/intl/ar/policies/privacy/" target="_blank">Google</a>.',
3502 'languageVal': 'Arabic (العربيّة)',
3503 'successTitle': 'رائع!',
3504 'successDetails': 'لقد اشتركت بنجاح للحصول على آخر الأخبار والنصائح من مطوّري برامج Android.'
3505 }
3506 });
3507 break;
3508 case 'zh-cn':
3509 window.polyglot.extend({
3510 'newsletter': {
3511 'title': '获取最新的 Android 开发者资讯和提示,助您在 Google Play 上取得成功。',
3512 'requiredHint': '* 必填字段',
3513 'name': '全名',
3514 'email': '电子邮件地址',
3515 'company': '公司/开发者名称',
3516 'appUrl': '您的某个 Play 商店应用网址',
3517 'business': {
3518 'label': '哪一项能够最准确地描述您的业务?',
3519 'apps': '应用',
3520 'games': '游戏',
3521 'both': '应用和游戏'
3522 },
3523 'confirmMailingList': '将我添加到邮寄名单,以便接收每月简报以及不定期发送的关于开发和 Google Play 商机的电子邮件。',
3524 'privacyPolicy': '我确认自己了解在此表单中提供的信息受 <a href="https://www.google.com/intl/zh-CN/' +
3525 'policies/privacy/" target="_blank">Google</a> 隐私权政策的约束。',
3526 'languageVal': 'Simplified Chinese (简体中文)',
3527 'successTitle': '太棒了!',
3528 'successDetails': '您已成功订阅最新的 Android 开发者资讯和提示。'
3529 }
3530 });
3531 break;
3532 case 'zh-tw':
3533 window.polyglot.extend({
3534 'newsletter': {
3535 'title': '獲得 Android 開發人員的最新消息和各項秘訣,讓您在 Google Play 上輕鬆邁向成功之路。',
3536 'requiredHint': '* 必要欄位',
3537 'name': '全名',
3538 'email': '電子郵件地址',
3539 'company': '公司/開發人員名稱',
3540 'appUrl': '您其中一個 Play 商店應用程式的網址',
3541 'business': {
3542 'label': '為您的商家選取最合適的產品類別。',
3543 'apps': '應用程式',
3544 'games': '遊戲',
3545 'both': '應用程式和遊戲'
3546 },
3547 'confirmMailingList': '我想加入 Google Play 的郵寄清單,以便接收每月電子報和 Google Play 不定期寄送的電子郵件,' +
3548 '瞭解關於開發和 Google Play 商機的資訊。',
3549 'privacyPolicy': '我瞭解,我在這張表單中提供的資訊將受到 <a href="' +
3550 'https://www.google.com/intl/zh-TW/policies/privacy/" target="_blank">Google</a> 隱私權政策.',
3551 'languageVal': 'Traditional Chinese (繁體中文)',
3552 'successTitle': '太棒了!',
3553 'successDetails': '您已經成功訂閱 Android 開發人員的最新消息和各項秘訣。'
3554 }
3555 });
3556 break;
3557 case 'fr':
3558 window.polyglot.extend({
3559 'newsletter': {
3560 'title': 'Recevez les dernières actualités destinées aux développeurs Android, ainsi que des conseils qui ' +
3561 'vous mèneront vers le succès sur Google Play.',
3562 'requiredHint': '* Champs obligatoires',
3563 'name': 'Nom complet',
3564 'email': 'Adresse e-mail',
3565 'company': 'Nom de la société ou du développeur',
3566 'appUrl': 'Une de vos URL Play Store',
3567 'business': {
3568 'label': 'Quelle option décrit le mieux votre activité ?',
3569 'apps': 'Applications',
3570 'games': 'Jeux',
3571 'both': 'Applications et jeux'
3572 },
3573 'confirmMailingList': 'Ajoutez-moi à la liste de diffusion de la newsletter mensuelle et tenez-moi informé ' +
3574 'par des e-mails occasionnels de l\'évolution et des opportunités de Google Play.',
3575 'privacyPolicy': 'Je comprends que les renseignements fournis dans ce formulaire seront soumis aux <a href="' +
3576 'https://www.google.com/intl/fr/policies/privacy/" target="_blank">règles de confidentialité</a> de Google.',
3577 'languageVal': 'French (français)',
3578 'successTitle': 'Super !',
3579 'successDetails': 'Vous êtes bien inscrit pour recevoir les actualités et les conseils destinés aux ' +
3580 'développeurs Android.'
3581 }
3582 });
3583 break;
3584 case 'de':
3585 window.polyglot.extend({
3586 'newsletter': {
3587 'title': 'Abonniere aktuelle Informationen und Tipps für Android-Entwickler und werde noch erfolgreicher ' +
3588 'bei Google Play.',
3589 'requiredHint': '* Pflichtfelder',
3590 'name': 'Vollständiger Name',
3591 'email': 'E-Mail-Adresse',
3592 'company': 'Unternehmens-/Entwicklername',
3593 'appUrl': 'Eine der URLs deiner Play Store App',
3594 'business': {
3595 'label': 'Welche der folgenden Kategorien beschreibt dein Unternehmen am besten?',
3596 'apps': 'Apps',
3597 'games': 'Spiele',
3598 'both': 'Apps und Spiele'
3599 },
3600 'confirmMailingList': 'Meine E-Mail-Adresse soll zur Mailingliste hinzugefügt werden, damit ich den ' +
3601 'monatlichen Newsletter sowie gelegentlich E-Mails zu Entwicklungen und Optionen bei Google Play erhalte.',
3602 'privacyPolicy': 'Ich bestätige, dass die in diesem Formular bereitgestellten Informationen gemäß der ' +
3603 '<a href="https://www.google.com/intl/de/policies/privacy/" target="_blank">Datenschutzerklärung</a> von ' +
3604 'Google verwendet werden dürfen.',
3605 'languageVal': 'German (Deutsch)',
3606 'successTitle': 'Super!',
3607 'successDetails': 'Du hast dich erfolgreich angemeldet und erhältst jetzt aktuelle Informationen und Tipps ' +
3608 'für Android-Entwickler.'
3609 }
3610 });
3611 break;
3612 case 'in':
3613 window.polyglot.extend({
3614 'newsletter': {
3615 'title': 'Receba as dicas e as notícias mais recentes para os desenvolvedores Android e seja bem-sucedido ' +
3616 'no Google Play.',
3617 'requiredHint': '* Bidang Wajib Diisi',
3618 'name': 'Nama lengkap',
3619 'email': 'Alamat email',
3620 'company': 'Nama pengembang / perusahaan',
3621 'appUrl': 'Salah satu URL aplikasi Play Store Anda',
3622 'business': {
3623 'label': 'Dari berikut ini, mana yang paling cocok dengan bisnis Anda?',
3624 'apps': 'Aplikasi',
3625 'games': 'Game',
3626 'both': 'Aplikasi dan Game'
3627 },
3628 'confirmMailingList': 'Tambahkan saya ke milis untuk mendapatkan buletin bulanan dan email sesekali mengenai ' +
3629 'perkembangan dan kesempatan yang ada di Google Play.',
3630 'privacyPolicy': 'Saya memahami bahwa informasi yang diberikan dalam formulir ini tunduk pada <a href="' +
3631 'https://www.google.com/intl/in/policies/privacy/" target="_blank">kebijakan privasi</a> Google.',
3632 'languageVal': 'Indonesian (Bahasa)',
3633 'successTitle': 'Hore!',
3634 'successDetails': 'Anda berhasil mendaftar untuk kiat dan berita pengembang Android terbaru.'
3635 }
3636 });
3637 break;
3638 case 'it':
3639 //window.polyglot.extend({
3640 // 'newsletter': {
3641 // 'title': 'Receba as dicas e as notícias mais recentes para os desenvolvedores Android e seja bem-sucedido ' +
3642 // 'no Google Play.',
3643 // 'requiredHint': '* Campos obrigatórios',
3644 // 'name': 'Nome completo',
3645 // 'email': 'Endereço de Email',
3646 // 'company': 'Nome da empresa / do desenvolvedor',
3647 // 'appUrl': 'URL de um dos seus apps da Play Store',
3648 // 'business': {
3649 // 'label': 'Qual das seguintes opções melhor descreve sua empresa?',
3650 // 'apps': 'Apps',
3651 // 'games': 'Jogos',
3652 // 'both': 'Apps e Jogos'
3653 // },
3654 // 'confirmMailingList': 'Inscreva-me na lista de e-mails para que eu receba o boletim informativo mensal, ' +
3655 // 'bem como e-mails ocasionais sobre o desenvolvimento e as oportunidades do Google Play.',
3656 // 'privacyPolicy': 'Reconheço que as informações fornecidas neste formulário estão sujeitas à <a href="' +
3657 // 'https://www.google.com.br/policies/privacy/" target="_blank">Política de Privacidade</a> do Google.',
3658 // 'languageVal': 'Italian (italiano)',
3659 // 'successTitle': 'Uhu!',
3660 // 'successDetails': 'Você se inscreveu para receber as notícias e as dicas mais recentes para os ' +
3661 // 'desenvolvedores Android.',
3662 // }
3663 //});
3664 break;
3665 case 'ja':
3666 window.polyglot.extend({
3667 'newsletter': {
3668 'title': 'Google Play での成功に役立つ Android デベロッパー向けの最新ニュースやおすすめの情報をお届けします。',
3669 'requiredHint': '* 必須',
3670 'name': '氏名',
3671 'email': 'メールアドレス',
3672 'company': '会社名 / デベロッパー名',
3673 'appUrl': 'Play ストア アプリの URL(いずれか 1 つ)',
3674 'business': {
3675 'label': 'お客様のビジネスに最もよく当てはまるものをお選びください。',
3676 'apps': 'アプリ',
3677 'games': 'ゲーム',
3678 'both': 'アプリとゲーム'
3679 },
3680 'confirmMailingList': '開発や Google Play の最新情報に関する毎月発行のニュースレターや不定期発行のメールを受け取る',
3681 'privacyPolicy': 'このフォームに入力した情報に <a href="https://www.google.com/intl/ja/policies/privacy/" ' +
3682 'target="_blank">Google</a> のプライバシー ポリシーが適用',
3683 'languageVal': 'Japanese (日本語)',
3684 'successTitle': '完了です!',
3685 'successDetails': 'Android デベロッパー向けの最新ニュースやおすすめの情報の配信登録が完了しました。'
3686 }
3687 });
3688 break;
3689 case 'ko':
3690 window.polyglot.extend({
3691 'newsletter': {
3692 'title': 'Google Play에서 성공을 거두는 데 도움이 되는 최신 Android 개발자 소식 및 도움말을 받아 보세요.',
3693 'requiredHint': '* 필수 입력란',
3694 'name': '이름',
3695 'email': '이메일 주소',
3696 'company': '회사/개발자 이름',
3697 'appUrl': 'Play 스토어 앱 URL 중 1개',
3698 'business': {
3699 'label': '다음 중 내 비즈니스를 가장 잘 설명하는 단어는 무엇인가요?',
3700 'apps': '앱',
3701 'games': '게임',
3702 'both': '앱 및 게임'
3703 },
3704 'confirmMailingList': '개발 및 Google Play 관련 소식에 관한 월별 뉴스레터 및 비정기 이메일을 받아보겠습니다.',
3705 'privacyPolicy': '이 양식에 제공한 정보는 <a href="https://www.google.com/intl/ko/policies/privacy/" ' +
3706 'target="_blank">Google의</a> 개인정보취급방침에 따라 사용됨을',
3707 'languageVal':'Korean (한국어)',
3708 'successTitle': '축하합니다!',
3709 'successDetails': '최신 Android 개발자 뉴스 및 도움말을 받아볼 수 있도록 가입을 완료했습니다.'
3710 }
3711 });
3712 break;
3713 case 'pt-br':
3714 window.polyglot.extend({
3715 'newsletter': {
3716 'title': 'Receba as dicas e as notícias mais recentes para os desenvolvedores Android e seja bem-sucedido ' +
3717 'no Google Play.',
3718 'requiredHint': '* Campos obrigatórios',
3719 'name': 'Nome completo',
3720 'email': 'Endereço de Email',
3721 'company': 'Nome da empresa / do desenvolvedor',
3722 'appUrl': 'URL de um dos seus apps da Play Store',
3723 'business': {
3724 'label': 'Qual das seguintes opções melhor descreve sua empresa?',
3725 'apps': 'Apps',
3726 'games': 'Jogos',
3727 'both': 'Apps e Jogos'
3728 },
3729 'confirmMailingList': 'Inscreva-me na lista de e-mails para que eu receba o boletim informativo mensal, ' +
3730 'bem como e-mails ocasionais sobre o desenvolvimento e as oportunidades do Google Play.',
3731 'privacyPolicy': 'Reconheço que as informações fornecidas neste formulário estão sujeitas à <a href="' +
3732 'https://www.google.com.br/policies/privacy/" target="_blank">Política de Privacidade</a> do Google.',
3733 'languageVal': 'Brazilian Portuguese (Português Brasileiro)',
3734 'successTitle': 'Uhu!',
3735 'successDetails': 'Você se inscreveu para receber as notícias e as dicas mais recentes para os ' +
3736 'desenvolvedores Android.'
3737 }
3738 });
3739 break;
3740 case 'ru':
3741 window.polyglot.extend({
3742 'newsletter': {
3743 'title': 'Хотите получать последние новости и советы для разработчиков Google Play? Заполните эту форму.',
3744 'requiredHint': '* Обязательные поля',
3745 'name': 'Полное имя',
3746 'email': 'Адрес электронной почты',
3747 'company': 'Название компании или имя разработчика',
3748 'appUrl': 'Ссылка на любое ваше приложение в Google Play',
3749 'business': {
3750 'label': 'Что вы создаете?',
3751 'apps': 'Приложения',
3752 'games': 'Игры',
3753 'both': 'Игры и приложения'
3754 },
3755 'confirmMailingList': 'Я хочу получать ежемесячную рассылку для разработчиков и другие полезные новости ' +
3756 'Google Play.',
3757 'privacyPolicy': 'Я предоставляю эти данные в соответствии с <a href="' +
3758 'https://www.google.com/intl/ru/policies/privacy/" target="_blank">Политикой конфиденциальности</a> Google.',
3759 'languageVal': 'Russian (Русский)',
3760 'successTitle': 'Поздравляем!',
3761 'successDetails': 'Теперь вы подписаны на последние новости и советы для разработчиков Android.'
3762 }
3763 });
3764 break;
3765 case 'es':
3766 window.polyglot.extend({
3767 'newsletter': {
3768 'title': 'Recibe las últimas noticias y sugerencias para programadores de Android y logra tener éxito en ' +
3769 'Google Play.',
3770 'requiredHint': '* Campos obligatorios',
3771 'name': 'Dirección de correo electrónico',
3772 'email': 'Endereço de Email',
3773 'company': 'Nombre de la empresa o del programador',
3774 'appUrl': 'URL de una de tus aplicaciones de Play Store',
3775 'business': {
3776 'label': '¿Qué describe mejor a tu empresa?',
3777 'apps': 'Aplicaciones',
3778 'games': 'Juegos',
3779 'both': 'Juegos y aplicaciones'
3780 },
3781 'confirmMailingList': 'Deseo unirme a la lista de distribución para recibir el boletín informativo mensual ' +
3782 'y correos electrónicos ocasionales sobre desarrollo y oportunidades de Google Play.',
3783 'privacyPolicy': 'Acepto que la información que proporcioné en este formulario cumple con la <a href="' +
3784 'https://www.google.com/intl/es/policies/privacy/" target="_blank">política de privacidad</a> de Google.',
3785 'languageVal': 'Spanish (español)',
3786 'successTitle': '¡Felicitaciones!',
3787 'successDetails': 'El registro para recibir las últimas noticias y sugerencias para programadores de Android ' +
3788 'se realizó correctamente.'
3789 }
3790 });
3791 break;
3792 case 'th':
3793 window.polyglot.extend({
3794 'newsletter': {
3795 'title': 'รับข่าวสารล่าสุดสำหรับนักพัฒนาซอฟต์แวร์ Android ตลอดจนเคล็ดลับที่จะช่วยให้คุณประสบความสำเร็จบน ' +
3796 'Google Play',
3797 'requiredHint': '* ช่องที่ต้องกรอก',
3798 'name': 'ชื่อและนามสกุล',
3799 'email': 'ที่อยู่อีเมล',
3800 'company': 'ชื่อบริษัท/นักพัฒนาซอฟต์แวร์',
3801 'appUrl': 'URL แอปใดแอปหนึ่งของคุณใน Play สโตร์',
3802 'business': {
3803 'label': 'ข้อใดตรงกับธุรกิจของคุณมากที่สุด',
3804 'apps': 'แอป',
3805 'games': 'เกม',
3806 'both': 'แอปและเกม'
3807 },
3808 'confirmMailingList': 'เพิ่มฉันลงในรายชื่ออีเมลเพื่อรับจดหมายข่าวรายเดือนและอีเมลเป็นครั้งคราวเกี่ยวกับก' +
3809 'ารพัฒนาซอฟต์แวร์และโอกาสใน Google Play',
3810 'privacyPolicy': 'ฉันรับทราบว่าข้อมูลที่ให้ไว้ในแบบฟอร์มนี้จะเป็นไปตามนโยบายส่วนบุคคลของ ' +
3811 '<a href="https://www.google.com/intl/th/policies/privacy/" target="_blank">Google</a>',
3812 'languageVal': 'Thai (ภาษาไทย)',
3813 'successTitle': 'ไชโย!',
3814 'successDetails': 'คุณลงชื่อสมัครรับข่าวสารและเคล็ดลับล่าสุดสำหรับนักพัฒนาซอฟต์แวร์ Android เสร็จเรียบร้อยแล้ว'
3815 }
3816 });
3817 break;
3818 case 'tr':
3819 window.polyglot.extend({
3820 'newsletter': {
3821 'title': 'Google Play\'de başarılı olmanıza yardımcı olacak en son Android geliştirici haberleri ve ipuçları.',
3822 'requiredHint': '* Zorunlu Alanlar',
3823 'name': 'Tam ad',
3824 'email': 'E-posta adresi',
3825 'company': 'Şirket / geliştirici adı',
3826 'appUrl': 'Play Store uygulama URL\'lerinizden biri',
3827 'business': {
3828 'label': 'İşletmenizi en iyi hangisi tanımlar?',
3829 'apps': 'Uygulamalar',
3830 'games': 'Oyunlar',
3831 'both': 'Uygulamalar ve Oyunlar'
3832 },
3833 'confirmMailingList': 'Beni, geliştirme ve Google Play fırsatlarıyla ilgili ara sıra gönderilen e-posta ' +
3834 'iletilerine ilişkin posta listesine ve aylık haber bültenine ekle.',
3835 'privacyPolicy': 'Bu formda sağlanan bilgilerin Google\'ın ' +
3836 '<a href="https://www.google.com/intl/tr/policies/privacy/" target="_blank">Gizlilik Politikası\'na</a> ' +
3837 'tabi olacağını kabul ediyorum.',
3838 'languageVal': 'Turkish (Türkçe)',
3839 'successTitle': 'Yaşasın!',
3840 'successDetails': 'En son Android geliştirici haberleri ve ipuçlarına başarıyla kaydoldunuz.'
3841 }
3842 });
3843 break;
3844 case 'vi':
3845 window.polyglot.extend({
3846 'newsletter': {
3847 'title': 'Nhận tin tức và mẹo mới nhất dành cho nhà phát triển Android sẽ giúp bạn tìm thấy thành công trên ' +
3848 'Google Play.',
3849 'requiredHint': '* Các trường bắt buộc',
3850 'name': 'Tên đầy đủ',
3851 'email': 'Địa chỉ email',
3852 'company': 'Tên công ty/nhà phát triển',
3853 'appUrl': 'Một trong số các URL ứng dụng trên cửa hàng Play của bạn',
3854 'business': {
3855 'label': 'Lựa chọn nào sau đây mô tả chính xác nhất doanh nghiệp của bạn?',
3856 'apps': 'Ứng dụng',
3857 'games': 'Trò chơi',
3858 'both': 'Ứng dụng và trò chơi'
3859 },
3860 'confirmMailingList': 'Thêm tôi vào danh sách gửi thư cho bản tin hàng tháng và email định kỳ về việc phát ' +
3861 'triển và cơ hội của Google Play.',
3862 'privacyPolicy': 'Tôi xác nhận rằng thông tin được cung cấp trong biểu mẫu này tuân thủ chính sách bảo mật ' +
3863 'của <a href="https://www.google.com/intl/vi/policies/privacy/" target="_blank">Google</a>.',
3864 'languageVal': 'Vietnamese (tiếng Việt)',
3865 'successTitle': 'Thật tuyệt!',
3866 'successDetails': 'Bạn đã đăng ký thành công nhận tin tức và mẹo mới nhất dành cho nhà phát triển của Android.'
3867 }
3868 });
3869 break;
3870}
3871
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003872(function($) {
3873 'use strict';
3874
3875 function Modal(el, options) {
3876 this.el = $(el);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00003877 this.options = $.extend({}, options);
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003878 this.isOpen = false;
3879
3880 this.el.on('click', function(event) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00003881 if (!$.contains(this.el.find('.dac-modal-window')[0], event.target)) {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003882 return this.el.trigger('modal-close');
3883 }
3884 }.bind(this));
3885
3886 this.el.on('modal-open', this.open_.bind(this));
3887 this.el.on('modal-close', this.close_.bind(this));
3888 this.el.on('modal-toggle', this.toggle_.bind(this));
3889 }
3890
3891 Modal.prototype.toggle_ = function() {
3892 this.el.trigger('modal-' + (this.isOpen ? 'close' : 'open'));
3893 };
3894
3895 Modal.prototype.close_ = function() {
smain@google.comeeeb9b82016-08-29 18:12:27 -07003896 // When closing the modal for Android Studio downloads, reload the page
3897 // because otherwise we might get stuck with post-download dialog state
3898 if ($("[data-modal='studio_tos'].dac-active").length) {
3899 location.reload();
3900 }
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003901 this.el.removeClass('dac-active');
3902 $('body').removeClass('dac-modal-open');
3903 this.isOpen = false;
3904 };
3905
3906 Modal.prototype.open_ = function() {
3907 this.el.addClass('dac-active');
3908 $('body').addClass('dac-modal-open');
3909 this.isOpen = true;
3910 };
3911
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00003912 function onClickToggleModal(event) {
Amanda Kassay4d5bcbf2016-05-13 12:24:39 -04003913 event.preventDefault();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00003914 var toggle = $(event.currentTarget);
3915 var options = toggle.data();
3916 var modal = options.modalToggle ? $('[data-modal="' + options.modalToggle + '"]') :
3917 toggle.closest('[data-modal]');
3918 modal.trigger('modal-toggle');
3919 }
Amanda Kassay4d5bcbf2016-05-13 12:24:39 -04003920
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003921 /**
3922 * jQuery plugin
3923 * @param {object} options - Override default options.
3924 */
3925 $.fn.dacModal = function(options) {
3926 return this.each(function() {
3927 new Modal(this, options);
3928 });
3929 };
3930
3931 $.fn.dacToggleModal = function(options) {
3932 return this.each(function() {
3933 new ToggleModal(this, options);
3934 });
3935 };
3936
3937 /**
3938 * Data Attribute API
3939 */
3940 $(document).on('ready.aranja', function() {
3941 $('[data-modal]').each(function() {
3942 $(this).dacModal($(this).data());
3943 });
3944
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00003945 $('html').on('click.modal', '[data-modal-toggle]', onClickToggleModal);
3946
3947 // Check if url anchor is targetting a toggle to open the modal.
3948 if (location.hash) {
smain@google.comeeeb9b82016-08-29 18:12:27 -07003949 var $elem = $(document.getElementById(location.hash.substr(1)));
3950 if ($elem.attr('data-modal-toggle')) {
3951 $elem.trigger('click');
3952 }
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00003953 }
3954
smain@google.comeeeb9b82016-08-29 18:12:27 -07003955 var isTargetLangValid = false;
3956 $(ANDROID_LANGUAGES).each(function(index, langCode) {
3957 if (langCode == window.getLangTarget()) {
3958 isTargetLangValid = true;
3959 return;
3960 }
3961 });
3962 if (window.getLangTarget() !== window.getLangPref() && isTargetLangValid) {
3963 $('#langform').trigger('modal-open');
3964 $("#langform button.yes").attr("onclick","window.changeLangPref('" + window.getLangTarget() + "', true); return false;");
3965 $("#langform button.no").attr("onclick","window.changeLangPref('" + window.getLangPref() + "', true); return false;");
3966 }
3967
3968 /* Check the current API level, but only if we're in the reference */
3969 if (location.pathname.indexOf('/reference') == 0) {
3970 // init available apis based on user pref
3971 changeApiLevel();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00003972 }
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003973 });
3974})(jQuery);
3975
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00003976/* Fullscreen - Toggle fullscreen mode for reference pages */
3977(function($) {
3978 'use strict';
3979
3980 /**
3981 * @param {HTMLElement} el - The DOM element.
3982 * @constructor
3983 */
3984 function Fullscreen(el) {
3985 this.el = $(el);
3986 this.html = $('html');
3987 this.icon = this.el.find('.dac-sprite');
3988 this.isFullscreen = window.readCookie(Fullscreen.COOKIE_) === 'true';
3989 this.activate_();
3990 this.el.on('click.dac-fullscreen', this.toggleHandler_.bind(this));
3991 }
3992
3993 /**
3994 * Cookie name for storing the state
3995 * @type {string}
3996 * @private
3997 */
3998 Fullscreen.COOKIE_ = 'fullscreen';
3999
4000 /**
4001 * Classes to modify the DOM
4002 * @type {{mode: string, fullscreen: string, fullscreenExit: string}}
4003 * @private
4004 */
4005 Fullscreen.CLASSES_ = {
4006 mode: 'dac-fullscreen-mode',
4007 fullscreen: 'dac-fullscreen',
4008 fullscreenExit: 'dac-fullscreen-exit'
4009 };
4010
4011 /**
4012 * Event listener for toggling fullscreen mode
4013 * @param {MouseEvent} event
4014 * @private
4015 */
4016 Fullscreen.prototype.toggleHandler_ = function(event) {
4017 event.stopPropagation();
4018 this.toggle(!this.isFullscreen, true);
4019 };
4020
4021 /**
4022 * Change the DOM based on current state.
4023 * @private
4024 */
4025 Fullscreen.prototype.activate_ = function() {
4026 this.icon.toggleClass(Fullscreen.CLASSES_.fullscreen, !this.isFullscreen);
4027 this.icon.toggleClass(Fullscreen.CLASSES_.fullscreenExit, this.isFullscreen);
4028 this.html.toggleClass(Fullscreen.CLASSES_.mode, this.isFullscreen);
4029 };
4030
4031 /**
4032 * Toggle fullscreen mode and store the state in a cookie.
4033 */
4034 Fullscreen.prototype.toggle = function() {
4035 this.isFullscreen = !this.isFullscreen;
4036 window.writeCookie(Fullscreen.COOKIE_, this.isFullscreen, null);
4037 this.activate_();
4038 };
4039
4040 /**
4041 * jQuery plugin
4042 */
4043 $.fn.dacFullscreen = function() {
4044 return this.each(function() {
4045 new Fullscreen($(this));
4046 });
4047 };
4048})(jQuery);
4049
4050(function($) {
4051 'use strict';
4052
4053 /**
4054 * @param {HTMLElement} selected - The link that is selected in the nav.
4055 * @constructor
4056 */
4057 function HeaderTabs(selected) {
4058
4059 // Don't highlight any tabs on the index page
4060 if (location.pathname === '/index.html' || location.pathname === '/') {
4061 //return;
4062 }
4063
4064 this.selected = $(selected);
4065 this.selectedParent = this.selected.closest('.dac-nav-secondary').siblings('a');
4066 this.links = $('.dac-header-tabs a');
4067
4068 this.selectActiveTab();
4069 }
4070
4071 HeaderTabs.prototype.selectActiveTab = function() {
4072 var section = null;
4073
4074 if (this.selectedParent.length) {
4075 section = this.selectedParent.text();
4076 } else {
4077 section = this.selected.text();
4078 }
4079
4080 if (section) {
4081 this.links.removeClass('selected');
4082
4083 this.links.filter(function() {
4084 return $(this).text() === $.trim(section);
4085 }).addClass('selected');
4086 }
4087 };
4088
4089 /**
4090 * jQuery plugin
4091 */
4092 $.fn.dacHeaderTabs = function() {
4093 return this.each(function() {
4094 new HeaderTabs(this);
4095 });
4096 };
4097})(jQuery);
4098
4099(function($) {
4100 'use strict';
4101 var icon = $('<i/>').addClass('dac-sprite dac-nav-forward');
4102 var config = JSON.parse(window.localStorage.getItem('global-navigation') || '{}');
4103 var forwardLink = $('<span/>')
4104 .addClass('dac-nav-link-forward')
4105 .html(icon)
smain@google.comeeeb9b82016-08-29 18:12:27 -07004106 .attr('tabindex', 0)
4107 .on('click keypress', function(e) {
4108 if (e.type == 'keypress' && e.which == 13 || e.type == 'click') {
4109 swap_(e);
4110 }
4111 });
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004112
4113 /**
4114 * @constructor
4115 */
4116 function Nav(navigation) {
4117 $('.dac-nav-list').dacCurrentPage().dacHeaderTabs().dacSidebarToggle($('body'));
4118
4119 navigation.find('[data-reference-tree]').dacReferenceNav();
4120
4121 setupViews_(navigation.children().eq(0).children());
4122
4123 initCollapsedNavs(navigation.find('.dac-nav-sub-slider'));
4124
4125 $('#dac-main-navigation').scrollIntoView('.selected')
4126 }
4127
4128 function updateStore(icon) {
4129 var navClass = getCurrentLandingPage_(icon);
4130 var isExpanded = icon.hasClass('dac-expand-less-black');
4131 var expandedNavs = config.expanded || [];
4132 if (isExpanded) {
4133 expandedNavs.push(navClass);
4134 } else {
4135 expandedNavs = expandedNavs.filter(function(item) {
4136 return item !== navClass;
4137 });
4138 }
4139 config.expanded = expandedNavs;
4140 window.localStorage.setItem('global-navigation', JSON.stringify(config));
4141 }
4142
4143 function toggleSubNav_(icon) {
4144 var isExpanded = icon.hasClass('dac-expand-less-black');
4145 icon.toggleClass('dac-expand-less-black', !isExpanded);
4146 icon.toggleClass('dac-expand-more-black', isExpanded);
4147 icon.data('sub-navigation.dac').slideToggle(200);
4148
4149 updateStore(icon);
4150 }
4151
4152 function handleSubNavToggle_(event) {
4153 event.preventDefault();
4154 var icon = $(event.target);
4155 toggleSubNav_(icon);
4156 }
4157
4158 function getCurrentLandingPage_(icon) {
4159 return icon.closest('li')[0].className.replace('dac-nav-item ', '');
4160 }
4161
4162 // Setup sub navigation collapse/expand
4163 function initCollapsedNavs(toggleIcons) {
4164 toggleIcons.each(setInitiallyActive_($('body')));
smain@google.comeeeb9b82016-08-29 18:12:27 -07004165 toggleIcons.on('click keypress', function(e) {
4166 if (e.type == 'keypress' && e.which == 13 || e.type == 'click') {
4167 handleSubNavToggle_(e);
4168 }
4169 });
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004170 }
4171
4172 function setInitiallyActive_(body) {
4173 var expandedNavs = config.expanded || [];
4174 return function(i, icon) {
4175 icon = $(icon);
4176 var subNav = icon.next();
4177
4178 if (!subNav.length) {
4179 return;
4180 }
4181
4182 var landingPageClass = getCurrentLandingPage_(icon);
4183 var expanded = expandedNavs.indexOf(landingPageClass) >= 0;
4184 landingPageClass = landingPageClass === 'home' ? 'about' : landingPageClass;
4185
smain@google.comeeeb9b82016-08-29 18:12:27 -07004186 if (landingPageClass == 'about' && location.pathname == '/index.html') {
4187 expanded = true;
4188 }
4189
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004190 // TODO: Should read from localStorage
4191 var visible = body.hasClass(landingPageClass) || expanded;
4192
4193 icon.data('sub-navigation.dac', subNav);
4194 icon.toggleClass('dac-expand-less-black', visible);
4195 icon.toggleClass('dac-expand-more-black', !visible);
4196 subNav.toggle(visible);
4197 };
4198 }
4199
4200 function setupViews_(views) {
4201 if (views.length === 1) {
4202 // Active tier 1 nav.
4203 views.addClass('dac-active');
4204 } else {
4205 // Activate back button and tier 2 nav.
4206 views.slice(0, 2).addClass('dac-active');
4207 var selectedNav = views.eq(2).find('.selected').after(forwardLink);
4208 var langAttr = selectedNav.attr(window.getLangPref() + '-lang');
4209 //form the label from locale attr if possible, else set to selectedNav text value
4210 if ((typeof langAttr !== typeof undefined && langAttr !== false) && (langAttr !== '')) {
4211 $('.dac-nav-back-title').text(langAttr);
4212 } else {
4213 $('.dac-nav-back-title').text(selectedNav.text());
4214 }
4215 }
4216
4217 // Navigation should animate.
4218 setTimeout(function() {
4219 views.removeClass('dac-no-anim');
4220 }, 10);
4221 }
4222
4223 function swap_(event) {
4224 event.preventDefault();
4225 $(event.currentTarget).trigger('swap-content');
4226 }
4227
4228 /**
4229 * jQuery plugin
4230 */
4231 $.fn.dacNav = function() {
4232 return this.each(function() {
4233 new Nav($(this));
4234 });
4235 };
4236})(jQuery);
4237
4238/* global NAVTREE_DATA */
4239(function($) {
4240 /**
4241 * Build the reference navigation with namespace dropdowns.
4242 * @param {jQuery} el - The DOM element.
4243 */
4244 function buildReferenceNav(el) {
smain@google.comeeeb9b82016-08-29 18:12:27 -07004245 var supportLibraryPath = '/reference/android/support/';
4246 var currPath = location.pathname;
4247
4248 if (currPath.indexOf(supportLibraryPath) > -1) {
4249 updateSupportLibrariesNav(supportLibraryPath, currPath);
4250 }
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004251 var namespaceList = el.find('[data-reference-namespaces]');
smain@google.comeeeb9b82016-08-29 18:12:27 -07004252 var resources = $('[data-reference-resources]').detach();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004253 var selected = namespaceList.find('.selected');
smain@google.comeeeb9b82016-08-29 18:12:27 -07004254 resources.appendTo(el);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004255
4256 // Links should be toggleable.
4257 namespaceList.find('a').addClass('dac-reference-nav-toggle dac-closed');
4258
smain@google.comeeeb9b82016-08-29 18:12:27 -07004259 // Set the path for the navtree data to use.
4260 var navtree_filepath = getNavtreeFilePath(supportLibraryPath, currPath);
4261
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004262 // Load in all resources
smain@google.comeeeb9b82016-08-29 18:12:27 -07004263 $.getScript(navtree_filepath, function(data, textStatus, xhr) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004264 if (xhr.status === 200) {
smain@google.comeeeb9b82016-08-29 18:12:27 -07004265 namespaceList.on(
4266 'click', 'a.dac-reference-nav-toggle', toggleResourcesHandler);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004267 }
4268 });
4269
4270 // No setup required if no resources are present
4271 if (!resources.length) {
4272 return;
4273 }
4274
4275 // The resources should be a part of selected namespace.
4276 var overview = addResourcesToView(resources, selected);
4277
4278 // Currently viewing Overview
smain@google.comeeeb9b82016-08-29 18:12:27 -07004279 if (location.href === overview.attr('href')) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004280 overview.parent().addClass('selected');
4281 }
4282
4283 // Open currently selected resource
4284 var listsToOpen = selected.children().eq(1);
smain@google.comeeeb9b82016-08-29 18:12:27 -07004285 listsToOpen = listsToOpen.add(
4286 listsToOpen.find('.selected').parent()).show();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004287
4288 // Mark dropdowns as open
4289 listsToOpen.prev().removeClass('dac-closed');
4290
4291 // Scroll into view
4292 namespaceList.scrollIntoView(selected);
4293 }
4294
smain@google.comeeeb9b82016-08-29 18:12:27 -07004295 function getNavtreeFilePath(supportLibraryPath, currPath) {
4296 var navtree_filepath = '';
4297 var navtree_filename = 'navtree_data.js';
4298 if (currPath.indexOf(supportLibraryPath + 'test') > -1) {
4299 navtree_filepath = supportLibraryPath + 'test/' + navtree_filename;
4300 } else if (currPath.indexOf(supportLibraryPath + 'wearable') > -1) {
4301 navtree_filepath = supportLibraryPath + 'wearable/' + navtree_filename;
4302 } else {
4303 navtree_filepath = '/' + navtree_filename;
4304 }
4305 return navtree_filepath;
4306 }
4307
4308 function updateSupportLibrariesNav(supportLibraryPath, currPath) {
4309 var navTitle = '';
4310 if (currPath.indexOf(supportLibraryPath + 'test') > -1) {
4311 navTitle = 'Test Support APIs';
4312 } else if (currPath.indexOf(supportLibraryPath + 'wearable') > -1) {
4313 navTitle = 'Wearable Support APIs';
4314 }
4315 $('#api-nav-title').text(navTitle);
4316 $('#api-level-toggle').hide();
4317 }
4318
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004319 /**
4320 * Handles the toggling of resources.
4321 * @param {Event} event
4322 */
4323 function toggleResourcesHandler(event) {
4324 event.preventDefault();
smain@google.comeeeb9b82016-08-29 18:12:27 -07004325 if (event.type == 'click' || event.type == 'keypress' && event.which == 13) {
4326 var el = $(this);
4327 // If resources for given namespace is not present, fetch correct data.
4328 if (this.tagName === 'A' && !this.hasResources) {
4329 addResourcesToView(buildResourcesViewForData(getDataForNamespace(el.text())), el.parent());
4330 }
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004331
smain@google.comeeeb9b82016-08-29 18:12:27 -07004332 el.toggleClass('dac-closed').next().slideToggle(200);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004333 }
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004334 }
4335
4336 /**
4337 * @param {String} namespace
4338 * @returns {Array} namespace data
4339 */
4340 function getDataForNamespace(namespace) {
4341 var namespaceData = NAVTREE_DATA.filter(function(data) {
4342 return data[0] === namespace;
4343 });
4344
4345 return namespaceData.length ? namespaceData[0][2] : [];
4346 }
4347
4348 /**
4349 * Build a list item for a resource
4350 * @param {Array} resource
4351 * @returns {String}
4352 */
4353 function buildResourceItem(resource) {
4354 return '<li class="api apilevel-' + resource[3] + '"><a href="/' + resource[1] + '">' + resource[0] + '</a></li>';
4355 }
4356
4357 /**
4358 * Build resources list items.
4359 * @param {Array} resources
4360 * @returns {String}
4361 */
4362 function buildResourceList(resources) {
4363 return '<li><h2>' + resources[0] + '</h2><ul>' + resources[2].map(buildResourceItem).join('') + '</ul>';
4364 }
4365
4366 /**
4367 * Build a resources view
4368 * @param {Array} data
4369 * @returns {jQuery} resources in an unordered list.
4370 */
4371 function buildResourcesViewForData(data) {
4372 return $('<ul>' + data.map(buildResourceList).join('') + '</ul>');
4373 }
4374
4375 /**
4376 * Add resources to a containing view.
4377 * @param {jQuery} resources
4378 * @param {jQuery} view
4379 * @returns {jQuery} the overview link.
4380 */
4381 function addResourcesToView(resources, view) {
4382 var namespace = view.children().eq(0);
4383 var overview = $('<a href="' + namespace.attr('href') + '">Overview</a>');
4384
4385 // Mark namespace with content;
4386 namespace[0].hasResources = true;
4387
4388 // Add correct classes / event listeners to resources.
4389 resources.prepend($('<li>').html(overview))
4390 .find('a')
4391 .addClass('dac-reference-nav-resource')
4392 .end()
smain@google.comeeeb9b82016-08-29 18:12:27 -07004393 .find('h2').attr('tabindex', 0)
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004394 .addClass('dac-reference-nav-toggle dac-closed')
smain@google.comeeeb9b82016-08-29 18:12:27 -07004395 .on('click keypress', toggleResourcesHandler)
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004396 .end()
4397 .add(resources.find('ul'))
4398 .addClass('dac-reference-nav-resources')
4399 .end()
4400 .appendTo(view);
4401
4402 return overview;
4403 }
4404
smain@google.comeeeb9b82016-08-29 18:12:27 -07004405 function setActiveReferencePackage(el) {
4406 var packageLinkEls = el.find('[data-reference-namespaces] a');
4407 var selected = null;
4408 var highestMatchCount = 0;
4409 packageLinkEls.each(function(index, linkEl) {
4410 var matchCount = 0;
4411 $(location.pathname.split('/')).each(function(index, subpath) {
4412 if (linkEl.href.indexOf('/' + subpath + '/') > -1) {
4413 matchCount++;
4414 }
4415 });
4416 if (matchCount > highestMatchCount) {
4417 selected = linkEl;
4418 highestMatchCount = matchCount;
4419 }
4420 });
4421 $(selected).parent().addClass('selected');
4422 }
4423
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004424 /**
4425 * jQuery plugin
4426 */
4427 $.fn.dacReferenceNav = function() {
4428 return this.each(function() {
smain@google.comeeeb9b82016-08-29 18:12:27 -07004429 setActiveReferencePackage($(this));
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004430 buildReferenceNav($(this));
4431 });
4432 };
4433})(jQuery);
4434
4435/** Scroll a container to make a target element visible
4436 This is called when the page finished loading. */
4437$.fn.scrollIntoView = function(target) {
4438 if ('string' === typeof target) {
4439 target = this.find(target);
4440 }
4441 if (this.is(':visible')) {
4442 if (target.length == 0) {
4443 // If no selected item found, exit
4444 return;
4445 }
4446
4447 // get the target element's offset from its container nav by measuring the element's offset
4448 // relative to the document then subtract the container nav's offset relative to the document
4449 var targetOffset = target.offset().top - this.offset().top;
4450 var containerHeight = this.height();
4451 if (targetOffset > containerHeight * .8) { // multiply nav height by .8 so we move up the item
4452 // if it's more than 80% down the nav
4453 // scroll the item up by an amount equal to 80% the container height
4454 this.scrollTop(targetOffset - (containerHeight * .8));
4455 }
4456 }
4457};
4458
4459(function($) {
4460 $.fn.dacCurrentPage = function() {
4461 // Highlight the header tabs...
4462 // highlight Design tab
4463 var baseurl = getBaseUri(window.location.pathname);
4464 var urlSegments = baseurl.split('/');
4465 var navEl = this;
4466 var body = $('body');
4467 var subNavEl = navEl.find('.dac-nav-secondary');
4468 var parentNavEl;
4469 var selected;
4470 // In NDK docs, highlight appropriate sub-nav
smain@google.comeeeb9b82016-08-29 18:12:27 -07004471 if (body.hasClass('dac-ndk')) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004472 if (body.hasClass('guide')) {
4473 selected = navEl.find('> li.guides > a').addClass('selected');
4474 } else if (body.hasClass('reference')) {
4475 selected = navEl.find('> li.reference > a').addClass('selected');
4476 } else if (body.hasClass('samples')) {
4477 selected = navEl.find('> li.samples > a').addClass('selected');
4478 } else if (body.hasClass('downloads')) {
4479 selected = navEl.find('> li.downloads > a').addClass('selected');
4480 }
smain@google.comeeeb9b82016-08-29 18:12:27 -07004481 } else if (body.hasClass('dac-studio')) {
4482 if (body.hasClass('download')) {
4483 selected = navEl.find('> li.download > a').addClass('selected');
4484 } else if (body.hasClass('features')) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004485 selected = navEl.find('> li.features > a').addClass('selected');
4486 } else if (body.hasClass('guide')) {
4487 selected = navEl.find('> li.guide > a').addClass('selected');
4488 } else if (body.hasClass('preview')) {
4489 selected = navEl.find('> li.preview > a').addClass('selected');
4490 }
4491 } else if (body.hasClass('design')) {
4492 selected = navEl.find('> li.design > a').addClass('selected');
4493 // highlight Home nav
smain@google.comeeeb9b82016-08-29 18:12:27 -07004494 } else if (body.hasClass('about') || location.pathname == '/index.html') {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004495 parentNavEl = navEl.find('> li.home > a');
4496 parentNavEl.addClass('has-subnav');
4497 // In Home docs, also highlight appropriate sub-nav
4498 if (urlSegments[1] === 'wear' || urlSegments[1] === 'tv' ||
4499 urlSegments[1] === 'auto') {
4500 selected = subNavEl.find('li.' + urlSegments[1] + ' > a').addClass('selected');
4501 } else if (urlSegments[1] === 'about') {
4502 selected = subNavEl.find('li.versions > a').addClass('selected');
4503 } else {
4504 selected = parentNavEl.removeClass('has-subnav').addClass('selected');
4505 }
4506 // highlight Develop nav
4507 } else if (body.hasClass('develop') || body.hasClass('google')) {
4508 parentNavEl = navEl.find('> li.develop > a');
4509 parentNavEl.addClass('has-subnav');
4510 // In Develop docs, also highlight appropriate sub-nav
4511 if (urlSegments[1] === 'training') {
4512 selected = subNavEl.find('li.training > a').addClass('selected');
4513 } else if (urlSegments[1] === 'guide') {
4514 selected = subNavEl.find('li.guide > a').addClass('selected');
4515 } else if (urlSegments[1] === 'reference') {
4516 // If the root is reference, but page is also part of Google Services, select Google
4517 if (body.hasClass('google')) {
4518 selected = subNavEl.find('li.google > a').addClass('selected');
4519 } else {
4520 selected = subNavEl.find('li.reference > a').addClass('selected');
4521 }
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004522 } else if (body.hasClass('google')) {
4523 selected = subNavEl.find('li.google > a').addClass('selected');
4524 } else if (body.hasClass('samples')) {
4525 selected = subNavEl.find('li.samples > a').addClass('selected');
4526 } else {
4527 selected = parentNavEl.removeClass('has-subnav').addClass('selected');
4528 }
4529 // highlight Distribute nav
4530 } else if (body.hasClass('distribute')) {
4531 parentNavEl = navEl.find('> li.distribute > a');
4532 parentNavEl.addClass('has-subnav');
4533 // In Distribute docs, also highlight appropriate sub-nav
4534 if (urlSegments[2] === 'users') {
4535 selected = subNavEl.find('li.users > a').addClass('selected');
4536 } else if (urlSegments[2] === 'engage') {
4537 selected = subNavEl.find('li.engage > a').addClass('selected');
4538 } else if (urlSegments[2] === 'monetize') {
4539 selected = subNavEl.find('li.monetize > a').addClass('selected');
4540 } else if (urlSegments[2] === 'analyze') {
4541 selected = subNavEl.find('li.analyze > a').addClass('selected');
4542 } else if (urlSegments[2] === 'tools') {
4543 selected = subNavEl.find('li.disttools > a').addClass('selected');
4544 } else if (urlSegments[2] === 'stories') {
4545 selected = subNavEl.find('li.stories > a').addClass('selected');
4546 } else if (urlSegments[2] === 'essentials') {
4547 selected = subNavEl.find('li.essentials > a').addClass('selected');
4548 } else if (urlSegments[2] === 'googleplay') {
4549 selected = subNavEl.find('li.googleplay > a').addClass('selected');
4550 } else {
4551 selected = parentNavEl.removeClass('has-subnav').addClass('selected');
4552 }
4553 } else if (body.hasClass('preview')) {
4554 selected = navEl.find('> li.preview > a').addClass('selected');
4555 }
4556 return $(selected);
4557 };
4558})(jQuery);
4559
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004560(function($) {
4561 'use strict';
4562
4563 /**
4564 * Toggle the visabilty of the mobile navigation.
4565 * @param {HTMLElement} el - The DOM element.
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004566 * @param {Object} options
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004567 * @constructor
4568 */
4569 function ToggleNav(el, options) {
4570 this.el = $(el);
4571 this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004572 this.body = $(document.body);
4573 this.navigation_ = this.body.find(this.options.navigation);
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004574 this.el.on('click', this.clickHandler_.bind(this));
4575 }
4576
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004577 ToggleNav.BREAKPOINT_ = 980;
4578
4579 /**
4580 * Open on correct sizes
4581 */
4582 function toggleSidebarVisibility(body) {
4583 var wasClosed = ('' + localStorage.getItem('navigation-open')) === 'false';
smain@google.comeeeb9b82016-08-29 18:12:27 -07004584 // Override the local storage setting for navigation-open for child sites
4585 // with no-subnav class.
4586 if (document.body.classList.contains('no-subnav')) {
4587 wasClosed = false;
4588 }
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004589
4590 if (wasClosed) {
4591 body.removeClass(ToggleNav.DEFAULTS_.activeClass);
4592 } else if (window.innerWidth >= ToggleNav.BREAKPOINT_) {
4593 body.addClass(ToggleNav.DEFAULTS_.activeClass);
4594 } else {
4595 body.removeClass(ToggleNav.DEFAULTS_.activeClass);
4596 }
4597 }
4598
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004599 /**
4600 * ToggleNav Default Settings
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004601 * @type {{body: boolean, dimmer: string, navigation: string, activeClass: string}}
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004602 * @private
4603 */
4604 ToggleNav.DEFAULTS_ = {
4605 body: true,
4606 dimmer: '.dac-nav-dimmer',
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004607 animatingClass: 'dac-nav-animating',
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004608 navigation: '[data-dac-nav]',
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004609 activeClass: 'dac-nav-open'
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004610 };
4611
4612 /**
4613 * The actual toggle logic.
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004614 * @param {Event} event
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004615 * @private
4616 */
4617 ToggleNav.prototype.clickHandler_ = function(event) {
4618 event.preventDefault();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004619 var animatingClass = this.options.animatingClass;
4620 var body = this.body;
4621
4622 body.addClass(animatingClass);
4623 body.toggleClass(this.options.activeClass);
4624
4625 setTimeout(function() {
4626 body.removeClass(animatingClass);
4627 }, this.navigation_.transitionDuration());
4628
4629 if (window.innerWidth >= ToggleNav.BREAKPOINT_) {
4630 localStorage.setItem('navigation-open', body.hasClass(this.options.activeClass));
4631 }
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004632 };
4633
4634 /**
4635 * jQuery plugin
4636 * @param {object} options - Override default options.
4637 */
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004638 $.fn.dacToggleMobileNav = function() {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004639 return this.each(function() {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004640 var el = $(this);
4641 new ToggleNav(el, el.data());
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004642 });
4643 };
4644
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004645 $.fn.dacSidebarToggle = function(body) {
4646 toggleSidebarVisibility(body);
4647 $(window).on('resize', toggleSidebarVisibility.bind(null, body));
4648 };
4649
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004650 /**
4651 * Data Attribute API
4652 */
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004653 $(function() {
4654 $('[data-dac-toggle-nav]').dacToggleMobileNav();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004655 });
4656})(jQuery);
4657
4658(function($) {
4659 'use strict';
4660
4661 /**
4662 * Submit the newsletter form to a Google Form.
4663 * @param {HTMLElement} el - The Form DOM element.
4664 * @constructor
4665 */
4666 function NewsletterForm(el) {
4667 this.el = $(el);
4668 this.form = this.el.find('form');
4669 $('<iframe/>').hide()
4670 .attr('name', 'dac-newsletter-iframe')
4671 .attr('src', '')
4672 .insertBefore(this.form);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004673 this.el.find('[data-newsletter-language]').val(window.polyglot.t('newsletter.languageVal'));
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004674 this.form.on('submit', this.submitHandler_.bind(this));
4675 }
4676
4677 /**
4678 * Milliseconds until modal has vanished after modal-close is triggered.
4679 * @type {number}
4680 * @private
4681 */
4682 NewsletterForm.CLOSE_DELAY_ = 300;
4683
4684 /**
4685 * Switch view to display form after close.
4686 * @private
4687 */
4688 NewsletterForm.prototype.closeHandler_ = function() {
4689 setTimeout(function() {
4690 this.el.trigger('swap-reset');
4691 }.bind(this), NewsletterForm.CLOSE_DELAY_);
4692 };
4693
4694 /**
4695 * Reset the modal to initial state.
4696 * @private
4697 */
4698 NewsletterForm.prototype.reset_ = function() {
4699 this.form.trigger('reset');
4700 this.el.one('modal-close', this.closeHandler_.bind(this));
4701 };
4702
4703 /**
4704 * Display a success view on submit.
4705 * @private
4706 */
4707 NewsletterForm.prototype.submitHandler_ = function() {
4708 this.el.one('swap-complete', this.reset_.bind(this));
4709 this.el.trigger('swap-content');
4710 };
4711
4712 /**
4713 * jQuery plugin
4714 * @param {object} options - Override default options.
4715 */
4716 $.fn.dacNewsletterForm = function(options) {
4717 return this.each(function() {
4718 new NewsletterForm(this, options);
4719 });
4720 };
4721
4722 /**
4723 * Data Attribute API
4724 */
4725 $(document).on('ready.aranja', function() {
4726 $('[data-newsletter]').each(function() {
4727 $(this).dacNewsletterForm();
4728 });
4729 });
4730})(jQuery);
4731
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004732/* globals METADATA, YOUTUBE_RESOURCES, BLOGGER_RESOURCES */
4733window.metadata = {};
4734
4735/**
4736 * Prepare metadata and indices for querying.
4737 */
4738window.metadata.prepare = (function() {
4739 // Helper functions.
4740 function mergeArrays() {
4741 return Array.prototype.concat.apply([], arguments);
4742 }
4743
4744 /**
4745 * Creates lookup maps for a resource index.
4746 * I.e. where MAP['some tag'][resource.id] === true when that resource has 'some tag'.
4747 * @param resourceDict
4748 * @returns {{}}
4749 */
4750 function buildResourceLookupMap(resourceDict) {
4751 var map = {};
4752 for (var key in resourceDict) {
4753 var dictForKey = {};
4754 var srcArr = resourceDict[key];
4755 for (var i = 0; i < srcArr.length; i++) {
4756 dictForKey[srcArr[i].index] = true;
4757 }
4758 map[key] = dictForKey;
4759 }
4760 return map;
4761 }
4762
4763 /**
4764 * Merges metadata maps for english and the current language into the global store.
4765 */
4766 function mergeMetadataMap(name, locale) {
4767 if (locale && locale !== 'en' && METADATA[locale]) {
4768 METADATA[name] = $.extend(METADATA.en[name], METADATA[locale][name]);
4769 } else {
4770 METADATA[name] = METADATA.en[name];
4771 }
4772 }
4773
4774 /**
4775 * Index all resources by type, url, tag and category.
4776 * @param resources
4777 */
4778 function createIndices(resources) {
4779 // URL, type, tag and category lookups
4780 var byType = METADATA.byType = {};
4781 var byUrl = METADATA.byUrl = {};
4782 var byTag = METADATA.byTag = {};
4783 var byCategory = METADATA.byCategory = {};
4784
4785 for (var i = 0; i < resources.length; i++) {
4786 var res = resources[i];
4787
4788 // Store index.
4789 res.index = i;
4790
4791 // Index by type.
4792 var type = res.type;
4793 if (type) {
4794 byType[type] = byType[type] || [];
4795 byType[type].push(res);
4796 }
4797
4798 // Index by tag.
4799 var tags = res.tags || [];
4800 for (var j = 0; j < tags.length; j++) {
4801 var tag = tags[j];
4802 if (tag) {
4803 byTag[tag] = byTag[tag] || [];
4804 byTag[tag].push(res);
4805 }
4806 }
4807
4808 // Index by category.
4809 var category = res.category;
4810 if (category) {
4811 byCategory[category] = byCategory[category] || [];
4812 byCategory[category].push(res);
4813 }
4814
4815 // Index by url.
4816 var url = res.url;
4817 if (url) {
4818 res.baseUrl = url.replace(/^intl\/\w+[\/]/, '');
4819 byUrl[res.baseUrl] = res;
4820 }
4821 }
4822 METADATA.hasType = buildResourceLookupMap(byType);
4823 METADATA.hasTag = buildResourceLookupMap(byTag);
4824 METADATA.hasCategory = buildResourceLookupMap(byCategory);
4825 }
4826
4827 return function() {
4828 // Only once.
4829 if (METADATA.all) { return; }
4830
4831 // Get current language.
4832 var locale = getLangPref();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004833 // Merge english resources.
smain@google.comeeeb9b82016-08-29 18:12:27 -07004834 if (useDevsiteMetadata) {
4835 var all_keys = Object.keys(METADATA['en']);
4836 METADATA.all = []
4837
4838 $(all_keys).each(function(index, category) {
4839 if (RESERVED_METADATA_CATEGORY_NAMES.indexOf(category) == -1) {
4840 METADATA.all = mergeArrays(
4841 METADATA.all,
4842 METADATA.en[category]
4843 );
4844 }
4845 });
4846
4847 METADATA.all = mergeArrays(
4848 METADATA.all,
4849 YOUTUBE_RESOURCES,
4850 BLOGGER_RESOURCES,
4851 METADATA.en.extras
4852 );
4853 } else {
4854 METADATA.all = mergeArrays(
4855 METADATA.en.about,
4856 METADATA.en.design,
4857 METADATA.en.distribute,
4858 METADATA.en.develop,
4859 YOUTUBE_RESOURCES,
4860 BLOGGER_RESOURCES,
4861 METADATA.en.extras
4862 );
4863 }
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004864
4865 // Merge local language resources.
4866 if (locale !== 'en' && METADATA[locale]) {
smain@google.comeeeb9b82016-08-29 18:12:27 -07004867 if (useDevsiteMetadata) {
4868 all_keys = Object.keys(METADATA[locale]);
4869 $(all_keys).each(function(index, category) {
4870 if (RESERVED_METADATA_CATEGORY_NAMES.indexOf(category) == -1) {
4871 METADATA.all = mergeArrays(
4872 METADATA.all,
4873 METADATA.en[category]
4874 );
4875 }
4876 });
4877
4878 METADATA.all = mergeArrays(
4879 METADATA.all,
4880 METADATA[locale].extras
4881 );
4882 } else {
4883 METADATA.all = mergeArrays(
4884 METADATA.all,
4885 METADATA[locale].about,
4886 METADATA[locale].design,
4887 METADATA[locale].distribute,
4888 METADATA[locale].develop,
4889 METADATA[locale].extras
4890 );
4891
4892 }
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004893 }
4894
4895 mergeMetadataMap('collections', locale);
4896 mergeMetadataMap('searchHeroCollections', locale);
4897 mergeMetadataMap('carousel', locale);
4898
4899 // Create query indicies for resources.
4900 createIndices(METADATA.all, locale);
4901
4902 // Reference metadata.
smain@google.comeeeb9b82016-08-29 18:12:27 -07004903 METADATA.androidReference = mergeArrays(
4904 window.DATA, window.SUPPORT_WEARABLE_DATA, window.SUPPORT_TEST_DATA);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00004905 METADATA.googleReference = mergeArrays(window.GMS_DATA, window.GCM_DATA);
4906 };
4907})();
4908
4909/* global METADATA, util */
4910window.metadata.query = (function($) {
4911 var pageMap = {};
4912
4913 function buildResourceList(opts) {
4914 window.metadata.prepare();
4915 var expressions = parseResourceQuery(opts.query || '');
4916 var instanceMap = {};
4917 var results = [];
4918
4919 for (var i = 0; i < expressions.length; i++) {
4920 var clauses = expressions[i];
4921
4922 // Get all resources for first clause
4923 var resources = getResourcesForClause(clauses.shift());
4924
4925 // Concat to final results list
4926 results = results.concat(resources.map(filterResources(clauses, i > 0, instanceMap)).filter(filterEmpty));
4927 }
4928
4929 // Set correct order
4930 if (opts.sortOrder && results.length) {
4931 results = opts.sortOrder === 'random' ? util.shuffle(results) : results.sort(sortResultsByKey(opts.sortOrder));
4932 }
4933
4934 // Slice max results.
4935 if (opts.maxResults !== Infinity) {
4936 results = results.slice(0, opts.maxResults);
4937 }
4938
4939 // Remove page level duplicates
4940 if (opts.allowDuplicates === undefined || opts.allowDuplicates === 'false') {
4941 results = results.filter(removePageLevelDuplicates);
4942
4943 for (var index = 0; index < results.length; ++index) {
4944 pageMap[results[index].index] = 1;
4945 }
4946 }
4947
4948 return results;
4949 }
4950
4951 function filterResources(clauses, removeDuplicates, map) {
4952 return function(resource) {
4953 var resourceIsAllowed = true;
4954
4955 // References must be defined.
4956 if (resource === undefined) {
4957 return;
4958 }
4959
4960 // Get canonical (localized) version of resource if possible.
4961 resource = METADATA.byUrl[resource.baseUrl] || METADATA.byUrl[resource.url] || resource;
4962
4963 // Filter out resources already used
4964 if (removeDuplicates) {
4965 resourceIsAllowed = !map[resource.index];
4966 }
4967
4968 // Must fulfill all criteria
4969 if (clauses.length > 0) {
4970 resourceIsAllowed = resourceIsAllowed && doesResourceMatchClauses(resource, clauses);
4971 }
4972
4973 // Mark resource as used.
4974 if (resourceIsAllowed) {
4975 map[resource.index] = 1;
4976 }
4977
4978 return resourceIsAllowed && resource;
4979 };
4980 }
4981
4982 function filterEmpty(resource) {
4983 return resource;
4984 }
4985
4986 function sortResultsByKey(key) {
4987 var desc = key.charAt(0) === '-';
4988
4989 if (desc) {
4990 key = key.substring(1);
4991 }
4992
4993 return function(x, y) {
4994 return (desc ? -1 : 1) * (parseInt(x[key], 10) - parseInt(y[key], 10));
4995 };
4996 }
4997
4998 function getResourcesForClause(clause) {
4999 switch (clause.attr) {
5000 case 'type':
5001 return METADATA.byType[clause.value];
5002 case 'tag':
5003 return METADATA.byTag[clause.value];
5004 case 'collection':
5005 var resources = METADATA.collections[clause.value] || {};
5006 return getResourcesByUrlCollection(resources.resources);
5007 case 'history':
5008 return getResourcesByUrlCollection($.dacGetVisitedUrls(clause.value));
5009 case 'section':
5010 return getResourcesByUrlCollection([clause.value].sections);
5011 default:
5012 return [];
5013 }
5014 }
5015
5016 function getResourcesByUrlCollection(resources) {
5017 return (resources || []).map(function(url) {
5018 return METADATA.byUrl[url];
5019 });
5020 }
5021
5022 function removePageLevelDuplicates(resource) {
5023 return resource && !pageMap[resource.index];
5024 }
5025
5026 function doesResourceMatchClauses(resource, clauses) {
5027 for (var i = 0; i < clauses.length; i++) {
5028 var map;
5029 switch (clauses[i].attr) {
5030 case 'type':
5031 map = METADATA.hasType[clauses[i].value];
5032 break;
5033 case 'tag':
5034 map = METADATA.hasTag[clauses[i].value];
5035 break;
5036 }
5037
5038 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
5039 return clauses[i].negative;
5040 }
5041 }
5042
5043 return true;
5044 }
5045
5046 function parseResourceQuery(query) {
5047 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
5048 var expressions = [];
5049 var expressionStrs = query.split(',') || [];
5050 for (var i = 0; i < expressionStrs.length; i++) {
5051 var expr = expressionStrs[i] || '';
5052
5053 // Break expression into clauses (clause e.g. 'tag:foo')
5054 var clauses = [];
5055 var clauseStrs = expr.split(/(?=[\+\-])/);
5056 for (var j = 0; j < clauseStrs.length; j++) {
5057 var clauseStr = clauseStrs[j] || '';
5058
5059 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
5060 var parts = clauseStr.split(':');
5061 var clause = {};
5062
5063 clause.attr = parts[0].replace(/^\s+|\s+$/g, '');
5064 if (clause.attr) {
5065 if (clause.attr.charAt(0) === '+') {
5066 clause.attr = clause.attr.substring(1);
5067 } else if (clause.attr.charAt(0) === '-') {
5068 clause.negative = true;
5069 clause.attr = clause.attr.substring(1);
5070 }
5071 }
5072
5073 if (parts.length > 1) {
5074 clause.value = parts[1].replace(/^\s+|\s+$/g, '');
5075 }
5076
5077 clauses.push(clause);
5078 }
5079
5080 if (!clauses.length) {
5081 continue;
5082 }
5083
5084 expressions.push(clauses);
5085 }
5086
5087 return expressions;
5088 }
5089
5090 return buildResourceList;
5091})(jQuery);
5092
5093/* global METADATA, getLangPref */
5094
5095window.metadata.search = (function() {
5096 'use strict';
5097
5098 var currentLang = getLangPref();
5099
5100 function search(query) {
5101 window.metadata.prepare();
5102 return {
5103 android: findDocsMatches(query, METADATA.androidReference),
5104 docs: findDocsMatches(query, METADATA.googleReference),
5105 resources: findResourceMatches(query)
5106 };
5107 }
5108
5109 function findDocsMatches(query, data) {
5110 var results = [];
5111
5112 for (var i = 0; i < data.length; i++) {
5113 var s = data[i];
5114 if (query.length !== 0 && s.label.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
5115 results.push(s);
5116 }
5117 }
5118
5119 rankAutocompleteApiResults(query, results);
5120
5121 return results;
5122 }
5123
5124 function findResourceMatches(query) {
5125 var results = [];
5126
5127 // Search for matching JD docs
5128 if (query.length >= 2) {
5129 /* In some langs, spaces may be optional between certain non-Ascii word-glyphs. For
5130 * those langs, only match query at word boundaries if query includes Ascii chars only.
5131 */
5132 var NO_BOUNDARY_LANGUAGES = ['ja','ko','vi','zh-cn','zh-tw'];
5133 var isAsciiOnly = /^[\u0000-\u007f]*$/.test(query);
5134 var noBoundaries = (NO_BOUNDARY_LANGUAGES.indexOf(window.getLangPref()) !== -1);
5135 var exprBoundary = (!isAsciiOnly && noBoundaries) ? '' : '(?:^|\\s)';
5136 var queryRegex = new RegExp(exprBoundary + query.toLowerCase(), 'g');
5137
5138 var all = METADATA.all;
5139 for (var i = 0; i < all.length; i++) {
5140 // current search comparison, with counters for tag and title,
5141 // used later to improve ranking
5142 var s = all[i];
5143 s.matched_tag = 0;
5144 s.matched_title = 0;
5145 var matched = false;
5146
5147 // Check if query matches any tags; work backwards toward 1 to assist ranking
5148 if (s.keywords) {
5149 for (var j = s.keywords.length - 1; j >= 0; j--) {
5150 // it matches a tag
5151 if (s.keywords[j].toLowerCase().match(queryRegex)) {
5152 matched = true;
5153 s.matched_tag = j + 1; // add 1 to index position
5154 }
5155 }
5156 }
5157
5158 // Check if query matches doc title
5159 if (s.title.toLowerCase().match(queryRegex)) {
5160 matched = true;
5161 s.matched_title = 1;
5162 }
5163
5164 // Remember the doc if it matches either
5165 if (matched) {
5166 results.push(s);
5167 }
5168 }
5169
5170 // Improve the current results
5171 results = lookupBetterResult(results);
5172
5173 // Rank/sort all the matched pages
5174 rankAutocompleteDocResults(results);
5175
5176 return results;
5177 }
5178 }
5179
5180 // Replaces a match with another resource by url, if it exists.
5181 function lookupReplacementByUrl(match, url) {
5182 var replacement = METADATA.byUrl[url];
5183
5184 // Replacement resource does not exists.
5185 if (!replacement) { return; }
5186
5187 replacement.matched_title = Math.max(replacement.matched_title, match.matched_title);
5188 replacement.matched_tag = Math.max(replacement.matched_tag, match.matched_tag);
5189
5190 return replacement;
5191 }
5192
5193 // Find the localized version of a page if it exists.
5194 function lookupLocalizedVersion(match) {
5195 return METADATA.byUrl[match.baseUrl] || METADATA.byUrl[match.url];
5196 }
5197
5198 // Find the main page for a tutorial when matching a subpage.
5199 function lookupTutorialIndex(match) {
5200 // Guard for non index tutorial pages.
5201 if (match.type !== 'training' || match.url.indexOf('index.html') >= 0) { return; }
5202
5203 var indexUrl = match.url.replace(/[^\/]+$/, 'index.html');
5204 return lookupReplacementByUrl(match, indexUrl);
5205 }
5206
5207 // Find related results which are a better match for the user.
5208 function lookupBetterResult(matches) {
5209 var newMatches = [];
5210
5211 matches = matches.filter(function(match) {
5212 var newMatch = match;
5213 newMatch = lookupTutorialIndex(newMatch) || newMatch;
5214 newMatch = lookupLocalizedVersion(newMatch) || newMatch;
5215
5216 if (newMatch !== match) {
5217 newMatches.push(newMatch);
5218 }
5219
5220 return newMatch === match;
5221 });
5222
5223 return toUnique(newMatches.concat(matches));
5224 }
5225
5226 /* Order the jd doc result list based on match quality */
5227 function rankAutocompleteDocResults(matches) {
5228 if (!matches || !matches.length) {
5229 return;
5230 }
5231
5232 var _resultScoreFn = function(match) {
5233 var score = 1.0;
5234
5235 // if the query matched a tag
5236 if (match.matched_tag > 0) {
5237 // multiply score by factor relative to position in tags list (max of 3)
5238 score *= 3 / match.matched_tag;
5239
5240 // if it also matched the title
5241 if (match.matched_title > 0) {
5242 score *= 2;
5243 }
5244 } else if (match.matched_title > 0) {
5245 score *= 3;
5246 }
5247
5248 if (match.lang === currentLang) {
5249 score *= 5;
5250 }
5251
5252 return score;
5253 };
5254
5255 for (var i = 0; i < matches.length; i++) {
5256 matches[i].__resultScore = _resultScoreFn(matches[i]);
5257 }
5258
5259 matches.sort(function(a, b) {
5260 var n = b.__resultScore - a.__resultScore;
5261
5262 if (n === 0) {
5263 // lexicographical sort if scores are the same
5264 n = (a.title < b.title) ? -1 : 1;
5265 }
5266
5267 return n;
5268 });
5269 }
5270
5271 /* Order the result list based on match quality */
5272 function rankAutocompleteApiResults(query, matches) {
5273 query = query || '';
5274 if (!matches || !matches.length) {
5275 return;
5276 }
5277
5278 // helper function that gets the last occurence index of the given regex
5279 // in the given string, or -1 if not found
5280 var _lastSearch = function(s, re) {
5281 if (s === '') {
5282 return -1;
5283 }
5284 var l = -1;
5285 var tmp;
5286 while ((tmp = s.search(re)) >= 0) {
5287 if (l < 0) {
5288 l = 0;
5289 }
5290 l += tmp;
5291 s = s.substr(tmp + 1);
5292 }
5293 return l;
5294 };
5295
5296 // helper function that counts the occurrences of a given character in
5297 // a given string
5298 var _countChar = function(s, c) {
5299 var n = 0;
5300 for (var i = 0; i < s.length; i++) {
5301 if (s.charAt(i) === c) {
5302 ++n;
5303 }
5304 }
5305 return n;
5306 };
5307
5308 var queryLower = query.toLowerCase();
5309 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
5310 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
5311 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
5312
5313 var _resultScoreFn = function(result) {
5314 // scores are calculated based on exact and prefix matches,
5315 // and then number of path separators (dots) from the last
5316 // match (i.e. favoring classes and deep package names)
5317 var score = 1.0;
5318 var labelLower = result.label.toLowerCase();
5319 var t;
5320 var partsAfter;
5321 t = _lastSearch(labelLower, partExactAlnumRE);
5322 if (t >= 0) {
5323 // exact part match
5324 partsAfter = _countChar(labelLower.substr(t + 1), '.');
5325 score *= 200 / (partsAfter + 1);
5326 } else {
5327 t = _lastSearch(labelLower, partPrefixAlnumRE);
5328 if (t >= 0) {
5329 // part prefix match
5330 partsAfter = _countChar(labelLower.substr(t + 1), '.');
5331 score *= 20 / (partsAfter + 1);
5332 }
5333 }
5334
5335 return score;
5336 };
5337
5338 for (var i = 0; i < matches.length; i++) {
5339 // if the API is deprecated, default score is 0; otherwise, perform scoring
5340 if (matches[i].deprecated === 'true') {
5341 matches[i].__resultScore = 0;
5342 } else {
5343 matches[i].__resultScore = _resultScoreFn(matches[i]);
5344 }
5345 }
5346
5347 matches.sort(function(a, b) {
5348 var n = b.__resultScore - a.__resultScore;
5349
5350 if (n === 0) {
5351 // lexicographical sort if scores are the same
5352 n = (a.label < b.label) ? -1 : 1;
5353 }
5354
5355 return n;
5356 });
5357 }
5358
5359 // Destructive but fast toUnique.
5360 // http://stackoverflow.com/a/25082874
5361 function toUnique(array) {
5362 var c;
5363 var b = array.length || 1;
5364
5365 while (c = --b) {
5366 while (c--) {
5367 if (array[b] === array[c]) {
5368 array.splice(c, 1);
5369 }
5370 }
5371 }
5372 return array;
5373 }
5374
5375 return search;
5376})();
5377
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005378(function($) {
5379 'use strict';
5380
5381 /**
5382 * Smoothly scroll to location on current page.
5383 * @param el
5384 * @param options
5385 * @constructor
5386 */
5387 function ScrollButton(el, options) {
5388 this.el = $(el);
5389 this.target = $(this.el.attr('href'));
5390 this.options = $.extend({}, ScrollButton.DEFAULTS_, options);
5391
5392 if (typeof this.options.offset === 'string') {
5393 this.options.offset = $(this.options.offset).height();
5394 }
5395
5396 this.el.on('click', this.clickHandler_.bind(this));
5397 }
5398
5399 /**
5400 * Default options
5401 * @type {{duration: number, easing: string, offset: number, scrollContainer: string}}
5402 * @private
5403 */
5404 ScrollButton.DEFAULTS_ = {
5405 duration: 300,
5406 easing: 'swing',
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00005407 offset: '.dac-header',
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005408 scrollContainer: 'html, body'
5409 };
5410
5411 /**
5412 * Scroll logic
5413 * @param event
5414 * @private
5415 */
5416 ScrollButton.prototype.clickHandler_ = function(event) {
5417 if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
5418 return;
5419 }
5420
5421 event.preventDefault();
5422
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00005423 var position = this.getTargetPosition();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005424 $(this.options.scrollContainer).animate({
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00005425 scrollTop: position - this.options.offset
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005426 }, this.options);
5427 };
5428
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00005429 ScrollButton.prototype.getTargetPosition = function() {
5430 if (this.options.scrollContainer === ScrollButton.DEFAULTS_.scrollContainer) {
5431 return this.target.offset().top;
5432 }
5433 var scrollContainer = $(this.options.scrollContainer)[0];
5434 var currentEl = this.target[0];
5435 var pos = 0;
5436 while (currentEl !== scrollContainer && currentEl !== null) {
5437 pos += currentEl.offsetTop;
5438 currentEl = currentEl.offsetParent;
5439 }
5440 return pos;
5441 };
5442
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005443 /**
5444 * jQuery plugin
5445 * @param {object} options - Override default options.
5446 */
5447 $.fn.dacScrollButton = function(options) {
5448 return this.each(function() {
5449 new ScrollButton(this, options);
5450 });
5451 };
5452
5453 /**
5454 * Data Attribute API
5455 */
5456 $(document).on('ready.aranja', function() {
5457 $('[data-scroll-button]').each(function() {
5458 $(this).dacScrollButton($(this).data());
5459 });
5460 });
5461})(jQuery);
5462
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00005463/* global getLangPref */
5464(function($) {
5465 var LANG;
5466
5467 function getSearchLang() {
5468 if (!LANG) {
5469 LANG = getLangPref();
5470
5471 // Fix zh-cn to be zh-CN.
5472 LANG = LANG.replace(/-\w+/, function(m) { return m.toUpperCase(); });
5473 }
5474 return LANG;
5475 }
5476
5477 function customSearch(query, start) {
5478 var searchParams = {
5479 // current cse instance:
5480 //cx: '001482626316274216503:zu90b7s047u',
5481 // new cse instance:
5482 cx: '000521750095050289010:zpcpi1ea4s8',
5483 key: 'AIzaSyCFhbGnjW06dYwvRCU8h_zjdpS4PYYbEe8',
5484 q: query,
5485 start: start || 1,
5486 num: 9,
5487 hl: getSearchLang(),
5488 fields: 'queries,items(pagemap,link,title,htmlSnippet,formattedUrl)'
5489 };
5490
5491 return $.get('https://content.googleapis.com/customsearch/v1?' + $.param(searchParams));
5492 }
5493
5494 function renderResults(el, results, searchAppliance) {
5495 var referenceResults = searchAppliance.getReferenceResults();
5496 if (!results.items) {
5497 el.append($('<div>').text('No results'));
5498 return;
5499 }
5500
5501 for (var i = 0; i < results.items.length; i++) {
5502 var item = results.items[i];
5503 var isDuplicate = false;
5504 $(referenceResults.android).each(function(index, result) {
5505 if (item.link.indexOf(result.link) > -1) {
5506 isDuplicate = true;
5507 return false;
5508 }
5509 });
5510
5511 if (!isDuplicate) {
5512 var hasImage = item.pagemap && item.pagemap.cse_thumbnail;
5513 var sectionMatch = item.link.match(/developer\.android\.com\/(\w*)/);
5514 var section = (sectionMatch && sectionMatch[1]) || 'blog';
5515
5516 var entry = $('<div>').addClass('dac-custom-search-entry cols');
5517
5518 if (hasImage) {
5519 var image = item.pagemap.cse_thumbnail[0];
5520 entry.append($('<div>').addClass('dac-custom-search-image-wrapper')
5521 .append($('<div>').addClass('dac-custom-search-image').css('background-image', 'url(' + image.src + ')')));
5522 }
5523
5524 entry.append($('<div>').addClass('dac-custom-search-text-wrapper')
5525 .append($('<p>').addClass('dac-custom-search-section').text(section))
5526 .append(
5527 $('<a>').text(item.title).attr('href', item.link).wrap('<h2>').parent().addClass('dac-custom-search-title')
5528 )
5529 .append($('<p>').addClass('dac-custom-search-snippet').html(item.htmlSnippet.replace(/<br>/g, '')))
5530 .append($('<a>').addClass('dac-custom-search-link').text(item.formattedUrl).attr('href', item.link)));
5531
5532 el.append(entry);
5533 }
5534 }
5535
5536 if (results.queries.nextPage) {
5537 var loadMoreButton = $('<button id="dac-custom-search-load-more">')
5538 .addClass('dac-custom-search-load-more')
5539 .text('Load more')
5540 .click(function() {
5541 loadMoreResults(el, results, searchAppliance);
5542 });
5543
5544 el.append(loadMoreButton);
5545 }
5546 };
5547
5548 function loadMoreResults(el, results, searchAppliance) {
5549 var query = results.queries.request[0].searchTerms;
5550 var start = results.queries.nextPage[0].startIndex;
5551 var loadMoreButton = el.find('#dac-custom-search-load-more');
5552
5553 loadMoreButton.text('Loading more...');
5554
5555 customSearch(query, start).then(function(results) {
5556 loadMoreButton.remove();
5557 renderResults(el, results, searchAppliance);
5558 });
5559 }
5560
5561 $.fn.customSearch = function(query, searchAppliance) {
5562 var el = $(this);
5563
5564 customSearch(query).then(function(results) {
5565 el.empty();
5566 renderResults(el, results, searchAppliance);
5567 });
5568 };
5569})(jQuery);
5570
5571/* global METADATA */
5572
5573(function($) {
5574 $.fn.dacSearchRenderHero = function(resources, query) {
5575 var el = $(this);
5576 el.empty();
5577
5578 var resource = METADATA.searchHeroCollections[query];
5579
5580 if (resource) {
5581 el.dacHero(resource, true);
5582 el.show();
5583
5584 return true;
5585 } else {
5586 el.hide();
5587 }
5588 };
5589})(jQuery);
5590
5591(function($) {
5592 $.fn.dacSearchRenderReferences = function(results, query) {
5593 var referenceCard = $('.suggest-card.reference');
5594 referenceCard.data('searchreferences.dac', {results: results, query: query});
5595 renderResults(referenceCard, results, query, false);
5596 };
5597
5598 var ROW_COUNT_COLLAPSED = 20;
5599 var ROW_COUNT_EXPANDED = 40;
5600 var ROW_COUNT_GOOGLE_COLLAPSED = 1;
5601 var ROW_COUNT_GOOGLE_EXPANDED = 8;
5602
5603 function onSuggestionClick(e) {
smain@google.comeeeb9b82016-08-29 18:12:27 -07005604 devsite.analytics.trackAnalyticsEvent('event',
5605 'Suggestion Click', 'clicked: ' + $(e.currentTarget).attr('href'),
5606 'query: ' + $('#search_autocomplete').val().toLowerCase());
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00005607 }
5608
5609 function buildLink(match) {
5610 var link = $('<a>').attr('href', window.toRoot + match.link);
5611
5612 var label = match.label;
5613 var classNameStart = label.match(/[A-Z]/) ? label.search(/[A-Z]/) : label.lastIndexOf('.') + 1;
5614 var newLink = '<span class="namespace">' +
5615 label.substr(0, classNameStart) +
5616 '</span>' +
5617 label.substr(classNameStart, label.length);
5618
5619 link.html(newLink);
5620 return link;
5621 }
5622
5623 function buildSuggestion(match, query) {
5624 var li = $('<li>').addClass('dac-search-results-reference-entry');
5625
5626 var link = buildLink(match);
5627 link.highlightMatches(query);
5628 li.append(link);
5629 return li[0];
5630 }
5631
5632 function buildResults(results, query) {
5633 return results.map(function(match) {
5634 return buildSuggestion(match, query);
5635 });
5636 }
5637
5638 function renderAndroidResults(list, gMatches, query) {
5639 list.empty();
5640
smain@google.comeeeb9b82016-08-29 18:12:27 -07005641 var header = $('<li class="dac-search-results-reference-header">android APIs</li>');
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00005642 list.append(header);
5643
5644 if (gMatches.length > 0) {
5645 list.removeClass('no-results');
5646
5647 var resources = buildResults(gMatches, query);
5648 list.append(resources);
5649 return true;
5650 } else {
5651 list.append('<li class="dac-search-results-reference-entry-empty">No results</li>');
5652 }
5653 }
5654
5655 function renderGoogleDocsResults(list, gGoogleMatches, query) {
5656 list = $('.suggest-card.reference ul');
5657
5658 if (gGoogleMatches.length > 0) {
5659 list.append('<li class="dac-search-results-reference-header">in Google Services</li>');
5660
5661 var resources = buildResults(gGoogleMatches, query);
5662 list.append(resources);
5663
5664 return true;
5665 }
5666 }
5667
5668 function renderResults(referenceCard, results, query, expanded) {
5669 var list = referenceCard.find('ul');
5670 list.toggleClass('is-expanded', !!expanded);
5671
5672 // Figure out how many results we can show in our fixed size box.
5673 var total = expanded ? ROW_COUNT_EXPANDED : ROW_COUNT_COLLAPSED;
5674 var googleCount = expanded ? ROW_COUNT_GOOGLE_EXPANDED : ROW_COUNT_GOOGLE_COLLAPSED;
5675 googleCount = Math.max(googleCount, total - results.android.length);
5676 googleCount = Math.min(googleCount, results.docs.length);
5677
5678 if (googleCount > 0) {
5679 // If there are google results, reserve space for its header.
5680 googleCount++;
5681 }
5682
5683 var androidCount = Math.max(0, total - googleCount);
5684 if (androidCount === 0) {
5685 // Reserve space for "No reference results"
5686 googleCount--;
5687 }
5688
5689 renderAndroidResults(list, results.android.slice(0, androidCount), query);
5690 renderGoogleDocsResults(list, results.docs.slice(0, googleCount - 1), query);
5691
5692 var totalResults = results.android.length + results.docs.length;
5693 if (totalResults === 0) {
5694 list.addClass('no-results');
5695 }
5696
5697 // Tweak see more logic to account for references.
5698 var hasMore = totalResults > ROW_COUNT_COLLAPSED && !util.matchesMedia('mobile');
5699 if (hasMore) {
5700 // We can't actually show all matches, only as many as the expanded list
5701 // will fit, so we actually lie if the total results count is more
5702 var moreCount = Math.min(totalResults, ROW_COUNT_EXPANDED + ROW_COUNT_GOOGLE_EXPANDED);
5703 var $moreLink = $('<li class="dac-search-results-reference-entry-empty " data-toggle="show-more">see more matches</li>');
5704 list.append($moreLink.on('click', onToggleMore));
5705 }
5706 var searchEl = $('#search-resources');
5707 searchEl.toggleClass('dac-has-more', searchEl.hasClass('dac-has-more') || (hasMore && !expanded));
5708 searchEl.toggleClass('dac-has-less', searchEl.hasClass('dac-has-less') || (hasMore && expanded));
5709 }
5710
5711 function onToggleMore(e) {
5712 var link = $(e.currentTarget);
5713 var referenceCard = $('.suggest-card.reference');
5714 var data = referenceCard.data('searchreferences.dac');
5715
5716 if (util.matchesMedia('mobile')) { return; }
5717
5718 renderResults(referenceCard, data.results, data.query, link.data('toggle') === 'show-more');
5719 }
5720
5721 $(document).on('click', '.dac-search-results-resources [data-toggle="show-more"]', onToggleMore);
5722 $(document).on('click', '.dac-search-results-resources [data-toggle="show-less"]', onToggleMore);
5723 $(document).on('click', '.suggest-card.reference a', onSuggestionClick);
5724})(jQuery);
5725
5726(function($) {
5727 function highlightPage(query, page) {
5728 page.find('.title').highlightMatches(query);
5729 }
5730
5731 $.fn.dacSearchRenderResources = function(gDocsMatches, query) {
5732 this.resourceWidget(gDocsMatches, {
5733 itemsPerPage: 18,
5734 initialResults: 6,
5735 cardSizes: ['6x2'],
5736 onRenderPage: highlightPage.bind(null, query)
5737 });
5738
5739 return this;
5740 };
5741})(jQuery);
5742
5743/*global metadata */
5744
5745(function($, metadata) {
5746 'use strict';
5747
5748 function Search() {
5749 this.body = $('body');
5750 this.lastQuery = null;
5751 this.searchResults = $('#search-results');
5752 this.searchClose = $('[data-search-close]');
5753 this.searchClear = $('[data-search-clear]');
5754 this.searchInput = $('#search_autocomplete');
5755 this.searchResultsContent = $('#dac-search-results-content');
5756 this.searchResultsFor = $('#search-results-for');
5757 this.searchResultsHistory = $('#dac-search-results-history');
5758 this.searchResultsResources = $('#search-resources');
5759 this.searchResultsHero = $('#dac-search-results-hero');
5760 this.searchResultsReference = $('#dac-search-results-reference');
5761 this.searchHeader = $('[data-search]').data('search-input.dac');
smain@google.comeeeb9b82016-08-29 18:12:27 -07005762 this.pageNav = $('a[name=navigation]');
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00005763 this.currQueryReferenceResults = {};
smain@google.comeeeb9b82016-08-29 18:12:27 -07005764 this.isOpen = false;
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00005765 }
5766
5767 Search.prototype.init = function() {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00005768 this.searchHistory = window.dacStore('search-history');
5769
5770 this.searchInput.focus(this.onSearchChanged.bind(this));
smain@google.comeeeb9b82016-08-29 18:12:27 -07005771 this.searchInput.keypress(this.handleKeyboardShortcut.bind(this));
5772 this.pageNav.keyup(this.handleTabbedToNav.bind(this));
5773 this.searchResults.keyup(this.handleKeyboardShortcut.bind(this));
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00005774 this.searchInput.on('input', this.onSearchChanged.bind(this));
5775 this.searchClear.click(this.clear.bind(this));
5776 this.searchClose.click(this.close.bind(this));
5777
5778 this.customSearch = $.fn.debounce(function(query) {
5779 $('#dac-custom-search-results').customSearch(query, this);
5780 }.bind(this), 1000);
5781 // Start search shortcut (/)
5782 $('body').keyup(function(event) {
5783 if (event.which === 191 && $(event.target).is(':not(:input)')) {
5784 this.searchInput.focus();
5785 }
5786 }.bind(this));
5787
5788 $(window).on('popstate', this.onPopState.bind(this));
5789 $(window).hashchange(this.onHashChange.bind(this));
5790 this.onHashChange();
5791 };
5792
5793 Search.prototype.checkRedirectToIndex = function() {
5794 var query = this.getUrlQuery();
5795 var target = window.getLangTarget();
5796 var prefix = (target !== 'en') ? '/intl/' + target : '';
5797 var pathname = location.pathname.slice(prefix.length);
5798 if (query != null && pathname !== '/index.html') {
5799 location.href = prefix + '/index.html' + location.hash;
5800 return true;
5801 }
5802 };
5803
5804 Search.prototype.handleKeyboardShortcut = function(event) {
5805 // Close (esc)
5806 if (event.which === 27) {
5807 this.searchClose.trigger('click');
5808 event.preventDefault();
5809 }
5810
5811 // Previous result (up arrow)
5812 if (event.which === 38) {
5813 this.previousResult();
5814 event.preventDefault();
5815 }
5816
5817 // Next result (down arrow)
5818 if (event.which === 40) {
5819 this.nextResult();
5820 event.preventDefault();
5821 }
5822
5823 // Navigate to result (enter)
5824 if (event.which === 13) {
5825 this.navigateToResult();
5826 event.preventDefault();
5827 }
5828 };
5829
smain@google.comeeeb9b82016-08-29 18:12:27 -07005830 Search.prototype.handleTabbedToNav = function(event) {
5831 if (this.isOpen) {
5832 this.searchClose.trigger('click');
5833 }
5834 }
5835
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00005836 Search.prototype.goToResult = function(relativeIndex) {
5837 var links = this.searchResults.find('a').filter(':visible');
5838 var selectedLink = this.searchResults.find('.dac-selected');
5839
5840 if (selectedLink.length) {
5841 var found = $.inArray(selectedLink[0], links);
5842
5843 selectedLink.removeClass('dac-selected');
5844 links.eq(found + relativeIndex).addClass('dac-selected');
5845 return true;
5846 } else {
5847 if (relativeIndex > 0) {
5848 links.first().addClass('dac-selected');
5849 }
5850 }
5851 };
5852
5853 Search.prototype.previousResult = function() {
5854 this.goToResult(-1);
5855 };
5856
5857 Search.prototype.nextResult = function() {
5858 this.goToResult(1);
5859 };
5860
5861 Search.prototype.navigateToResult = function() {
5862 var query = this.getQuery();
5863 var selectedLink = this.searchResults.find('.dac-selected');
5864
5865 if (selectedLink.length) {
5866 selectedLink[0].click();
5867 } else {
5868 this.searchHistory.push(query);
5869 this.addQueryToUrl(query);
5870
5871 var isMobileOrTablet = typeof window.orientation !== 'undefined';
5872
5873 if (isMobileOrTablet) {
5874 this.searchInput.blur();
5875 }
5876 }
5877 };
5878
5879 Search.prototype.onHashChange = function() {
5880 var query = this.getUrlQuery();
5881 if (query != null && query !== this.getQuery()) {
5882 this.searchInput.val(query);
5883 this.onSearchChanged();
5884 }
5885 };
5886
5887 Search.prototype.clear = function() {
5888 this.searchInput.val('');
5889 window.location.hash = '';
5890 this.onSearchChanged();
5891 this.searchInput.focus();
5892 };
5893
5894 Search.prototype.close = function() {
5895 this.removeQueryFromUrl();
5896 this.searchInput.blur();
5897 this.hideOverlay();
smain@google.comeeeb9b82016-08-29 18:12:27 -07005898 this.pageNav.focus();
5899 this.isOpen = false;
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00005900 };
5901
5902 Search.prototype.getUrlQuery = function() {
5903 var queryMatch = location.hash.match(/q=(.*)&?/);
5904 return queryMatch && queryMatch[1] && decodeURI(queryMatch[1]);
5905 };
5906
5907 Search.prototype.getQuery = function() {
5908 return this.searchInput.val().replace(/(^ +)|( +$)/g, '');
5909 };
5910
5911 Search.prototype.getReferenceResults = function() {
5912 return this.currQueryReferenceResults;
5913 };
5914
5915 Search.prototype.onSearchChanged = function() {
5916 var query = this.getQuery();
5917
5918 this.showOverlay();
5919 this.render(query);
5920 };
5921
5922 Search.prototype.render = function(query) {
5923 if (this.lastQuery === query) { return; }
5924
5925 if (query.length < 2) {
5926 query = '';
5927 }
5928
5929 this.lastQuery = query;
5930 this.searchResultsFor.text(query);
5931
5932 // CSE results lag behind the metadata/reference results. We need to empty
5933 // the CSE results and add 'Loading' text so user's aren't looking at two
5934 // different sets of search results at one time.
5935 var $loadingEl =
5936 $('<div class="loadingCustomSearchResults">Loading Results...</div>');
5937 $('#dac-custom-search-results').empty().prepend($loadingEl);
5938
5939 this.customSearch(query);
5940 var metadataResults = metadata.search(query);
5941 this.searchResultsResources.dacSearchRenderResources(metadataResults.resources, query);
5942 this.searchResultsReference.dacSearchRenderReferences(metadataResults, query);
5943 this.currQueryReferenceResults = metadataResults;
5944 var hasHero = this.searchResultsHero.dacSearchRenderHero(metadataResults.resources, query);
5945 var hasQuery = !!query;
5946
5947 this.searchResultsReference.toggle(!hasHero);
5948 this.searchResultsContent.toggle(hasQuery);
5949 this.searchResultsHistory.toggle(!hasQuery);
5950 this.addQueryToUrl(query);
5951 this.pushState();
5952 };
5953
5954 Search.prototype.addQueryToUrl = function(query) {
5955 var hash = 'q=' + encodeURI(query);
5956
5957 if (query) {
5958 if (window.history.replaceState) {
5959 window.history.replaceState(null, '', '#' + hash);
5960 } else {
5961 window.location.hash = hash;
5962 }
5963 }
5964 };
5965
5966 Search.prototype.onPopState = function() {
5967 if (!this.getUrlQuery()) {
5968 this.hideOverlay();
5969 this.searchHeader.unsetActiveState();
5970 }
5971 };
5972
5973 Search.prototype.removeQueryFromUrl = function() {
5974 window.location.hash = '';
5975 };
5976
5977 Search.prototype.pushState = function() {
5978 if (window.history.pushState && !this.lastQuery.length) {
5979 window.history.pushState(null, '');
5980 }
5981 };
5982
5983 Search.prototype.showOverlay = function() {
smain@google.comeeeb9b82016-08-29 18:12:27 -07005984 this.isOpen = true;
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00005985 this.body.addClass('dac-modal-open dac-search-open');
5986 };
5987
5988 Search.prototype.hideOverlay = function() {
5989 this.body.removeClass('dac-modal-open dac-search-open');
5990 };
5991
5992 $(document).on('ready.aranja', function() {
5993 var search = new Search();
5994 search.init();
5995 });
5996})(jQuery, metadata);
5997
5998window.dacStore = (function(window) {
5999 /**
6000 * Creates a new persistent store.
6001 * If localStorage is unavailable, the items are stored in memory.
6002 *
6003 * @constructor
6004 * @param {string} name The name of the store
6005 * @param {number} maxSize The maximum number of items the store can hold.
6006 */
6007 var Store = function(name, maxSize) {
6008 var content = [];
6009
6010 var hasLocalStorage = !!window.localStorage;
6011
6012 if (hasLocalStorage) {
6013 try {
6014 content = JSON.parse(window.localStorage.getItem(name) || []);
6015 } catch (e) {
6016 // Store contains invalid data
6017 window.localStorage.removeItem(name);
6018 }
6019 }
6020
6021 function push(item) {
6022 if (content[0] === item) {
6023 return;
6024 }
6025
6026 content.unshift(item);
6027
6028 if (maxSize) {
6029 content.splice(maxSize, content.length);
6030 }
6031
6032 if (hasLocalStorage) {
6033 window.localStorage.setItem(name, JSON.stringify(content));
6034 }
6035 }
6036
6037 function all() {
6038 // Return a copy
6039 return content.slice();
6040 }
6041
6042 return {
6043 push: push,
6044 all: all
6045 };
6046 };
6047
6048 var stores = {
6049 'search-history': new Store('search-history', 3)
6050 };
6051
6052 /**
6053 * Get a named persistent store.
6054 * @param {string} name
6055 * @return {Store}
6056 */
6057 return function getStore(name) {
6058 return stores[name];
6059 };
6060})(window);
6061
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006062(function($) {
6063 'use strict';
6064
6065 /**
6066 * A component that swaps two dynamic height views with an animation.
6067 * Listens for the following events:
6068 * * swap-content: triggers SwapContent.swap_()
6069 * * swap-reset: triggers SwapContent.reset()
6070 * @param el
6071 * @param options
6072 * @constructor
6073 */
6074 function SwapContent(el, options) {
6075 this.el = $(el);
6076 this.options = $.extend({}, SwapContent.DEFAULTS_, options);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00006077 this.options.dynamic = this.options.dynamic === 'true';
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006078 this.containers = this.el.find(this.options.container);
6079 this.initiallyActive = this.containers.children('.' + this.options.activeClass).eq(0);
6080 this.el.on('swap-content', this.swap.bind(this));
6081 this.el.on('swap-reset', this.reset.bind(this));
smain@google.comeeeb9b82016-08-29 18:12:27 -07006082 this.el.find(this.options.swapButton).on('click keypress', function(e) {
6083 if (e.type == 'keypress' && e.which == 13 || e.type == 'click') {
6084 this.swap();
6085 }
6086 }.bind(this));
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006087 }
6088
6089 /**
6090 * SwapContent's default settings.
6091 * @type {{activeClass: string, container: string, transitionSpeed: number}}
6092 * @private
6093 */
6094 SwapContent.DEFAULTS_ = {
6095 activeClass: 'dac-active',
6096 container: '[data-swap-container]',
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00006097 dynamic: 'true',
6098 swapButton: '[data-swap-button]',
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006099 transitionSpeed: 500
6100 };
6101
6102 /**
6103 * Returns container's visible height.
6104 * @param container
6105 * @returns {number}
6106 */
6107 SwapContent.prototype.currentHeight = function(container) {
6108 return container.children('.' + this.options.activeClass).outerHeight();
6109 };
6110
6111 /**
6112 * Reset to show initial content
6113 */
6114 SwapContent.prototype.reset = function() {
6115 if (!this.initiallyActive.hasClass(this.initiallyActive)) {
6116 this.containers.children().toggleClass(this.options.activeClass);
6117 }
6118 };
6119
6120 /**
6121 * Complete the swap.
6122 */
6123 SwapContent.prototype.complete = function() {
6124 this.containers.height('auto');
6125 this.containers.trigger('swap-complete');
6126 };
6127
6128 /**
6129 * Perform the swap of content.
6130 */
6131 SwapContent.prototype.swap = function() {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006132 this.containers.each(function(index, container) {
6133 container = $(container);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00006134
6135 if (!this.options.dynamic) {
6136 container.children().toggleClass(this.options.activeClass);
6137 this.complete.bind(this);
smain@google.comeeeb9b82016-08-29 18:12:27 -07006138 $('.' + this.options.activeClass).focus();
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00006139 return;
6140 }
6141
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006142 container.height(this.currentHeight(container)).children().toggleClass(this.options.activeClass);
6143 container.animate({height: this.currentHeight(container)}, this.options.transitionSpeed,
6144 this.complete.bind(this));
6145 }.bind(this));
6146 };
6147
6148 /**
6149 * jQuery plugin
6150 * @param {object} options - Override default options.
6151 */
6152 $.fn.dacSwapContent = function(options) {
6153 return this.each(function() {
6154 new SwapContent(this, options);
6155 });
6156 };
6157
6158 /**
6159 * Data Attribute API
6160 */
6161 $(document).on('ready.aranja', function() {
6162 $('[data-swap]').each(function() {
6163 $(this).dacSwapContent($(this).data());
6164 });
6165 });
6166})(jQuery);
6167
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00006168/* Tabs */
6169(function($) {
6170 'use strict';
6171
6172 /**
6173 * @param {HTMLElement} el - The DOM element.
6174 * @param {Object} options
6175 * @constructor
6176 */
6177 function Tabs(el, options) {
6178 this.el = $(el);
6179 this.options = $.extend({}, Tabs.DEFAULTS_, options);
6180 this.init();
6181 }
6182
6183 Tabs.DEFAULTS_ = {
6184 activeClass: 'dac-active',
6185 viewDataAttr: 'tab-view',
6186 itemDataAttr: 'tab-item'
6187 };
6188
6189 Tabs.prototype.init = function() {
6190 var itemDataAttribute = '[data-' + this.options.itemDataAttr + ']';
6191 this.tabEl_ = this.el.find(itemDataAttribute);
6192 this.tabViewEl_ = this.el.find('[data-' + this.options.viewDataAttr + ']');
6193 this.el.on('click.dac-tabs', itemDataAttribute, this.changeTabs.bind(this));
6194 };
6195
6196 Tabs.prototype.changeTabs = function(event) {
6197 var current = $(event.currentTarget);
6198 var index = current.index();
6199
6200 if (current.hasClass(this.options.activeClass)) {
6201 current.add(this.tabViewEl_.eq(index)).removeClass(this.options.activeClass);
6202 } else {
6203 this.tabEl_.add(this.tabViewEl_).removeClass(this.options.activeClass);
6204 current.add(this.tabViewEl_.eq(index)).addClass(this.options.activeClass);
6205 }
6206 };
6207
6208 /**
6209 * jQuery plugin
6210 */
6211 $.fn.dacTabs = function() {
6212 return this.each(function() {
6213 var el = $(this);
6214 new Tabs(el, el.data());
6215 });
6216 };
6217
6218 /**
6219 * Data Attribute API
6220 */
6221 $(function() {
6222 $('[data-tabs]').dacTabs();
6223 });
6224})(jQuery);
6225
6226/* Toast Component */
6227(function($) {
6228 'use strict';
6229 /**
6230 * @constant
6231 * @type {String}
6232 */
6233 var LOCAL_STORAGE_KEY = 'toast-closed-index';
6234
6235 /**
6236 * Dictionary from local storage.
6237 */
6238 var toastDictionary = localStorage.getItem(LOCAL_STORAGE_KEY);
6239 toastDictionary = toastDictionary ? JSON.parse(toastDictionary) : {};
6240
6241 /**
6242 * Variable used for caching the body.
6243 */
6244 var bodyCached;
6245
6246 /**
6247 * @param {HTMLElement} el - The DOM element.
6248 * @param {Object} options
6249 * @constructor
6250 */
6251 function Toast(el, options) {
6252 this.el = $(el);
6253 this.options = $.extend({}, Toast.DEFAULTS_, options);
6254 this.init();
6255 }
6256
6257 Toast.DEFAULTS_ = {
6258 closeBtnClass: 'dac-toast-close-btn',
6259 closeDuration: 200,
6260 visibleClass: 'dac-visible',
6261 wrapClass: 'dac-toast-wrap'
6262 };
6263
6264 /**
6265 * Generate a close button.
6266 * @returns {*|HTMLElement}
6267 */
6268 Toast.prototype.closeBtn = function() {
6269 this.closeBtnEl = this.closeBtnEl || $('<button class="' + this.options.closeBtnClass + '">' +
smain@google.comeeeb9b82016-08-29 18:12:27 -07006270 '<span class="dac-button dac-raised dac-primary">OK</span>' +
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00006271 '</button>');
6272 return this.closeBtnEl;
6273 };
6274
6275 /**
6276 * Initialize a new toast element
6277 */
6278 Toast.prototype.init = function() {
6279 this.hash = this.el.text().replace(/[\s\n\t]/g, '').split('').slice(0, 128).join('');
6280
6281 if (toastDictionary[this.hash]) {
6282 return;
6283 }
6284
6285 this.closeBtn().on('click', this.onClickHandler.bind(this));
6286 this.el.find('.' + this.options.wrapClass).append(this.closeBtn());
6287 this.el.addClass(this.options.visibleClass);
6288 this.dynamicPadding(this.el.outerHeight());
6289 };
6290
6291 /**
6292 * Add padding to make sure all page is visible.
6293 */
6294 Toast.prototype.dynamicPadding = function(val) {
6295 var currentPadding = parseInt(bodyCached.css('padding-bottom') || 0);
6296 bodyCached.css('padding-bottom', val + currentPadding);
6297 };
6298
6299 /**
6300 * Remove a toast from the DOM
6301 */
6302 Toast.prototype.remove = function() {
6303 this.dynamicPadding(-this.el.outerHeight());
6304 this.el.remove();
6305 };
6306
6307 /**
6308 * Handle removal of the toast.
6309 */
6310 Toast.prototype.onClickHandler = function() {
6311 // Only fadeout toasts from top of stack. Others are removed immediately.
6312 var duration = this.el.index() === 0 ? this.options.closeDuration : 0;
6313 this.el.fadeOut(duration, this.remove.bind(this));
6314
6315 // Save closed state.
6316 toastDictionary[this.hash] = 1;
6317 localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(toastDictionary));
6318 };
6319
6320 /**
6321 * jQuery plugin
6322 * @param {object} options - Override default options.
6323 */
6324 $.fn.dacToast = function() {
6325 return this.each(function() {
6326 var el = $(this);
6327 new Toast(el, el.data());
6328 });
6329 };
6330
6331 /**
6332 * Data Attribute API
6333 */
6334 $(function() {
6335 bodyCached = $('#body-content');
6336 $('[data-toast]').dacToast();
6337 });
6338})(jQuery);
6339
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006340(function($) {
6341 function Toggle(el) {
6342 $(el).on('click.dac.togglesection', this.toggle);
6343 }
6344
6345 Toggle.prototype.toggle = function() {
6346 var $this = $(this);
6347
6348 var $parent = getParent($this);
6349 var isExpanded = $parent.hasClass('is-expanded');
6350
6351 transitionMaxHeight($parent.find('.dac-toggle-content'), !isExpanded);
6352 $parent.toggleClass('is-expanded');
6353
6354 return false;
6355 };
6356
6357 function getParent($this) {
6358 var selector = $this.attr('data-target');
6359
6360 if (!selector) {
6361 selector = $this.attr('href');
6362 selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '');
6363 }
6364
6365 var $parent = selector && $(selector);
6366
6367 $parent = $parent && $parent.length ? $parent : $this.closest('.dac-toggle');
6368
6369 return $parent.length ? $parent : $this.parent();
6370 }
6371
6372 /**
6373 * Runs a transition of max-height along with responsive styles which hide or expand the element.
6374 * @param $el
6375 * @param visible
6376 */
6377 function transitionMaxHeight($el, visible) {
6378 var contentHeight = $el.prop('scrollHeight');
6379 var targetHeight = visible ? contentHeight : 0;
6380 var duration = $el.transitionDuration();
6381
6382 // If we're hiding, first set the maxHeight we're transitioning from.
6383 if (!visible) {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00006384 $el.css({
6385 transitionDuration: '0s',
6386 maxHeight: contentHeight + 'px'
6387 })
6388 .resolveStyles()
6389 .css('transitionDuration', '');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006390 }
6391
6392 // Transition to new state
6393 $el.css('maxHeight', targetHeight);
6394
6395 // Reset maxHeight to css value after transition.
6396 setTimeout(function() {
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00006397 $el.css({
6398 transitionDuration: '0s',
6399 maxHeight: ''
6400 })
6401 .resolveStyles()
6402 .css('transitionDuration', '');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006403 }, duration);
6404 }
6405
6406 // Utility to get the transition duration for the element.
6407 $.fn.transitionDuration = function() {
6408 var d = $(this).css('transitionDuration') || '0s';
6409
6410 return +(parseFloat(d) * (/ms/.test(d) ? 1 : 1000)).toFixed(0);
6411 };
6412
6413 // jQuery plugin
6414 $.fn.toggleSection = function(option) {
6415 return this.each(function() {
6416 var $this = $(this);
6417 var data = $this.data('dac.togglesection');
6418 if (!data) {$this.data('dac.togglesection', (data = new Toggle(this)));}
6419 if (typeof option === 'string') {data[option].call($this);}
6420 });
6421 };
6422
6423 // Data api
6424 $(document)
6425 .on('click.toggle', '[data-toggle="section"]', Toggle.prototype.toggle);
6426})(jQuery);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00006427
6428(function(window) {
6429 /**
6430 * Media query breakpoints. Should match CSS.
6431 */
6432 var BREAKPOINTS = {
6433 mobile: [0, 719],
6434 tablet: [720, 959],
6435 desktop: [960, 9999]
6436 };
6437
6438 /**
6439 * Fisher-Yates Shuffle (Knuth shuffle).
6440 * @param {Array} input
6441 * @returns {Array} shuffled array.
6442 */
6443 function shuffle(input) {
6444 for (var i = input.length; i >= 0; i--) {
6445 var randomIndex = Math.floor(Math.random() * (i + 1));
6446 var randomItem = input[randomIndex];
6447 input[randomIndex] = input[i];
6448 input[i] = randomItem;
6449 }
6450
6451 return input;
6452 }
6453
6454 /**
6455 * Matches media breakpoints like in CSS.
6456 * @param {string} form of either mobile, tablet or desktop.
6457 */
6458 function matchesMedia(form) {
6459 var breakpoint = BREAKPOINTS[form];
6460 return window.innerWidth >= breakpoint[0] && window.innerWidth <= breakpoint[1];
6461 }
6462
6463 window.util = {
6464 shuffle: shuffle,
6465 matchesMedia: matchesMedia
6466 };
6467})(window);
6468
6469(function($, window) {
6470 'use strict';
6471
6472 var YouTubePlayer = (function() {
6473 var player;
6474
6475 function VideoPlayer() {
6476 this.mPlayerPaused = false;
6477 this.doneSetup = false;
6478 }
6479
6480 VideoPlayer.prototype.setup = function() {
6481 // loads the IFrame Player API code asynchronously.
6482 $.getScript('https://www.youtube.com/iframe_api');
6483
6484 // Add the shadowbox HTML to the body
6485 $('body').prepend(
6486'<div id="video-player" class="Video">' +
6487 '<div id="video-overlay" class="Video-overlay" />' +
6488 '<div class="Video-container">' +
6489 '<div class="Video-frame">' +
6490 '<span class="Video-loading">Loading&hellip;</span>' +
6491 '<div id="youTubePlayer"></div>' +
6492 '</div>' +
6493 '<div class="Video-controls">' +
6494 '<button id="picture-in-picture" class="Video-button Video-button--picture-in-picture">' +
6495 '<button id="close-video" class="Video-button Video-button--close" />' +
6496 '</div>' +
6497 '</div>' +
6498'</div>');
6499
6500 this.videoPlayer = $('#video-player');
6501
6502 var pictureInPictureButton = this.videoPlayer.find('#picture-in-picture');
6503 pictureInPictureButton.on('click.aranja', this.toggleMinimizeVideo.bind(this));
6504
6505 var videoOverlay = this.videoPlayer.find('#video-overlay');
6506 var closeButton = this.videoPlayer.find('#close-video');
6507 var closeVideo = this.closeVideo.bind(this);
6508 videoOverlay.on('click.aranja', closeVideo);
6509 closeButton.on('click.aranja', closeVideo);
6510
6511 this.doneSetup = true;
6512 };
6513
6514 VideoPlayer.prototype.startYouTubePlayer = function(videoId) {
6515 this.videoPlayer.show();
6516
6517 if (!this.isLoaded) {
6518 this.queueVideo = videoId;
6519 return;
6520 }
6521
6522 this.mPlayerPaused = false;
6523 // check if we've already created this player
6524 if (!this.youTubePlayer) {
6525 // check if there's a start time specified
6526 var idAndHash = videoId.split('#');
6527 var startTime = 0;
6528 if (idAndHash.length > 1) {
6529 startTime = idAndHash[1].split('t=')[1] !== undefined ? idAndHash[1].split('t=')[1] : 0;
6530 }
6531 // enable localized player
6532 var lang = getLangPref();
6533 var captionsOn = lang === 'en' ? 0 : 1;
6534
6535 this.youTubePlayer = new YT.Player('youTubePlayer', {
6536 height: 720,
6537 width: 1280,
6538 videoId: idAndHash[0],
6539 // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
6540 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
6541 // jscs:enable
6542 events: {
6543 'onReady': this.onPlayerReady.bind(this),
6544 'onStateChange': this.onPlayerStateChange.bind(this)
6545 }
6546 });
6547 } else {
6548 // if a video different from the one already playing was requested, cue it up
6549 if (videoId !== this.getVideoId()) {
6550 this.youTubePlayer.cueVideoById(videoId);
6551 }
6552 this.youTubePlayer.playVideo();
6553 }
6554 };
6555
6556 VideoPlayer.prototype.onPlayerReady = function(event) {
6557 if (!isMobile) {
6558 event.target.playVideo();
6559 this.mPlayerPaused = false;
6560 }
6561 };
6562
6563 VideoPlayer.prototype.toggleMinimizeVideo = function(event) {
6564 event.stopPropagation();
6565 this.videoPlayer.toggleClass('Video--picture-in-picture');
6566 };
6567
6568 VideoPlayer.prototype.closeVideo = function() {
6569 try {
6570 this.youTubePlayer.pauseVideo();
6571 } catch (e) {
6572 }
6573 this.videoPlayer.fadeOut(200, function() {
6574 this.videoPlayer.removeClass('Video--picture-in-picture');
6575 }.bind(this));
6576 };
6577
6578 VideoPlayer.prototype.getVideoId = function() {
6579 // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
6580 return this.youTubePlayer && this.youTubePlayer.getVideoData().video_id;
6581 // jscs:enable
6582 };
6583
6584 /* Track youtube playback for analytics */
6585 VideoPlayer.prototype.onPlayerStateChange = function(event) {
6586 var videoId = this.getVideoId();
6587 var currentTime = this.youTubePlayer && this.youTubePlayer.getCurrentTime();
6588
6589 // Video starts, send the video ID
6590 if (event.data === YT.PlayerState.PLAYING) {
6591 if (this.mPlayerPaused) {
smain@google.comeeeb9b82016-08-29 18:12:27 -07006592 devsite.analytics.trackAnalyticsEvent('event',
6593 'Videos', 'Resume', videoId);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00006594 } else {
6595 // track the start playing event so we know from which page the video was selected
smain@google.comeeeb9b82016-08-29 18:12:27 -07006596 devsite.analytics.trackAnalyticsEvent('event',
6597 'Videos', 'Start: ' + videoId, 'on: ' + document.location.href);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00006598 }
6599 this.mPlayerPaused = false;
6600 }
6601
6602 // Video paused, send video ID and video elapsed time
6603 if (event.data === YT.PlayerState.PAUSED) {
smain@google.comeeeb9b82016-08-29 18:12:27 -07006604 devsite.analytics.trackAnalyticsEvent('event',
6605 'Videos', 'Paused: ' + videoId, 'on: ' + currentTime);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00006606 this.mPlayerPaused = true;
6607 }
6608
6609 // Video finished, send video ID and video elapsed time
6610 if (event.data === YT.PlayerState.ENDED) {
smain@google.comeeeb9b82016-08-29 18:12:27 -07006611 devsite.analytics.trackAnalyticsEvent('event',
6612 'Videos', 'Finished: ' + videoId, 'on: ' + currentTime);
Amanda Kassay8bac6eb2016-05-26 17:58:23 +00006613 this.mPlayerPaused = true;
6614 }
6615 };
6616
6617 return {
6618 getPlayer: function() {
6619 if (!player) {
6620 player = new VideoPlayer();
6621 }
6622
6623 return player;
6624 }
6625 };
6626 })();
6627
6628 var videoPlayer = YouTubePlayer.getPlayer();
6629
6630 window.onYouTubeIframeAPIReady = function() {
6631 videoPlayer.isLoaded = true;
6632
6633 if (videoPlayer.queueVideo) {
6634 videoPlayer.startYouTubePlayer(videoPlayer.queueVideo);
6635 }
6636 };
6637
6638 function wrapLinkInPlayer(e) {
6639 e.preventDefault();
6640
6641 if (!videoPlayer.doneSetup) {
6642 videoPlayer.setup();
6643 }
6644
6645 var videoIdMatches = $(e.currentTarget).attr('href').match(/(?:youtu.be\/|v=)([^&]*)/);
6646 var videoId = videoIdMatches && videoIdMatches[1];
6647
6648 if (videoId) {
6649 videoPlayer.startYouTubePlayer(videoId);
6650 }
6651 }
6652
6653 $(document).on('click.video', 'a[href*="youtube.com/watch"], a[href*="youtu.be"]', wrapLinkInPlayer);
6654})(jQuery, window);
6655
6656/**
6657 * Wide table
6658 *
6659 * Wraps tables in a scrollable area so you can read them on mobile.
6660 */
6661(function($) {
6662 function initWideTable() {
6663 $('table.jd-sumtable').each(function(i, table) {
6664 $(table).wrap('<div class="dac-expand wide-table">');
6665 });
6666 }
6667
6668 $(function() {
6669 initWideTable();
6670 });
6671})(jQuery);
6672
6673/** Utilities */
6674
6675/* returns the given string with all HTML brackets converted to entities
6676 TODO: move this to the site's JS library */
6677function escapeHTML(string) {
6678 return string.replace(/</g,"&lt;")
6679 .replace(/>/g,"&gt;");
6680};
6681
6682function getQueryVariable(variable) {
6683 var query = window.location.search.substring(1);
6684 var vars = query.split("&");
6685 for (var i=0;i<vars.length;i++) {
6686 var pair = vars[i].split("=");
6687 if(pair[0] == variable){return pair[1];}
6688 }
6689 return(false);
6690};