blob: 9709f50bd9559597b74f3c7fae959a8f8af9aac6 [file] [log] [blame]
Scott Maine4d8f1b2012-06-21 18:03:05 -07001var cookie_namespace = 'android_developer';
Scott Maine4d8f1b2012-06-21 18:03:05 -07002var isMobile = false; // true if mobile, so we can adjust some layout
Scott Mainf6145542013-04-01 16:38:11 -07003var mPagePath; // initialized in ready() function
Scott Maine4d8f1b2012-06-21 18:03:05 -07004
Scott Main1b3db112012-07-03 14:06:22 -07005var basePath = getBaseUri(location.pathname);
smain@google.com4f3a05a2016-08-31 11:30:02 -07006var SITE_ROOT = toRoot + basePath.substring(1, basePath.indexOf("/", 1));
7
8// 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'];
Scott Main3b90aff2013-08-01 18:09:35 -070015
Scott Main25e73002013-03-27 15:24:06 -070016// Ensure that all ajax getScript() requests allow caching
17$.ajaxSetup({
18 cache: true
19});
Scott Maine4d8f1b2012-06-21 18:03:05 -070020
21/****** ON LOAD SET UP STUFF *********/
22
Scott Maine4d8f1b2012-06-21 18:03:05 -070023$(document).ready(function() {
smain@google.com698fff02014-11-20 20:39:33 -080024
Scott Maine4d8f1b2012-06-21 18:03:05 -070025 // prep nav expandos
smain@google.com4f3a05a2016-08-31 11:30:02 -070026 var pagePath = location.href.replace(location.hash, '');
Scott Maine4d8f1b2012-06-21 18:03:05 -070027 // account for intl docs by removing the intl/*/ path
28 if (pagePath.indexOf("/intl/") == 0) {
smain@google.com4f3a05a2016-08-31 11:30:02 -070029 pagePath = pagePath.substr(pagePath.indexOf("/", 6)); // start after intl/ to get last /
Scott Maine4d8f1b2012-06-21 18:03:05 -070030 }
Scott Mainac2aef52013-02-12 14:15:23 -080031
Scott Maine4d8f1b2012-06-21 18:03:05 -070032 if (pagePath.indexOf(SITE_ROOT) == 0) {
33 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
34 pagePath += 'index.html';
35 }
36 }
37
Scott Main01a25452013-02-12 17:32:27 -080038 // 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;
Scott Maine4d8f1b2012-06-21 18:03:05 -070041 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
Scott Mainf6145542013-04-01 16:38:11 -070060 // 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.com4f3a05a2016-08-31 11:30:02 -070063
64 // Check for params and remove them.
65 mPagePath = mPagePath.split('?')[0];
Scott Mainf6145542013-04-01 16:38:11 -070066 highlightSidenav();
Scott Mainac2aef52013-02-12 14:15:23 -080067
Scott Mainf6145542013-04-01 16:38:11 -070068 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -070069 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -070070 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -070071 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -080072 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -070073
74 // set up prev links
75 var $prevLink = [];
76 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -070077
Scott Maine4d8f1b2012-06-21 18:03:05 -070078 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
79false; // navigate across topic boundaries only in design docs
80 if ($prevListItem.length) {
smain@google.comc91ecb72014-06-23 10:22:23 -070081 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Scott Main5a1123e2012-09-26 12:51:28 -070082 // jump to last topic of previous section
83 $prevLink = $prevListItem.find('a:last');
84 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -070085 // 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');
Scott Main3b90aff2013-08-01 18:09:35 -070092
Scott Maine4d8f1b2012-06-21 18:03:05 -070093 // except if cross boundaries aren't allowed, and we're at the top of a section already
94 // (and there's another parent)
smain@google.com4f3a05a2016-08-31 11:30:02 -070095 if (!crossBoundaries && $parentListItem.hasClass('nav-section') &&
96 $selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -070097 $prevLink = [];
98 }
99 }
100
Scott Maine4d8f1b2012-06-21 18:03:05 -0700101 // set up next links
102 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700103 var startClass = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700104 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700105
Scott Main1a00f7f2013-10-29 11:11:19 -0700106 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700107 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700108 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700109
110 // if there aren't any children, go to the next section (required for About pages)
smain@google.com4f3a05a2016-08-31 11:30:02 -0700111 if ($nextLink.length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700112 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700113 } 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());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700117 }
Scott Main3b90aff2013-08-01 18:09:35 -0700118
Scott Main5a1123e2012-09-26 12:51:28 -0700119 // 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
Scott Maine4d8f1b2012-06-21 18:03:05 -0700122 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)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700127 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700128 isCrossingBoundary = true;
129 // no more topics in this section, jump to the first topic in the next section
smain@google.comabf34112014-06-23 11:39:02 -0700130 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Scott Main5a1123e2012-09-26 12:51:28 -0700131 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)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700133 if ($nextLink.length == 0) {
134 // if that doesn't work, we're at the end of the list, so disable NEXT link
smain@google.com4f3a05a2016-08-31 11:30:02 -0700135 $('.next-page-link').attr('href', '').addClass("disabled")
Scott Main1a00f7f2013-10-29 11:11:19 -0700136 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700137 // and completely hide the one in the footer
138 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700139 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700140 }
141 }
142 }
Scott Main5a1123e2012-09-26 12:51:28 -0700143
144 if (startClass) {
145 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
146
Scott Main3b90aff2013-08-01 18:09:35 -0700147 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700148 // then we need to add a bottom border to button
149 if (!$("#tb").length) {
150 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700151 }
Scott Main5a1123e2012-09-26 12:51:28 -0700152 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
153 $('.content-footer.next-class').show();
smain@google.com4f3a05a2016-08-31 11:30:02 -0700154 $('.next-page-link').attr('href', '')
Scott Main5a1123e2012-09-26 12:51:28 -0700155 .removeClass("hide").addClass("disabled")
156 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700157 // and completely hide the one in the footer
158 $('.content-footer .next-page-link').hide();
smain@google.com4f3a05a2016-08-31 11:30:02 -0700159 $('.content-footer .prev-page-link').hide();
160
Scott Main1a00f7f2013-10-29 11:11:19 -0700161 if ($nextLink.length) {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700162 $('.next-class-link').attr('href', $nextLink.attr('href'))
163 .removeClass("hide");
164
165 $('.content-footer .next-class-link').append($nextLink.html());
166
Scott Main1a00f7f2013-10-29 11:11:19 -0700167 $('.next-class-link').find('.new').empty();
168 }
Scott Main5a1123e2012-09-26 12:51:28 -0700169 } else {
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700170 $('.next-page-link').attr('href', $nextLink.attr('href'))
171 .removeClass("hide");
smain@google.com4f3a05a2016-08-31 11:30:02 -0700172 // for the footer link, also add the previous and next page titles
173 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 }
Scott Main5a1123e2012-09-26 12:51:28 -0700179 }
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 }
Scott Main3b90aff2013-08-01 18:09:35 -0700188 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700189 }
Scott Main3b90aff2013-08-01 18:09:35 -0700190
Scott Main5a1123e2012-09-26 12:51:28 -0700191 // 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');
Scott Maine7d75352014-05-22 15:50:56 -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 });
Scott Main3b90aff2013-08-01 18:09:35 -0700208
Scott Main5a1123e2012-09-26 12:51:28 -0700209 var $olClasses = $('<ol class="class-list"></ol>');
210 var $liClass;
Scott Main5a1123e2012-09-26 12:51:28 -0700211 var $h2Title;
212 var $pSummary;
213 var $olLessons;
214 var $liLesson;
215 $classLinks.each(function(index) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700216 $liClass = $('<li class="clearfix"></li>');
smain@google.com4f3a05a2016-08-31 11:30:02 -0700217 $h2Title = $('<a class="title" href="' + $(this).attr('href') + '"><h2 class="norule">' + $(this).html() + '</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700218 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700219
Scott Main5a1123e2012-09-26 12:51:28 -0700220 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700221
Scott Main5a1123e2012-09-26 12:51:28 -0700222 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700223
Scott Main5a1123e2012-09-26 12:51:28 -0700224 if ($lessons.length) {
Scott Main5a1123e2012-09-26 12:51:28 -0700225 $lessons.each(function(index) {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700226 $olLessons.append('<li><a href="' + $(this).attr('href') + '">' + $(this).html() + '</a></li>');
Scott Main5a1123e2012-09-26 12:51:28 -0700227 });
228 } else {
Scott Main5a1123e2012-09-26 12:51:28 -0700229 $pSummary.addClass('article');
230 }
231
Dirk Dougherty29e93432015-05-05 18:17:13 -0700232 $liClass.append($h2Title).append($pSummary).append($olLessons);
Scott Main5a1123e2012-09-26 12:51:28 -0700233 $olClasses.append($liClass);
234 });
smain@google.com4f3a05a2016-08-31 11:30:02 -0700235 $('#classes').append($olClasses);
Scott Main5a1123e2012-09-26 12:51:28 -0700236 }
237
Scott Maine4d8f1b2012-06-21 18:03:05 -0700238 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700239 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700240
Scott Maine4d8f1b2012-06-21 18:03:05 -0700241 // Set up play-on-hover <video> tags.
smain@google.com4f3a05a2016-08-31 11:30:02 -0700242 $('video.play-on-hover').bind('click', function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700243 $(this).get(0).load(); // in case the video isn't seekable
244 $(this).get(0).play();
245 });
246
smain@google.com4f3a05a2016-08-31 11:30:02 -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
Scott Maine4d8f1b2012-06-21 18:03:05 -0700253 // Set up tooltips
254 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700255 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700256 var $target = $(this);
257 var $tooltip = $('<div>')
258 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700259 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700260 .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 Doughertyf97b2ef2015-05-12 21:23:05 -0700286 if (history && history.replaceState) {
287 // Change url without scrolling.
288 history.replaceState({}, '', '#' + id);
289 } else {
290 document.location.hash = id;
291 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700292 }
293 });
294
295 //Loads the +1 button
smain@google.com4f3a05a2016-08-31 11:30:02 -0700296 //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);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700299});
Scott Main7e447ed2013-02-19 17:22:37 -0800300// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700301
Scott Mainad08f072013-08-20 16:49:57 -0700302function initExpandableNavItems(rootTag) {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700303 var toggleIcon = $(
304 rootTag + ' li.nav-section .nav-section-header .toggle-icon, ' +
305 rootTag + ' li.nav-section .nav-section-header a[href="#"]');
Scott Mainad08f072013-08-20 16:49:57 -0700306
smain@google.com4f3a05a2016-08-31 11:30:02 -0700307 toggleIcon.on('click keypress', function(e) {
308 if (e.type == 'keypress' && e.which == 13 || e.type == 'click') {
309 doNavToggle(this);
Scott Mainad08f072013-08-20 16:49:57 -0700310 }
311 });
Scott Mainf0093852013-08-22 11:37:11 -0700312
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 });
Scott Mainad08f072013-08-20 16:49:57 -0700320}
321
smain@google.com4f3a05a2016-08-31 11:30:02 -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);
Dirk Doughertyc3921652014-05-13 16:55:26 -0700336
smain@google.com4f3a05a2016-08-31 11:30:02 -0700337 // now expand me
338 section.closest('li').addClass('expanded');
339 section.children('ul').slideDown(250);
Dirk Doughertyc3921652014-05-13 16:55:26 -0700340 }
Dirk Doughertyc3921652014-05-13 16:55:26 -0700341}
342
Scott Maine624b3f2013-09-12 12:56:41 -0700343/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700344function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700345 // 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
Scott Mainf6145542013-04-01 16:38:11 -0700357 var $selListItem;
smain@google.com4f3a05a2016-08-31 11:30:02 -0700358 var breadcrumb = [];
359
Scott Mainf6145542013-04-01 16:38:11 -0700360 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700361 // Find this page's <li> in sidenav and set selected
362 $selListItem = $selNavLink.closest('li');
363 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700364
Scott Mainf6145542013-04-01 16:38:11 -0700365 // 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();
smain@google.com4f3a05a2016-08-31 11:30:02 -0700369
370 var link = $(this).find('a').first();
371
372 if (!$(this).is($selListItem)) {
373 breadcrumb.unshift(link)
374 }
Scott Mainf6145542013-04-01 16:38:11 -0700375 });
smain@google.com4f3a05a2016-08-31 11:30:02 -0700376
377 $('#nav').scrollIntoView($selNavLink);
Scott Mainf6145542013-04-01 16:38:11 -0700378 }
smain@google.com4f3a05a2016-08-31 11:30:02 -0700379
380 breadcrumb.forEach(function(link) {
381 link.dacCrumbs();
382 });
Scott Mainf6145542013-04-01 16:38:11 -0700383}
384
Scott Maine624b3f2013-09-12 12:56:41 -0700385function unHighlightSidenav() {
386 $("ul#nav li.selected").removeClass("selected");
387 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
388}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700389
Scott Maine4d8f1b2012-06-21 18:03:05 -0700390var 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
Scott Main498d7102013-08-21 15:47:38 -0700399$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700400 $("pre:not(.no-pretty-print)").addClass("prettyprint");
401 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700402});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700403
Scott Maine4d8f1b2012-06-21 18:03:05 -0700404/* Show popup dialogs */
405function showDialog(id) {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700406 $dialog = $("#" + id);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700407 $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
Scott Maine4d8f1b2012-06-21 18:03:05 -0700412/* ######### COOKIES! ########## */
413
414function readCookie(cookie) {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700415 var myCookie = cookie_namespace + "_" + cookie + "=";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700416 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
smain@google.com6bdcb982014-11-14 11:53:07 -0800431function writeCookie(cookie, val, section) {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700432 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=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700437 document.cookie = cookieValue;
438}
439
440/* ######### END COOKIES! ########## */
441
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700442/*
443 * Manages secion card states and nav resize to conclude loading
444 */
Dirk Doughertyc3921652014-05-13 16:55:26 -0700445(function() {
446 $(document).ready(function() {
447
Dirk Doughertyc3921652014-05-13 16:55:26 -0700448 // Stack hover states
449 $('.section-card-menu').each(function(index, el) {
450 var height = $(el).height();
smain@google.com4f3a05a2016-08-31 11:30:02 -0700451 $(el).css({height:height + 'px', position:'relative'});
Dirk Doughertyc3921652014-05-13 16:55:26 -0700452 var $cardInfo = $(el).find('.card-info');
453
454 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
455 });
456
Dirk Doughertyc3921652014-05-13 16:55:26 -0700457 });
458
459})();
460
Scott Maind7026f72013-06-17 15:08:49 -0700461/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -0700462
Scott Maine4d8f1b2012-06-21 18:03:05 -0700463function 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
Scott Maine4d8f1b2012-06-21 18:03:05 -0700483function 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
Scott Maind7026f72013-06-17 15:08:49 -0700491function hideNestedItems(list, toggle) {
492 $list = $(list);
493 // hide nested lists
smain@google.com4f3a05a2016-08-31 11:30:02 -0700494 if ($list.hasClass('showing')) {
Scott Maind7026f72013-06-17 15:08:49 -0700495 $("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 }
smain@google.com4f3a05a2016-08-31 11:30:02 -0700502 $(".more,.less", $(toggle)).toggle();
Scott Maind7026f72013-06-17 15:08:49 -0700503}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700504
smain@google.com95948b82014-06-16 19:24:25 -0700505/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
506function setupIdeDocToggle() {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700507 $("select.ide").change(function() {
smain@google.com95948b82014-06-16 19:24:25 -0700508 var selected = $(this).find("option:selected").attr("value");
509 $(".select-ide").hide();
smain@google.com4f3a05a2016-08-31 11:30:02 -0700510 $(".select-ide." + selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700511
smain@google.com95948b82014-06-16 19:24:25 -0700512 $("select.ide").val(selected);
513 });
514}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700515
Scott Maine4d8f1b2012-06-21 18:03:05 -0700516/* 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) {
Scott Maindc63dda2013-08-22 16:03:21 -0700519 var div = $(obj).closest(".toggle-content");
smain@google.com4f3a05a2016-08-31 11:30:02 -0700520 var toggleMe = $(".toggle-content-toggleme:eq(0)", div);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700521 if (div.hasClass("closed")) { // if it's closed, open it
522 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -0700523 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700524 div.removeClass("closed").addClass("open");
smain@google.com4f3a05a2016-08-31 11:30:02 -0700525 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot +
526 "assets/images/styles/disclosure_up.png");
Scott Maine4d8f1b2012-06-21 18:03:05 -0700527 } else { // if it's open, close it
528 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -0700529 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700530 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -0700531 div.find(".toggle-content").removeClass("open").addClass("closed")
532 .find(".toggle-content-toggleme").hide();
smain@google.com4f3a05a2016-08-31 11:30:02 -0700533 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot +
534 "assets/images/styles/disclosure_down.png");
Scott Maine4d8f1b2012-06-21 18:03:05 -0700535 });
536 }
537 return false;
538}
Scott Mainf5089842012-08-14 16:31:07 -0700539
Scott Maindb3678b2012-10-23 14:13:41 -0700540/* New version of expandable content */
smain@google.com4f3a05a2016-08-31 11:30:02 -0700541function toggleExpandable(link, id) {
542 if ($(id).is(':visible')) {
Scott Maindb3678b2012-10-23 14:13:41 -0700543 $(id).slideUp();
544 $(link).removeClass('expanded');
545 } else {
546 $(id).slideDown();
547 $(link).addClass('expanded');
548 }
549}
550
551function hideExpandable(ids) {
552 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -0800553 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -0700554}
555
Scott Main3b90aff2013-08-01 18:09:35 -0700556/*
Scott Mainf5089842012-08-14 16:31:07 -0700557 * 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
Scott Maineb410352013-01-14 19:03:40 -0800584 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -0700585 * 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
smain@google.com4f3a05a2016-08-31 11:30:02 -0700595(function($) {
596 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -0700597
smain@google.com4f3a05a2016-08-31 11:30:02 -0700598 //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
Scott Mainf5089842012-08-14 16:31:07 -0700610
smain@google.com4f3a05a2016-08-31 11:30:02 -0700611 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -0700612
smain@google.com4f3a05a2016-08-31 11:30:02 -0700613 //Set up a carousel for each
614 return this.each(function() {
Scott Mainf5089842012-08-14 16:31:07 -0700615
smain@google.com4f3a05a2016-08-31 11:30:02 -0700616 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;
Scott Mainf5089842012-08-14 16:31:07 -0700624
smain@google.com4f3a05a2016-08-31 11:30:02 -0700625 var li = $("li", ul);
626 var itemLength = li.size();
627 var curr = o.start;
Scott Mainf5089842012-08-14 16:31:07 -0700628
smain@google.com4f3a05a2016-08-31 11:30:02 -0700629 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"});
Scott Mainf5089842012-08-14 16:31:07 -0700632
smain@google.com4f3a05a2016-08-31 11:30:02 -0700633 var liSize = o.vertical ? height(li) : width(li);
634 var ulSize = liSize * itemLength;
635 var divSize = liSize;
Scott Mainf5089842012-08-14 16:31:07 -0700636
smain@google.com4f3a05a2016-08-31 11:30:02 -0700637 li.css({width: li.width(), height: li.height()});
638 ul.css(sizeCss, ulSize + "px").css(animCss, -(curr * liSize));
Scott Mainf5089842012-08-14 16:31:07 -0700639
smain@google.com4f3a05a2016-08-31 11:30:02 -0700640 div.css(sizeCss, divSize + "px");
Scott Main3b90aff2013-08-01 18:09:35 -0700641
smain@google.com4f3a05a2016-08-31 11:30:02 -0700642 //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 }
Scott Main3b90aff2013-08-01 18:09:35 -0700659
smain@google.com4f3a05a2016-08-31 11:30:02 -0700660 //Previous button
661 if (o.btnPrev)
Scott Mainf5089842012-08-14 16:31:07 -0700662 $(o.btnPrev).click(function(e) {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700663 e.preventDefault();
664 return go(curr - o.scroll);
Scott Mainf5089842012-08-14 16:31:07 -0700665 });
666
smain@google.com4f3a05a2016-08-31 11:30:02 -0700667 //Next button
668 if (o.btnNext)
Scott Mainf5089842012-08-14 16:31:07 -0700669 $(o.btnNext).click(function(e) {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700670 e.preventDefault();
671 return go(curr + o.scroll);
Scott Mainf5089842012-08-14 16:31:07 -0700672 });
Scott Maineb410352013-01-14 19:03:40 -0800673
smain@google.com4f3a05a2016-08-31 11:30:02 -0700674 //Pause button
675 if (o.btnPause)
Scott Maineb410352013-01-14 19:03:40 -0800676 $(o.btnPause).click(function(e) {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700677 e.preventDefault();
678 if ($(this).hasClass('paused')) {
679 startRotateTimer();
680 } else {
681 pauseRotateTimer();
682 }
Scott Maineb410352013-01-14 19:03:40 -0800683 });
Scott Main3b90aff2013-08-01 18:09:35 -0700684
smain@google.com4f3a05a2016-08-31 11:30:02 -0700685 //Auto rotation
686 if (o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -0700687
smain@google.com4f3a05a2016-08-31 11:30:02 -0700688 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 }
Scott Maineb410352013-01-14 19:03:40 -0800699
smain@google.com4f3a05a2016-08-31 11:30:02 -0700700 function pauseRotateTimer() {
701 clearInterval(timer);
702 $(o.btnPause).addClass('paused');
703 }
Scott Mainf5089842012-08-14 16:31:07 -0700704
smain@google.com4f3a05a2016-08-31 11:30:02 -0700705 //Go to an item
706 function go(to) {
707 if (!running) {
Scott Mainf5089842012-08-14 16:31:07 -0700708
smain@google.com4f3a05a2016-08-31 11:30:02 -0700709 if (to < 0) {
710 to = itemLength - 1;
711 } else if (to > itemLength - 1) {
712 to = 0;
713 }
714 curr = to;
Scott Mainf5089842012-08-14 16:31:07 -0700715
smain@google.com4f3a05a2016-08-31 11:30:02 -0700716 running = true;
Scott Mainf5089842012-08-14 16:31:07 -0700717
smain@google.com4f3a05a2016-08-31 11:30:02 -0700718 ul.animate(
719 animCss == "left" ? {left: -(curr * liSize)} : {top: -(curr * liSize)} , o.speed, o.easing,
Scott Mainf5089842012-08-14 16:31:07 -0700720 function() {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700721 running = false;
Scott Mainf5089842012-08-14 16:31:07 -0700722 }
723 );
724
smain@google.com4f3a05a2016-08-31 11:30:02 -0700725 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
726 $((curr - o.scroll < 0 && o.btnPrev) ||
727 (curr + o.scroll > itemLength && o.btnNext) ||
728 []
729 ).addClass("disabled");
Scott Mainf5089842012-08-14 16:31:07 -0700730
smain@google.com4f3a05a2016-08-31 11:30:02 -0700731 var nav_items = $('li', pagination);
732 nav_items.removeClass('active');
733 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -0700734
smain@google.com4f3a05a2016-08-31 11:30:02 -0700735 }
736 if (o.auto) startRotateTimer();
737 return false;
738 };
739 });
740 };
Scott Main3b90aff2013-08-01 18:09:35 -0700741
smain@google.com4f3a05a2016-08-31 11:30:02 -0700742 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 };
Scott Mainf5089842012-08-14 16:31:07 -0700751
smain@google.com4f3a05a2016-08-31 11:30:02 -0700752})(jQuery);
Scott Mainf5089842012-08-14 16:31:07 -0700753
Scott Main3b90aff2013-08-01 18:09:35 -0700754/*
Scott Mainf5089842012-08-14 16:31:07 -0700755 * 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 */
smain@google.com4f3a05a2016-08-31 11:30:02 -0700791(function($) {
792 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -0700793
smain@google.com4f3a05a2016-08-31 11:30:02 -0700794 //Options - see above
795 o = $.extend({
796 speed : 250,
797 easing: null,
798 nav_id: null,
799 frame_id: null
800 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -0700801
smain@google.com4f3a05a2016-08-31 11:30:02 -0700802 //Set up a carousel for each
803 return this.each(function() {
Scott Mainf5089842012-08-14 16:31:07 -0700804
smain@google.com4f3a05a2016-08-31 11:30:02 -0700805 var curr = 0;
806 var running = false;
807 var animCss = "margin-left";
808 var sizeCss = "width";
809 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -0700810
smain@google.com4f3a05a2016-08-31 11:30:02 -0700811 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) {
Scott Mainf5089842012-08-14 16:31:07 -0700818 go($(nav_li).index($(this)));
819 })
Scott Main3b90aff2013-08-01 18:09:35 -0700820
smain@google.com4f3a05a2016-08-31 11:30:02 -0700821 //Go to an item
822 function go(to) {
823 if (!running) {
824 curr = to;
825 running = true;
Scott Mainf5089842012-08-14 16:31:07 -0700826
smain@google.com4f3a05a2016-08-31 11:30:02 -0700827 frame.animate({'margin-left' : -(curr * content_width)}, o.speed, o.easing,
Scott Mainf5089842012-08-14 16:31:07 -0700828 function() {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700829 running = false;
Scott Mainf5089842012-08-14 16:31:07 -0700830 }
831 );
832
smain@google.com4f3a05a2016-08-31 11:30:02 -0700833 nav_li.removeClass('active');
834 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -0700835
Scott Mainf5089842012-08-14 16:31:07 -0700836 }
837 return false;
smain@google.com4f3a05a2016-08-31 11:30:02 -0700838 };
Scott Main0e76e7e2013-03-12 10:24:07 -0700839 });
Amanda Kassay843649b2016-03-17 14:11:13 -0400840 };
Scott Mainf5089842012-08-14 16:31:07 -0700841
smain@google.com4f3a05a2016-08-31 11:30:02 -0700842 function css(el, prop) {
843 return parseInt($.css(el[0], prop)) || 0;
Amanda Kassay843649b2016-03-17 14:11:13 -0400844 };
smain@google.com4f3a05a2016-08-31 11:30:02 -0700845 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 };
Scott Mainf5089842012-08-14 16:31:07 -0700851
smain@google.com4f3a05a2016-08-31 11:30:02 -0700852})(jQuery);
Scott Mainf5089842012-08-14 16:31:07 -0700853
854/* ######################################################## */
855/* ################# JAVADOC REFERENCE ################### */
856/* ######################################################## */
857
smain@google.com4f3a05a2016-08-31 11:30:02 -0700858
Scott Mainf5089842012-08-14 16:31:07 -0700859
860var API_LEVEL_COOKIE = "api_level";
861var minLevel = 1;
862var maxLevel = 1;
863
Scott Mainf5089842012-08-14 16:31:07 -0700864function buildApiLevelSelector() {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700865 maxLevel = API_LEVELS.length;
Scott Mainf5089842012-08-14 16:31:07 -0700866 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);
smain@google.com4f3a05a2016-08-31 11:30:02 -0700877 for (var i = maxLevel - 1; i >= 0; i--) {
878 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)
Scott Mainf5089842012-08-14 16:31:07 -0700880 select.append(option);
881 }
882
883 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
smain@google.com4f3a05a2016-08-31 11:30:02 -0700884 var selectedLevelItem = $("#apiLevelSelector option[value='" + userApiLevel + "']").get(0);
885 selectedLevelItem.setAttribute('selected', true);
Scott Mainf5089842012-08-14 16:31:07 -0700886}
887
888function changeApiLevel() {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700889 maxLevel = API_LEVELS.length;
890 minLevel = parseInt($('#doc-api-level').attr('class'));
Scott Mainf5089842012-08-14 16:31:07 -0700891 var selectedLevel = maxLevel;
892
893 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
894 toggleVisisbleApis(selectedLevel, "body");
895
smain@google.com6bdcb982014-11-14 11:53:07 -0800896 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -0700897
898 if (selectedLevel < minLevel) {
smain@google.com4f3a05a2016-08-31 11:30:02 -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();');
Scott Mainf5089842012-08-14 16:31:07 -0700904 }
905}
906
907function toggleVisisbleApis(selectedLevel, context) {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700908 var apis = $(".api", context);
Scott Mainf5089842012-08-14 16:31:07 -0700909 apis.each(function(i) {
910 var obj = $(this);
911 var className = obj.attr("class");
smain@google.com4f3a05a2016-08-31 11:30:02 -0700912 var apiLevelIndex = className.lastIndexOf("-") + 1;
Scott Mainf5089842012-08-14 16:31:07 -0700913 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)) {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700925 apiLevelNum = maxLevel;
Scott Mainf5089842012-08-14 16:31:07 -0700926 }
927
928 // Grey things out that aren't available and give a tooltip title
929 if (apiLevelNum > selectedLevelNum) {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700930 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");
Scott Mainf5089842012-08-14 16:31:07 -0700934 });
935}
936
Scott Mainf5089842012-08-14 16:31:07 -0700937/* ################# SIDENAV TREE VIEW ################### */
Robert Lyd2dd6e52012-11-29 21:28:48 -0800938/* TODO: eliminate redundancy with non-google functions */
smain@google.com4f3a05a2016-08-31 11:30:02 -0700939function init_google_navtree(navtree_id, toroot, root_nodes) {
Robert Lyd2dd6e52012-11-29 21:28:48 -0800940 var me = new Object();
941 me.toroot = toroot;
942 me.node = new Object();
943
944 me.node.li = document.getElementById(navtree_id);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700945 if (!me.node.li) {
946 return;
947 }
948
Robert Lyd2dd6e52012-11-29 21:28:48 -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);
Robert Lyd2dd6e52012-11-29 21:28:48 -0800958}
959
smain@google.com4f3a05a2016-08-31 11:30:02 -0700960function new_google_node(me, mom, text, link, children_data, api_level) {
Robert Lyd2dd6e52012-11-29 21:28:48 -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) {
Scott Main3b90aff2013-08-01 18:09:35 -0700968 node.children_ul = document.createElement("ul");
969 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -0800970 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);
Scott Main3b90aff2013-08-01 18:09:35 -0700977
smain@google.com4f3a05a2016-08-31 11:30:02 -0700978 if (link) {
Robert Lyd2dd6e52012-11-29 21:28:48 -0800979 child = document.createElement("a");
980
smain@google.com4f3a05a2016-08-31 11:30:02 -0700981 } else {
Robert Lyd2dd6e52012-11-29 21:28:48 -0800982 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -0800983 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -0800984
985 }
986 if (children_data != null) {
smain@google.com4f3a05a2016-08-31 11:30:02 -0700987 node.li.className = "nav-section";
Robert Lyd2dd6e52012-11-29 21:28:48 -0800988 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -0700989 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -0800990 node.li.appendChild(node.label_div);
991 get_google_node(me, node);
992 node.label_div.appendChild(child);
smain@google.com4f3a05a2016-08-31 11:30:02 -0700993 } else {
Robert Lyd2dd6e52012-11-29 21:28:48 -0800994 node.li.appendChild(child);
995 }
smain@google.com4f3a05a2016-08-31 11:30:02 -0700996 if (link) {
Robert Lyd2dd6e52012-11-29 21:28:48 -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
smain@google.com4f3a05a2016-08-31 11:30:02 -07001007function get_google_node(me, mom) {
Robert Lyd2dd6e52012-11-29 21:28:48 -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
smain@google.com4f3a05a2016-08-31 11:30:02 -07001014 if (linkText.match("^" + "com.google.android") == "com.google.android") {
Robert Lyd2dd6e52012-11-29 21:28:48 -08001015 linkText = linkText.substr(19, linkText.length);
1016 }
smain@google.com4f3a05a2016-08-31 11:30:02 -07001017 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
1018 node_data[2], node_data[3]);
Robert Lyd2dd6e52012-11-29 21:28:48 -08001019 }
1020}
Scott Mainad08f072013-08-20 16:49:57 -07001021
Scott Mainad08f072013-08-20 16:49:57 -07001022/****** NEW version of script to build google and sample navs dynamically ******/
1023// TODO: update Google reference docs to tolerate this new implementation
1024
Scott Maine624b3f2013-09-12 12:56:41 -07001025var NODE_NAME = 0;
1026var NODE_HREF = 1;
1027var NODE_GROUP = 2;
1028var NODE_TAGS = 3;
1029var NODE_CHILDREN = 4;
1030
smain@google.com4f3a05a2016-08-31 11:30:02 -07001031function init_google_navtree2(navtree_id, data) {
1032 var $containerUl = $("#" + navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07001033 for (var i in data) {
1034 var node_data = data[i];
1035 $containerUl.append(new_google_node2(node_data));
1036 }
1037
Scott Main70557ee2013-10-30 14:47:40 -07001038 // Make all third-generation list items 'sticky' to prevent them from collapsing
1039 $containerUl.find('li li li.nav-section').addClass('sticky');
1040
smain@google.com4f3a05a2016-08-31 11:30:02 -07001041 initExpandableNavItems("#" + navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07001042}
1043
smain@google.com4f3a05a2016-08-31 11:30:02 -07001044function new_google_node2(node_data) {
Scott Maine624b3f2013-09-12 12:56:41 -07001045 var linkText = node_data[NODE_NAME];
smain@google.com4f3a05a2016-08-31 11:30:02 -07001046 if (linkText.match("^" + "com.google.android") == "com.google.android") {
Scott Mainad08f072013-08-20 16:49:57 -07001047 linkText = linkText.substr(19, linkText.length);
1048 }
1049 var $li = $('<li>');
1050 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07001051 if (node_data[NODE_HREF] != null) {
smain@google.com4f3a05a2016-08-31 11:30:02 -07001052 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >' +
1053 linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07001054 } else {
smain@google.com4f3a05a2016-08-31 11:30:02 -07001055 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >' +
1056 linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07001057 }
1058 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07001059 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07001060 $li.addClass("nav-section");
1061 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07001062 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07001063
Scott Maine624b3f2013-09-12 12:56:41 -07001064 for (var i in node_data[NODE_CHILDREN]) {
1065 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07001066 $childUl.append(new_google_node2(child_node_data));
1067 }
1068 $li.append($childUl);
1069 }
1070 $li.prepend($a);
1071
1072 return $li;
1073}
1074
Robert Lyd2dd6e52012-11-29 21:28:48 -08001075function showGoogleRefTree() {
1076 init_default_google_navtree(toRoot);
1077 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08001078}
1079
1080function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07001081 // load json file for navtree data
1082 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
smain@google.com4f3a05a2016-08-31 11:30:02 -07001083 // 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 }
Scott Mainf6145542013-04-01 16:38:11 -07001088 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08001089}
1090
1091function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07001092 // load json file for navtree data
1093 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
smain@google.com4f3a05a2016-08-31 11:30:02 -07001094 // 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 Dougherty4f7e5152010-09-16 10:43:40 -07001099 });
1100}
1101
Scott Mainf5089842012-08-14 16:31:07 -07001102/* 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) {
smain@google.com4f3a05a2016-08-31 11:30:02 -07001110 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;
Scott Mainf5089842012-08-14 16:31:07 -07001129}
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);
smain@google.com4f3a05a2016-08-31 11:30:02 -07001140 if ((expand == null && a.text() == "[Expand]") || expand) {
Scott Mainf5089842012-08-14 16:31:07 -07001141 expandos.each(function(i) {
1142 toggleInherited(this, true);
1143 });
1144 a.text("[Collapse]");
smain@google.com4f3a05a2016-08-31 11:30:02 -07001145 } else if ((expand == null && a.text() == "[Collapse]") || (expand == false)) {
Scott Mainf5089842012-08-14 16:31:07 -07001146 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
Scott Mainf5089842012-08-14 16:31:07 -07001182/* 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
smain@google.com4f3a05a2016-08-31 11:30:02 -07001188$(document).keydown(function(e) {
1189 var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
Scott Mainf5089842012-08-14 16:31:07 -07001190 if (control && e.which == 70) { // 70 is "F"
1191 ensureAllInheritedExpanded();
1192 }
1193});
Scott Main498d7102013-08-21 15:47:38 -07001194
Scott Main498d7102013-08-21 15:47:38 -07001195/* 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');
smain@google.com4f3a05a2016-08-31 11:30:02 -07001218 $(id).css('background', '#e7e7e7');
Scott Main498d7102013-08-21 15:47:38 -07001219 });
1220 $("#codesample-line-numbers a.number").mouseout(function() {
1221 var id = $(this).attr('href');
smain@google.com4f3a05a2016-08-31 11:30:02 -07001222 $(id).css('background', 'none');
Scott Main498d7102013-08-21 15:47:38 -07001223 });
1224 });
1225}
1226
1227// create SHIFT key binder to avoid the selectText method when selecting multiple lines
1228var shifted = false;
smain@google.com4f3a05a2016-08-31 11:30:02 -07001229$(document).bind('keyup keydown', function(e) {
1230 shifted = e.shiftKey; return true;
1231});
Scott Main498d7102013-08-21 15:47:38 -07001232
1233// courtesy of jasonedelman.com
1234function selectText(element) {
smain@google.com4f3a05a2016-08-31 11:30:02 -07001235 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 }
Scott Main285f0772013-08-22 23:22:09 +00001249}
Scott Main03aca9a2013-10-31 07:20:55 -07001250
Scott Main03aca9a2013-10-31 07:20:55 -07001251/** 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() {
smain@google.com4f3a05a2016-08-31 11:30:02 -07001261 var $li = $("<li>").append($(this).find("a").first().clone());
1262 var $samplesLink = $li.find("a");
1263 if ($samplesLink.text().endsWith('/')) {
1264 $samplesLink.text($samplesLink.text().slice(0,-1));
1265 }
1266 $ul.append($li);
Scott Main03aca9a2013-10-31 07:20:55 -07001267 });
1268
1269 $("#samples").append($ul);
1270
1271}
Dirk Doughertyc3921652014-05-13 16:55:26 -07001272
Dirk Doughertyc3921652014-05-13 16:55:26 -07001273/* ########################################################## */
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 Doughertyc3921652014-05-13 16:55:26 -07001281 $(document).ready(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001282 // Need to initialize hero carousel before other sections for dedupe
1283 // to work correctly.
1284 $('[data-carousel-query]').dacCarouselQuery();
1285
smain@google.com4f3a05a2016-08-31 11:30:02 -07001286 // Iterate over all instances and initialize a resource widget.
1287 $('.resource-widget').resourceWidget();
Dirk Doughertyc3921652014-05-13 16:55:26 -07001288 });
1289
smain@google.com4f3a05a2016-08-31 11:30:02 -07001290 $.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'),
Robert Lye7eeb402014-06-03 19:35:24 -07001299 /* Added by LFL 6/6/14 */
smain@google.com4f3a05a2016-08-31 11:30:02 -07001300 resourceStyle: this.data('resourcestyle') || 'card',
1301 stackSort: this.data('stacksort') || 'true',
1302 // For filter based resources
1303 allowDuplicates: this.data('allow-duplicates') || 'false'
Dirk Doughertyc3921652014-05-13 16:55:26 -07001304 };
smain@google.com4f3a05a2016-08-31 11:30:02 -07001305 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07001306
smain@google.com4f3a05a2016-08-31 11:30:02 -07001307 $.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 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07001314
smain@google.com4f3a05a2016-08-31 11:30:02 -07001315 /*
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 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07001333
1334 if (isFlow) {
1335 drawResourcesFlowWidget($widget, opts, resources);
1336 } else if (isCarousel) {
1337 drawResourcesCarouselWidget($widget, opts, resources);
1338 } else if (isStack) {
smain@google.com4f3a05a2016-08-31 11:30:02 -07001339 opts.numStacks = $widget.data('numstacks');
1340 drawResourcesStackWidget($widget, opts, resources);
Dirk Doughertyc3921652014-05-13 16:55:26 -07001341 }
1342 }
1343
smain@google.com4f3a05a2016-08-31 11:30:02 -07001344 $.fn.resourceWidget = function(resources, options) {
1345 return this.each(function() {
1346 initResourceWidget(this, resources, options);
1347 });
1348 };
1349
Dirk Doughertyc3921652014-05-13 16:55:26 -07001350 /* Initializes a Resource Carousel Widget */
1351 function drawResourcesCarouselWidget($widget, opts, resources) {
1352 $widget.empty();
Dirk Dougherty29e93432015-05-05 18:17:13 -07001353 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07001354
1355 $widget.addClass('resource-card slideshow-container')
1356 .append($('<a>').addClass('slideshow-prev').text('Prev'))
1357 .append($('<a>').addClass('slideshow-next').text('Next'));
1358
smain@google.com4f3a05a2016-08-31 11:30:02 -07001359 var css = {'width': $widget.width() + 'px',
1360 'height': $widget.height() + 'px'};
Dirk Doughertyc3921652014-05-13 16:55:26 -07001361
1362 var $ul = $('<ul>');
1363
1364 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07001365 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07001366 .attr('href', cleanUrl(resources[i].url))
smain@google.com4f3a05a2016-08-31 11:30:02 -07001367 .decorateResourceCard(resources[i], plusone);
Dirk Doughertyc3921652014-05-13 16:55:26 -07001368
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 });
smain@google.com4f3a05a2016-08-31 11:30:02 -07001383 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07001384
Robert Lye7eeb402014-06-03 19:35:24 -07001385 /* Initializes a Resource Card Stack Widget (column-based layout)
1386 Modified by LFL 6/6/14
1387 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001388 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 Dougherty29e93432015-05-05 18:17:13 -07001391 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07001392 var cards = $widget.find('.resource-card').detach().toArray();
1393 var numStacks = opts.numStacks || 1;
1394 var $stacks = [];
Dirk Doughertyc3921652014-05-13 16:55:26 -07001395
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
Robert Lye7eeb402014-06-03 19:35:24 -07001404 if (sections) {
smain@google.com4f3a05a2016-08-31 11:30:02 -07001405 for (i = 0; i < sections.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -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))
smain@google.com4f3a05a2016-08-31 11:30:02 -07001412 .decorateResourceCard(sections[i].resource, plusone)[0]
Robert Lye7eeb402014-06-03 19:35:24 -07001413 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07001414
Robert Lye7eeb402014-06-03 19:35:24 -07001415 } else {
1416 cards.push(
1417 $('<div>')
1418 .addClass('resource-card section-card-menu')
smain@google.com4f3a05a2016-08-31 11:30:02 -07001419 .decorateResourceSection(sections[i], plusone)[0]
Robert Lye7eeb402014-06-03 19:35:24 -07001420 );
1421 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07001422 }
1423 }
1424
1425 cards = cards.concat(sectionResources);
1426
smain@google.com4f3a05a2016-08-31 11:30:02 -07001427 for (i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07001428 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07001429
Robert Lye7eeb402014-06-03 19:35:24 -07001430 if (opts.resourceStyle.indexOf('related') > -1) {
1431 $card.addClass('related-card');
1432 }
smain@google.com95948b82014-06-16 19:24:25 -07001433
Dirk Doughertyc3921652014-05-13 16:55:26 -07001434 cards.push($card[0]);
1435 }
1436
smain@google.com4f3a05a2016-08-31 11:30:02 -07001437 if (opts.stackSort !== 'false') {
1438 for (i = 0; i < cards.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -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 Doughertyc3921652014-05-13 16:55:26 -07001443
Robert Lye7eeb402014-06-03 19:35:24 -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 Doughertyc3921652014-05-13 16:55:26 -07001450 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07001451
Robert Lye7eeb402014-06-03 19:35:24 -07001452 $stacks[minIndex].append($(cards[i]));
1453 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07001454 }
smain@google.com4f3a05a2016-08-31 11:30:02 -07001455 }
smain@google.com95948b82014-06-16 19:24:25 -07001456
1457 /*
Robert Lye7eeb402014-06-03 19:35:24 -07001458 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 */
smain@google.com95948b82014-06-16 19:24:25 -07001461 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07001462 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07001463
Robert Lye7eeb402014-06-03 19:35:24 -07001464 // 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.
smain@google.com4f3a05a2016-08-31 11:30:02 -07001468 if (opts.resourceStyle === 'generic') {
Robert Lye7eeb402014-06-03 19:35:24 -07001469 $el = $('<div>')
1470 .addClass('resource')
1471 .attr('href', cleanUrl(resource.url))
1472 .decorateResource(resource, opts);
1473 } else {
1474 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07001475
Robert Lye7eeb402014-06-03 19:35:24 -07001476 $el = $('<a>')
1477 .addClass(cls)
1478 .attr('href', cleanUrl(resource.url))
1479 .decorateResourceCard(resource, plusone);
1480 }
smain@google.com95948b82014-06-16 19:24:25 -07001481
Robert Lye7eeb402014-06-03 19:35:24 -07001482 return $el;
1483 }
Quddus Chong2cb2f682015-09-04 14:45:46 -07001484
Dirk Dougherty29e93432015-05-05 18:17:13 -07001485 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) {
smain@google.com4f3a05a2016-08-31 11:30:02 -07001494 column.addClass('col-mobile-1of1');
Dirk Dougherty29e93432015-05-05 18:17:13 -07001495 }
1496 return column;
1497 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07001498
1499 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
1500 function drawResourcesFlowWidget($widget, opts, resources) {
smain@google.com4f3a05a2016-08-31 11:30:02 -07001501 // We'll be doing our own modifications to opts.
1502 opts = $.extend({}, opts);
1503
Dirk Dougherty29e93432015-05-05 18:17:13 -07001504 $widget.empty().addClass('cols');
smain@google.com4f3a05a2016-08-31 11:30:02 -07001505 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 Dougherty29e93432015-05-05 18:17:13 -07001524 var plusone = false; // stop showing plusone buttons on cards
smain@google.com4f3a05a2016-08-31 11:30:02 -07001525 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 Doughertyc3921652014-05-13 16:55:26 -07001529
smain@google.com4f3a05a2016-08-31 11:30:02 -07001530 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 Doughertycbe032f2015-05-22 11:41:40 -07001536
smain@google.com4f3a05a2016-08-31 11:30:02 -07001537 while (i < max) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07001538 var cardSize = cardSizes[j++ % cardSizes.length];
smain@google.com4f3a05a2016-08-31 11:30:02 -07001539 cardSize = cardSize.replace(/^\s+|\s+$/, '');
Quddus Chong2cb2f682015-09-04 14:45:46 -07001540
smain@google.com4f3a05a2016-08-31 11:30:02 -07001541 var column = createResponsiveFlowColumn(cardSize).appendTo(page);
Dirk Doughertyc3921652014-05-13 16:55:26 -07001542
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.
smain@google.com4f3a05a2016-08-31 11:30:02 -07001551 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1] +
1552 'x' + isStack[2] * isStack[3]) .appendTo(column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07001553 }
1554
1555 // Build each stack item or just a single item
1556 do {
1557 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07001558
Robert Lye7eeb402014-06-03 19:35:24 -07001559 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07001560
1561 $card.addClass('resource-card-' + cardSize +
smain@google.com4f3a05a2016-08-31 11:30:02 -07001562 ' resource-card-' + resource.type.toLowerCase());
smain@google.com95948b82014-06-16 19:24:25 -07001563
Dirk Doughertyc3921652014-05-13 16:55:26 -07001564 if (isStack) {
1565 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
smain@google.com4f3a05a2016-08-31 11:30:02 -07001566 if (++stackCount === parseInt(isStack[3])) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07001567 $card.addClass('resource-card-row-stack-last');
1568 stackCount = 0;
1569 }
1570 } else {
1571 stackCount = 0;
1572 }
1573
Dirk Dougherty29e93432015-05-05 18:17:13 -07001574 $card.appendTo($stackDiv || column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07001575
smain@google.com4f3a05a2016-08-31 11:30:02 -07001576 } 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);
1581 devsite.analytics.trackAnalyticsEvent('event',
1582 'Cards', 'Click More', clicks);
1583 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07001584 }
smain@google.com4f3a05a2016-08-31 11:30:02 -07001585
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);
1604 devsite.analytics.trackAnalyticsEvent('event', 'Cards', 'Click Less');
1605 }
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 Doughertyc3921652014-05-13 16:55:26 -07001617 }
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
smain@google.com4f3a05a2016-08-31 11:30:02 -07001627 function cleanUrl(url) {
Robert Lye7eeb402014-06-03 19:35:24 -07001628 if (url && url.indexOf('//') === -1) {
1629 url = toRoot + url;
1630 }
smain@google.com95948b82014-06-16 19:24:25 -07001631
Robert Lye7eeb402014-06-03 19:35:24 -07001632 return url;
1633 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07001634
smain@google.com4f3a05a2016-08-31 11:30:02 -07001635 // 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 Doughertyc3921652014-05-13 16:55:26 -07001638})();
1639
1640(function($) {
smain@google.com4f3a05a2016-08-31 11:30:02 -07001641 // A mapping from category and type values to new values or human presentable strings.
1642 var SECTION_MAP = {
1643 googleplay: 'google play'
1644 };
Robert Lye7eeb402014-06-03 19:35:24 -07001645
smain@google.com95948b82014-06-16 19:24:25 -07001646 /*
Robert Lye7eeb402014-06-03 19:35:24 -07001647 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');
smain@google.com95948b82014-06-16 19:24:25 -07001652
Robert Lye7eeb402014-06-03 19:35:24 -07001653 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07001654
Robert Lye7eeb402014-06-03 19:35:24 -07001655 if (resource.cta) {
1656 $description.append($('<a>').addClass('cta').html(resource.cta));
1657 }
smain@google.com95948b82014-06-16 19:24:25 -07001658
Robert Lye7eeb402014-06-03 19:35:24 -07001659 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07001660 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07001661 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07001662
Robert Lye7eeb402014-06-03 19:35:24 -07001663 $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 }
smain@google.com95948b82014-06-16 19:24:25 -07001669
Robert Lye7eeb402014-06-03 19:35:24 -07001670 return $description;
1671 }
smain@google.com95948b82014-06-16 19:24:25 -07001672
Dirk Doughertyc3921652014-05-13 16:55:26 -07001673 /* Simple jquery function to create dom for a standard resource card */
smain@google.com4f3a05a2016-08-31 11:30:02 -07001674 $.fn.decorateResourceCard = function(resource, plusone) {
1675 var section = resource.category || resource.type;
1676 section = (SECTION_MAP[section] || section).toLowerCase();
smain@google.com95948b82014-06-16 19:24:25 -07001677 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07001678 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07001679
Robert Lye7eeb402014-06-03 19:35:24 -07001680 if (imgUrl.indexOf('//') === -1) {
1681 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07001682 }
Robert Lye7eeb402014-06-03 19:35:24 -07001683
smain@google.com4f3a05a2016-08-31 11:30:02 -07001684 if (resource.type === 'youtube' || resource.type === 'video') {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07001685 $('<div>').addClass('play-button')
1686 .append($('<i class="dac-sprite dac-play-white">'))
1687 .appendTo(this);
1688 }
1689
Robert Lye7eeb402014-06-03 19:35:24 -07001690 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07001691 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07001692 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07001693 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07001694
Robert Lye7eeb402014-06-03 19:35:24 -07001695 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
1696 .append($('<div>').addClass('section').text(section))
smain@google.com4f3a05a2016-08-31 11:30:02 -07001697 .append($('<div>').addClass('title' + (resource.title_highlighted ? ' highlighted' : ''))
1698 .html(resource.title_highlighted || resource.title))
Robert Lye7eeb402014-06-03 19:35:24 -07001699 .append(buildResourceCardDescription(resource, plusone))
1700 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07001701
1702 return this;
1703 };
1704
1705 /* Simple jquery function to create dom for a resource section card (menu) */
smain@google.com4f3a05a2016-08-31 11:30:02 -07001706 $.fn.decorateResourceSection = function(section, plusone) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07001707 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];
1745 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 }
1766 }
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 };
smain@google.com95948b82014-06-16 19:24:25 -07001783
Robert Lye7eeb402014-06-03 19:35:24 -07001784 /* Render other types of resource styles that are not cards. */
1785 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07001786 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07001787 'assets/images/resource-card-default-android.jpg';
1788 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07001789
Robert Lye7eeb402014-06-03 19:35:24 -07001790 if (imgUrl.indexOf('//') === -1) {
1791 imgUrl = toRoot + imgUrl;
1792 }
smain@google.com95948b82014-06-16 19:24:25 -07001793
Robert Lye7eeb402014-06-03 19:35:24 -07001794 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(
smain@google.com4f3a05a2016-08-31 11:30:02 -07001802 $('<h4>').addClass('title').html(resource.title_highlighted || resource.title),
Robert Lye7eeb402014-06-03 19:35:24 -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 Doughertyc3921652014-05-13 16:55:26 -07001810})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07001811
Robert Lye7eeb402014-06-03 19:35:24 -07001812/*
1813 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07001814
Robert Lye7eeb402014-06-03 19:35:24 -07001815 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07001816 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07001817 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07001818
Robert Lye7eeb402014-06-03 19:35:24 -07001819 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07001820
Robert Lye7eeb402014-06-03 19:35:24 -07001821 <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>
smain@google.com95948b82014-06-16 19:24:25 -07001828
Robert Lye7eeb402014-06-03 19:35:24 -07001829 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07001830
Robert Lye7eeb402014-06-03 19:35:24 -07001831 </div>
smain@google.com95948b82014-06-16 19:24:25 -07001832
Robert Lye7eeb402014-06-03 19:35:24 -07001833 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
smain@google.com95948b82014-06-16 19:24:25 -07001835 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07001836 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07001837 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07001838 tall.
smain@google.com95948b82014-06-16 19:24:25 -07001839
Robert Lye7eeb402014-06-03 19:35:24 -07001840 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);
smain@google.com95948b82014-06-16 19:24:25 -07001854
Robert Lye7eeb402014-06-03 19:35:24 -07001855 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]);
smain@google.com95948b82014-06-16 19:24:25 -07001863
Robert Lye7eeb402014-06-03 19:35:24 -07001864 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 });
smain@google.com95948b82014-06-16 19:24:25 -07001874
Robert Lye7eeb402014-06-03 19:35:24 -07001875 $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());
smain@google.com95948b82014-06-16 19:24:25 -07001896 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07001897 (maxHeight && height > maxHeight ? maxHeight : height));
1898 }
smain@google.com95948b82014-06-16 19:24:25 -07001899 }
Robert Lye7eeb402014-06-03 19:35:24 -07001900})();
1901
Robert Lye7eeb402014-06-03 19:35:24 -07001902/*
1903 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07001904
Robert Lye7eeb402014-06-03 19:35:24 -07001905 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.
smain@google.com95948b82014-06-16 19:24:25 -07001909
Robert Lye7eeb402014-06-03 19:35:24 -07001910 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07001911
Robert Lye7eeb402014-06-03 19:35:24 -07001912 <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>
smain@google.com95948b82014-06-16 19:24:25 -07001918
Robert Lye7eeb402014-06-03 19:35:24 -07001919 <div class="tab-carousel-content">
1920 <div data-tab="handsets">
1921 <!--Full width content here-->
1922 </div>
smain@google.com95948b82014-06-16 19:24:25 -07001923
Robert Lye7eeb402014-06-03 19:35:24 -07001924 <div data-tab="wearable">
1925 <!--Full width content here-->
1926 </div>
smain@google.com95948b82014-06-16 19:24:25 -07001927
Robert Lye7eeb402014-06-03 19:35:24 -07001928 <div data-tab="tv">
1929 <!--Full width content here-->
1930 </div>
1931 </div>
1932 </div>
smain@google.com95948b82014-06-16 19:24:25 -07001933
Robert Lye7eeb402014-06-03 19:35:24 -07001934*/
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') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07001956 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07001957 });
smain@google.com95948b82014-06-16 19:24:25 -07001958
Robert Lye7eeb402014-06-03 19:35:24 -07001959 // 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);
smain@google.com95948b82014-06-16 19:24:25 -07001963
Robert Lye7eeb402014-06-03 19:35:24 -07001964 // Store height since we will change contents to absolute.
1965 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07001966
Robert Lye7eeb402014-06-03 19:35:24 -07001967 // 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 });
smain@google.com95948b82014-06-16 19:24:25 -07001971
Robert Lye7eeb402014-06-03 19:35:24 -07001972 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;
smain@google.com95948b82014-06-16 19:24:25 -07001977
Robert Lye7eeb402014-06-03 19:35:24 -07001978 // Animate content sections.
1979 $toTab.css({left:(width * dir) + 'px'});
1980 $curTab.animate({left:(width * -dir) + 'px'});
1981 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07001982
Robert Lye7eeb402014-06-03 19:35:24 -07001983 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07001984 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07001985 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07001986
Robert Lye7eeb402014-06-03 19:35:24 -07001987 // Store new current section.
1988 $curTab = $toTab;
1989 }
1990 }
smain@google.com95948b82014-06-16 19:24:25 -07001991 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08001992})();
Dirk Dougherty29e93432015-05-05 18:17:13 -07001993
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001994/**
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');
smain@google.com4f3a05a2016-08-31 11:30:02 -07002007 h2Titles.css({paddingBottom:0}).after('<hr/>');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07002008
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();
smain@google.com4f3a05a2016-08-31 11:30:02 -07002040 var $contents = allNextUntil($hr[0], 'h2, .next-docs');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07002041 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
Dirk Doughertycbe032f2015-05-22 11:41:40 -07002052 // 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
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07002057 // 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();
smain@google.com4f3a05a2016-08-31 11:30:02 -07002069
2070 // extra div used for max-height calculation.
2071 $contents.wrapAll('<div class="dac-toggle-content dac-expand"><div>');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07002072
Dirk Doughertycbe032f2015-05-22 11:41:40 -07002073 // 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
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07002083 // Add it back to the dom.
2084 $anchor[anchorMethod].call($anchor, $section);
2085 });
2086 }
2087
smain@google.com4f3a05a2016-08-31 11:30:02 -07002088 // 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 Doughertyf97b2ef2015-05-12 21:23:05 -07002101 $(function() {
2102 initWidget();
2103 });
2104})(jQuery);
2105
smain@google.com4f3a05a2016-08-31 11:30:02 -07002106(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);
2164 var formattedDate = monthNames[published.getMonth()] + ' ' + published.getDate() + ' ' + published.getFullYear();
2165 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
2233 $(document).on('click.blog-reader', 'a.resource-card[href*="blogspot.com/"]',
2234 wrapLinkWithReader);
2235})(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));
2566 devsite.analytics.trackAnalyticsEvent('event',
2567 'Filters', 'Check', $(checkbox).val());
2568 } else {
2569 item.data('chip.dac-filter').remove();
2570 this.addToItemValue(item, -1);
2571 devsite.analytics.trackAnalyticsEvent('event',
2572 'Filters', 'Uncheck', $(checkbox).val());
2573 }
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 Dougherty29e93432015-05-05 18:17:13 -07002596(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 Dougherty29e93432015-05-05 18:17:13 -07002652(function($) {
smain@google.com4f3a05a2016-08-31 11:30:02 -07002653 'use strict';
Dirk Dougherty29e93432015-05-05 18:17:13 -07002654
smain@google.com4f3a05a2016-08-31 11:30:02 -07002655 /**
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) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07002729 this.el = $(el);
smain@google.com4f3a05a2016-08-31 11:30:02 -07002730 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 Dougherty29e93432015-05-05 18:17:13 -07002738
smain@google.com4f3a05a2016-08-31 11:30:02 -07002739 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 Dougherty29e93432015-05-05 18:17:13 -07002820 opts.maxResults = parseInt(opts.maxResults || '100', 10);
2821 opts.query = opts.carouselQuery;
smain@google.com4f3a05a2016-08-31 11:30:02 -07002822 var resources = window.metadata.query(opts);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002823
smain@google.com4f3a05a2016-08-31 11:30:02 -07002824 el.empty();
2825 $(resources).each(function() {
2826 var resource = $.extend({}, this, METADATA.carousel[this.url]);
2827 el.dacHero(resource);
2828 });
Dirk Dougherty29e93432015-05-05 18:17:13 -07002829
2830 // Pagination element.
smain@google.com4f3a05a2016-08-31 11:30:02 -07002831 el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>');
Dirk Dougherty29e93432015-05-05 18:17:13 -07002832
smain@google.com4f3a05a2016-08-31 11:30:02 -07002833 el.dacCarousel();
Dirk Dougherty29e93432015-05-05 18:17:13 -07002834 }
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,
Dirk Doughertycbe032f2015-05-22 11:41:40 -07002880 swipeThreshold: 160,
Dirk Dougherty29e93432015-05-05 18:17:13 -07002881 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
Dirk Doughertycbe032f2015-05-22 11:41:40 -07002908 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
Dirk Dougherty29e93432015-05-05 18:17:13 -07002917 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
Dirk Doughertycbe032f2015-05-22 11:41:40 -07002934 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
Dirk Dougherty29e93432015-05-05 18:17:13 -07002958 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.
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07002992 this.el.resolveStyles();
2993 // Update pagination
2994 this.pagination.removeClass('active').eq(next).addClass('active');
Dirk Dougherty29e93432015-05-05 18:17:13 -07002995
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07002996 // Transition out current frame
2997 this.frames.eq(this.current).toggleClass('active out');
Dirk Dougherty29e93432015-05-05 18:17:13 -07002998
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07002999 // Transition in a new frame
3000 this.frames.eq(next).toggleClass('active');
Dirk Dougherty29e93432015-05-05 18:17:13 -07003001
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003002 this.current = next;
Dirk Dougherty29e93432015-05-05 18:17:13 -07003003 };
3004
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003005 // Helper which resolves new styles for an element, so it can start transitioning
3006 // from the new values.
3007 $.fn.resolveStyles = function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003008 /*jshint expr:true*/
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003009 this[0] && this[0].offsetTop;
3010 return this;
3011 };
Dirk Dougherty29e93432015-05-05 18:17:13 -07003012
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
smain@google.com4f3a05a2016-08-31 11:30:02 -07003028/* 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);
3301 $('#language').find('option[value="' + targetLang + '"]').attr('selected', true);
3302 if (submit) {
3303 $('#setlang').submit();
3304 }
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',
Dirk Doughertyfc617202016-09-21 18:29:18 -07003317 'id',
smain@google.com4f3a05a2016-08-31 11:30:02 -07003318 '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 (العربيّة)',
Dirk Doughertyfc617202016-09-21 18:29:18 -07003354 'id': 'Indonesian (Bahasa)',
smain@google.com4f3a05a2016-08-31 11:30:02 -07003355 '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() {
3388 var lang = getQueryVariable('hl');
3389 if (lang === false || LANGUAGES.indexOf(lang) === -1) {
3390 lang = locale;
3391 }
3392 return lang;
3393 })();
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;
Dirk Doughertyfc617202016-09-21 18:29:18 -07003612 case 'id':
smain@google.com4f3a05a2016-08-31 11:30:02 -07003613 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 Dougherty29e93432015-05-05 18:17:13 -07003872(function($) {
3873 'use strict';
3874
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003875 function Modal(el, options) {
3876 this.el = $(el);
smain@google.com4f3a05a2016-08-31 11:30:02 -07003877 this.options = $.extend({}, options);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003878 this.isOpen = false;
3879
3880 this.el.on('click', function(event) {
smain@google.com4f3a05a2016-08-31 11:30:02 -07003881 if (!$.contains(this.el.find('.dac-modal-window')[0], event.target)) {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003882 return this.el.trigger('modal-close');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003883 }
3884 }.bind(this));
3885
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003886 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));
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003889 }
3890
3891 Modal.prototype.toggle_ = function() {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003892 this.el.trigger('modal-' + (this.isOpen ? 'close' : 'open'));
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003893 };
3894
3895 Modal.prototype.close_ = function() {
smain@google.com4f3a05a2016-08-31 11:30:02 -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 Doughertyf97b2ef2015-05-12 21:23:05 -07003901 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
smain@google.com4f3a05a2016-08-31 11:30:02 -07003912 function onClickToggleModal(event) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003913 event.preventDefault();
smain@google.com4f3a05a2016-08-31 11:30:02 -07003914 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 }
Dirk Dougherty29e93432015-05-05 18:17:13 -07003920
3921 /**
3922 * jQuery plugin
3923 * @param {object} options - Override default options.
3924 */
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003925 $.fn.dacModal = function(options) {
3926 return this.each(function() {
3927 new Modal(this, options);
3928 });
3929 };
3930
Dirk Dougherty29e93432015-05-05 18:17:13 -07003931 $.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() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003941 $('[data-modal]').each(function() {
3942 $(this).dacModal($(this).data());
3943 });
3944
smain@google.com4f3a05a2016-08-31 11:30:02 -07003945 $('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) {
3949 var $elem = $(document.getElementById(location.hash.substr(1)));
3950 if ($elem.attr('data-modal-toggle')) {
3951 $elem.trigger('click');
3952 }
3953 }
3954
3955 var isTargetLangValid = false;
3956 $(ANDROID_LANGUAGES).each(function(index, langCode) {
3957 if (langCode == window.getLangTarget()) {
3958 isTargetLangValid = true;
3959 return;
3960 }
Dirk Dougherty29e93432015-05-05 18:17:13 -07003961 });
smain@google.com4f3a05a2016-08-31 11:30:02 -07003962 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();
3972 }
Dirk Dougherty29e93432015-05-05 18:17:13 -07003973 });
3974})(jQuery);
3975
smain@google.com4f3a05a2016-08-31 11:30:02 -07003976/* 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)
4106 .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 });
4112
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')));
4165 toggleIcons.on('click keypress', function(e) {
4166 if (e.type == 'keypress' && e.which == 13 || e.type == 'click') {
4167 handleSubNavToggle_(e);
4168 }
4169 });
4170 }
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
4186 if (landingPageClass == 'about' && location.pathname == '/index.html') {
4187 expanded = true;
4188 }
4189
4190 // 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) {
4245 var supportLibraryPath = '/reference/android/support/';
4246 var currPath = location.pathname;
4247
4248 if (currPath.indexOf(supportLibraryPath) > -1) {
4249 updateSupportLibrariesNav(supportLibraryPath, currPath);
4250 }
4251 var namespaceList = el.find('[data-reference-namespaces]');
4252 var resources = $('[data-reference-resources]').detach();
4253 var selected = namespaceList.find('.selected');
4254 resources.appendTo(el);
4255
4256 // Links should be toggleable.
4257 namespaceList.find('a').addClass('dac-reference-nav-toggle dac-closed');
4258
4259 // Set the path for the navtree data to use.
4260 var navtree_filepath = getNavtreeFilePath(supportLibraryPath, currPath);
4261
4262 // Load in all resources
4263 $.getScript(navtree_filepath, function(data, textStatus, xhr) {
4264 if (xhr.status === 200) {
4265 namespaceList.on(
4266 'click', 'a.dac-reference-nav-toggle', toggleResourcesHandler);
4267 }
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
4279 if (location.href === overview.attr('href')) {
4280 overview.parent().addClass('selected');
4281 }
4282
4283 // Open currently selected resource
4284 var listsToOpen = selected.children().eq(1);
4285 listsToOpen = listsToOpen.add(
4286 listsToOpen.find('.selected').parent()).show();
4287
4288 // Mark dropdowns as open
4289 listsToOpen.prev().removeClass('dac-closed');
4290
4291 // Scroll into view
4292 namespaceList.scrollIntoView(selected);
4293 }
4294
4295 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
4319 /**
4320 * Handles the toggling of resources.
4321 * @param {Event} event
4322 */
4323 function toggleResourcesHandler(event) {
4324 event.preventDefault();
4325 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 }
4331
4332 el.toggleClass('dac-closed').next().slideToggle(200);
4333 }
4334 }
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()
4393 .find('h2').attr('tabindex', 0)
4394 .addClass('dac-reference-nav-toggle dac-closed')
4395 .on('click keypress', toggleResourcesHandler)
4396 .end()
4397 .add(resources.find('ul'))
4398 .addClass('dac-reference-nav-resources')
4399 .end()
4400 .appendTo(view);
4401
4402 return overview;
4403 }
4404
4405 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
4424 /**
4425 * jQuery plugin
4426 */
4427 $.fn.dacReferenceNav = function() {
4428 return this.each(function() {
4429 setActiveReferencePackage($(this));
4430 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
4471 if (body.hasClass('dac-ndk')) {
4472 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 }
4481 } 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')) {
4485 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
4494 } else if (body.hasClass('about') || location.pathname == '/index.html') {
4495 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 }
4522 } 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 Dougherty29e93432015-05-05 18:17:13 -07004560(function($) {
4561 'use strict';
4562
4563 /**
4564 * Toggle the visabilty of the mobile navigation.
4565 * @param {HTMLElement} el - The DOM element.
smain@google.com4f3a05a2016-08-31 11:30:02 -07004566 * @param {Object} options
Dirk Dougherty29e93432015-05-05 18:17:13 -07004567 * @constructor
4568 */
4569 function ToggleNav(el, options) {
4570 this.el = $(el);
4571 this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
smain@google.com4f3a05a2016-08-31 11:30:02 -07004572 this.body = $(document.body);
4573 this.navigation_ = this.body.find(this.options.navigation);
Dirk Dougherty29e93432015-05-05 18:17:13 -07004574 this.el.on('click', this.clickHandler_.bind(this));
4575 }
4576
smain@google.com4f3a05a2016-08-31 11:30:02 -07004577 ToggleNav.BREAKPOINT_ = 980;
4578
4579 /**
4580 * Open on correct sizes
4581 */
4582 function toggleSidebarVisibility(body) {
4583 var wasClosed = ('' + localStorage.getItem('navigation-open')) === 'false';
4584 // 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 }
4589
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 Dougherty29e93432015-05-05 18:17:13 -07004599 /**
4600 * ToggleNav Default Settings
smain@google.com4f3a05a2016-08-31 11:30:02 -07004601 * @type {{body: boolean, dimmer: string, navigation: string, activeClass: string}}
Dirk Dougherty29e93432015-05-05 18:17:13 -07004602 * @private
4603 */
4604 ToggleNav.DEFAULTS_ = {
4605 body: true,
4606 dimmer: '.dac-nav-dimmer',
smain@google.com4f3a05a2016-08-31 11:30:02 -07004607 animatingClass: 'dac-nav-animating',
Dirk Dougherty29e93432015-05-05 18:17:13 -07004608 navigation: '[data-dac-nav]',
smain@google.com4f3a05a2016-08-31 11:30:02 -07004609 activeClass: 'dac-nav-open'
Dirk Dougherty29e93432015-05-05 18:17:13 -07004610 };
4611
4612 /**
4613 * The actual toggle logic.
smain@google.com4f3a05a2016-08-31 11:30:02 -07004614 * @param {Event} event
Dirk Dougherty29e93432015-05-05 18:17:13 -07004615 * @private
4616 */
4617 ToggleNav.prototype.clickHandler_ = function(event) {
4618 event.preventDefault();
smain@google.com4f3a05a2016-08-31 11:30:02 -07004619 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 Dougherty29e93432015-05-05 18:17:13 -07004632 };
4633
4634 /**
4635 * jQuery plugin
4636 * @param {object} options - Override default options.
4637 */
smain@google.com4f3a05a2016-08-31 11:30:02 -07004638 $.fn.dacToggleMobileNav = function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004639 return this.each(function() {
smain@google.com4f3a05a2016-08-31 11:30:02 -07004640 var el = $(this);
4641 new ToggleNav(el, el.data());
Dirk Dougherty29e93432015-05-05 18:17:13 -07004642 });
4643 };
4644
smain@google.com4f3a05a2016-08-31 11:30:02 -07004645 $.fn.dacSidebarToggle = function(body) {
4646 toggleSidebarVisibility(body);
4647 $(window).on('resize', toggleSidebarVisibility.bind(null, body));
4648 };
4649
Dirk Dougherty29e93432015-05-05 18:17:13 -07004650 /**
4651 * Data Attribute API
4652 */
smain@google.com4f3a05a2016-08-31 11:30:02 -07004653 $(function() {
4654 $('[data-dac-toggle-nav]').dacToggleMobileNav();
Dirk Dougherty29e93432015-05-05 18:17:13 -07004655 });
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);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004668 this.form = this.el.find('form');
4669 $('<iframe/>').hide()
4670 .attr('name', 'dac-newsletter-iframe')
4671 .attr('src', '')
4672 .insertBefore(this.form);
smain@google.com4f3a05a2016-08-31 11:30:02 -07004673 this.el.find('[data-newsletter-language]').val(window.polyglot.t('newsletter.languageVal'));
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004674 this.form.on('submit', this.submitHandler_.bind(this));
Dirk Dougherty29e93432015-05-05 18:17:13 -07004675 }
4676
4677 /**
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004678 * 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.
Dirk Dougherty29e93432015-05-05 18:17:13 -07004705 * @private
4706 */
4707 NewsletterForm.prototype.submitHandler_ = function() {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004708 this.el.one('swap-complete', this.reset_.bind(this));
4709 this.el.trigger('swap-content');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004710 };
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() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004726 $('[data-newsletter]').each(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004727 $(this).dacNewsletterForm();
4728 });
4729 });
4730})(jQuery);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004731
smain@google.com4f3a05a2016-08-31 11:30:02 -07004732/* 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();
4833 // Merge english resources.
4834 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 }
4864
4865 // Merge local language resources.
4866 if (locale !== 'en' && METADATA[locale]) {
4867 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 }
4893 }
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.
4903 METADATA.androidReference = mergeArrays(
4904 window.DATA, window.SUPPORT_WEARABLE_DATA, window.SUPPORT_TEST_DATA);
4905 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 Doughertyf97b2ef2015-05-12 21:23:05 -07005378(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',
smain@google.com4f3a05a2016-08-31 11:30:02 -07005407 offset: '.dac-header',
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005408 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
smain@google.com4f3a05a2016-08-31 11:30:02 -07005423 var position = this.getTargetPosition();
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005424 $(this.options.scrollContainer).animate({
smain@google.com4f3a05a2016-08-31 11:30:02 -07005425 scrollTop: position - this.options.offset
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005426 }, this.options);
5427 };
5428
smain@google.com4f3a05a2016-08-31 11:30:02 -07005429 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 Doughertyf97b2ef2015-05-12 21:23:05 -07005443 /**
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
smain@google.com4f3a05a2016-08-31 11:30:02 -07005463/* 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) {
5604 devsite.analytics.trackAnalyticsEvent('event',
5605 'Suggestion Click', 'clicked: ' + $(e.currentTarget).attr('href'),
5606 'query: ' + $('#search_autocomplete').val().toLowerCase());
5607 }
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
5641 var header = $('<li class="dac-search-results-reference-header">android APIs</li>');
5642 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');
5762 this.pageNav = $('a[name=navigation]');
5763 this.currQueryReferenceResults = {};
5764 this.isOpen = false;
5765 }
5766
5767 Search.prototype.init = function() {
5768 this.searchHistory = window.dacStore('search-history');
5769
5770 this.searchInput.focus(this.onSearchChanged.bind(this));
5771 this.searchInput.keypress(this.handleKeyboardShortcut.bind(this));
5772 this.pageNav.keyup(this.handleTabbedToNav.bind(this));
5773 this.searchResults.keyup(this.handleKeyboardShortcut.bind(this));
5774 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
5830 Search.prototype.handleTabbedToNav = function(event) {
5831 if (this.isOpen) {
5832 this.searchClose.trigger('click');
5833 }
5834 }
5835
5836 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();
5898 this.pageNav.focus();
5899 this.isOpen = false;
5900 };
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() {
5984 this.isOpen = true;
5985 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 Doughertyf97b2ef2015-05-12 21:23:05 -07006062(function($) {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07006063 '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);
smain@google.com4f3a05a2016-08-31 11:30:02 -07006077 this.options.dynamic = this.options.dynamic === 'true';
Dirk Doughertycbe032f2015-05-22 11:41:40 -07006078 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.com4f3a05a2016-08-31 11:30:02 -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 Doughertycbe032f2015-05-22 11:41:40 -07006087 }
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]',
smain@google.com4f3a05a2016-08-31 11:30:02 -07006097 dynamic: 'true',
6098 swapButton: '[data-swap-button]',
Dirk Doughertycbe032f2015-05-22 11:41:40 -07006099 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 Doughertycbe032f2015-05-22 11:41:40 -07006132 this.containers.each(function(index, container) {
6133 container = $(container);
smain@google.com4f3a05a2016-08-31 11:30:02 -07006134
6135 if (!this.options.dynamic) {
6136 container.children().toggleClass(this.options.activeClass);
6137 this.complete.bind(this);
6138 $('.' + this.options.activeClass).focus();
6139 return;
6140 }
6141
Dirk Doughertycbe032f2015-05-22 11:41:40 -07006142 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
smain@google.com4f3a05a2016-08-31 11:30:02 -07006168/* 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 + '">' +
6270 '<span class="dac-button dac-raised dac-primary">OK</span>' +
6271 '</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 Doughertycbe032f2015-05-22 11:41:40 -07006340(function($) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07006341 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
Dirk Doughertycbe032f2015-05-22 11:41:40 -07006367 $parent = $parent && $parent.length ? $parent : $this.closest('.dac-toggle');
6368
6369 return $parent.length ? $parent : $this.parent();
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07006370 }
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) {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07006378 var contentHeight = $el.prop('scrollHeight');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07006379 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) {
smain@google.com4f3a05a2016-08-31 11:30:02 -07006384 $el.css({
6385 transitionDuration: '0s',
6386 maxHeight: contentHeight + 'px'
6387 })
6388 .resolveStyles()
6389 .css('transitionDuration', '');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07006390 }
6391
6392 // Transition to new state
6393 $el.css('maxHeight', targetHeight);
6394
6395 // Reset maxHeight to css value after transition.
6396 setTimeout(function() {
smain@google.com4f3a05a2016-08-31 11:30:02 -07006397 $el.css({
6398 transitionDuration: '0s',
6399 maxHeight: ''
6400 })
6401 .resolveStyles()
6402 .css('transitionDuration', '');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07006403 }, 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);
smain@google.com4f3a05a2016-08-31 11:30:02 -07006427
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) {
6592 devsite.analytics.trackAnalyticsEvent('event',
6593 'Videos', 'Resume', videoId);
6594 } else {
6595 // track the start playing event so we know from which page the video was selected
6596 devsite.analytics.trackAnalyticsEvent('event',
6597 'Videos', 'Start: ' + videoId, 'on: ' + document.location.href);
6598 }
6599 this.mPlayerPaused = false;
6600 }
6601
6602 // Video paused, send video ID and video elapsed time
6603 if (event.data === YT.PlayerState.PAUSED) {
6604 devsite.analytics.trackAnalyticsEvent('event',
6605 'Videos', 'Paused: ' + videoId, 'on: ' + currentTime);
6606 this.mPlayerPaused = true;
6607 }
6608
6609 // Video finished, send video ID and video elapsed time
6610 if (event.data === YT.PlayerState.ENDED) {
6611 devsite.analytics.trackAnalyticsEvent('event',
6612 'Videos', 'Finished: ' + videoId, 'on: ' + currentTime);
6613 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};