blob: 18bcd67b2b1742212ec7788bd2f6f34261f3475b [file] [log] [blame]
Dirk Dougherty541b4942014-02-14 18:31:53 -08001var cookie_namespace = 'android_developer';
Dirk Dougherty541b4942014-02-14 18:31:53 -08002var isMobile = false; // true if mobile, so we can adjust some layout
3var mPagePath; // initialized in ready() function
4
5var basePath = getBaseUri(location.pathname);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08006var SITE_ROOT = toRoot + basePath.substring(1, basePath.indexOf("/", 1));
Dirk Dougherty541b4942014-02-14 18:31:53 -08007
8// Ensure that all ajax getScript() requests allow caching
9$.ajaxSetup({
10 cache: true
11});
12
13/****** ON LOAD SET UP STUFF *********/
14
Dirk Dougherty541b4942014-02-14 18:31:53 -080015$(document).ready(function() {
16
Dirk Dougherty541b4942014-02-14 18:31:53 -080017 // prep nav expandos
18 var pagePath = document.location.pathname;
19 // account for intl docs by removing the intl/*/ path
20 if (pagePath.indexOf("/intl/") == 0) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -080021 pagePath = pagePath.substr(pagePath.indexOf("/", 6)); // start after intl/ to get last /
Dirk Dougherty541b4942014-02-14 18:31:53 -080022 }
23
24 if (pagePath.indexOf(SITE_ROOT) == 0) {
25 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
26 pagePath += 'index.html';
27 }
28 }
29
30 // Need a copy of the pagePath before it gets changed in the next block;
31 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
32 var pagePathOriginal = pagePath;
33 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
34 // If running locally, SITE_ROOT will be a relative path, so account for that by
35 // finding the relative URL to this page. This will allow us to find links on the page
36 // leading back to this page.
37 var pathParts = pagePath.split('/');
38 var relativePagePathParts = [];
39 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
40 for (var i = 0; i < upDirs; i++) {
41 relativePagePathParts.push('..');
42 }
43 for (var i = 0; i < upDirs; i++) {
44 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
45 }
46 relativePagePathParts.push(pathParts[pathParts.length - 1]);
47 pagePath = relativePagePathParts.join('/');
48 } else {
49 // Otherwise the page path is already an absolute URL
50 }
51
Dirk Dougherty541b4942014-02-14 18:31:53 -080052 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
53 // and highlight the sidenav
54 mPagePath = pagePath;
55 highlightSidenav();
56
57 // set up prev/next links if they exist
58 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
59 var $selListItem;
60 if ($selNavLink.length) {
61 $selListItem = $selNavLink.closest('li');
62
63 // set up prev links
64 var $prevLink = [];
65 var $prevListItem = $selListItem.prev('li');
66
67 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
68false; // navigate across topic boundaries only in design docs
69 if ($prevListItem.length) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -070070 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Dirk Dougherty541b4942014-02-14 18:31:53 -080071 // jump to last topic of previous section
72 $prevLink = $prevListItem.find('a:last');
73 } else if (!$selListItem.hasClass('nav-section')) {
74 // jump to previous topic in this section
75 $prevLink = $prevListItem.find('a:eq(0)');
76 }
77 } else {
78 // jump to this section's index page (if it exists)
79 var $parentListItem = $selListItem.parents('li');
80 $prevLink = $selListItem.parents('li').find('a');
81
82 // except if cross boundaries aren't allowed, and we're at the top of a section already
83 // (and there's another parent)
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -080084 if (!crossBoundaries && $parentListItem.hasClass('nav-section') &&
85 $selListItem.hasClass('nav-section')) {
Dirk Dougherty541b4942014-02-14 18:31:53 -080086 $prevLink = [];
87 }
88 }
89
90 // set up next links
91 var $nextLink = [];
92 var startClass = false;
Dirk Dougherty541b4942014-02-14 18:31:53 -080093 var isCrossingBoundary = false;
94
95 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
96 // we're on an index page, jump to the first topic
97 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
98
99 // if there aren't any children, go to the next section (required for About pages)
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800100 if ($nextLink.length == 0) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800101 $nextLink = $selListItem.next('li').find('a');
102 } else if ($('.topic-start-link').length) {
103 // as long as there's a child link and there is a "topic start link" (we're on a landing)
104 // then set the landing page "start link" text to be the first doc title
105 $('.topic-start-link').text($nextLink.text().toUpperCase());
106 }
107
108 // If the selected page has a description, then it's a class or article homepage
109 if ($selListItem.find('a[description]').length) {
110 // this means we're on a class landing page
111 startClass = true;
112 }
113 } else {
114 // jump to the next topic in this section (if it exists)
115 $nextLink = $selListItem.next('li').find('a:eq(0)');
116 if ($nextLink.length == 0) {
117 isCrossingBoundary = true;
118 // no more topics in this section, jump to the first topic in the next section
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700119 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800120 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
121 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
122 if ($nextLink.length == 0) {
123 // if that doesn't work, we're at the end of the list, so disable NEXT link
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800124 $('.next-page-link').attr('href', '').addClass("disabled")
Dirk Dougherty541b4942014-02-14 18:31:53 -0800125 .click(function() { return false; });
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700126 // and completely hide the one in the footer
127 $('.content-footer .next-page-link').hide();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800128 }
129 }
130 }
131 }
132
133 if (startClass) {
134 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
135
136 // if there's no training bar (below the start button),
137 // then we need to add a bottom border to button
138 if (!$("#tb").length) {
139 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
140 }
141 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
142 $('.content-footer.next-class').show();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800143 $('.next-page-link').attr('href', '')
Dirk Dougherty541b4942014-02-14 18:31:53 -0800144 .removeClass("hide").addClass("disabled")
145 .click(function() { return false; });
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700146 // and completely hide the one in the footer
147 $('.content-footer .next-page-link').hide();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800148 $('.content-footer .prev-page-link').hide();
149
Dirk Dougherty541b4942014-02-14 18:31:53 -0800150 if ($nextLink.length) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800151 $('.next-class-link').attr('href', $nextLink.attr('href'))
152 .removeClass("hide");
153
154 $('.content-footer .next-class-link').append($nextLink.html());
155
Dirk Dougherty541b4942014-02-14 18:31:53 -0800156 $('.next-class-link').find('.new').empty();
157 }
158 } else {
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700159 $('.next-page-link').attr('href', $nextLink.attr('href'))
160 .removeClass("hide");
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800161 // for the footer link, also add the previous and next page titles
162 $('.content-footer .prev-page-link').append($prevLink.html());
163 $('.content-footer .next-page-link').append($nextLink.html());
Dirk Dougherty541b4942014-02-14 18:31:53 -0800164 }
165
166 if (!startClass && $prevLink.length) {
167 var prevHref = $prevLink.attr('href');
168 if (prevHref == SITE_ROOT + 'index.html') {
169 // Don't show Previous when it leads to the homepage
170 } else {
171 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
172 }
173 }
174
Dirk Dougherty541b4942014-02-14 18:31:53 -0800175 }
176
Dirk Dougherty541b4942014-02-14 18:31:53 -0800177 // Set up the course landing pages for Training with class names and descriptions
178 if ($('body.trainingcourse').length) {
179 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700180
181 // create an array for all the class descriptions
182 var $classDescriptions = new Array($classLinks.length);
183 var lang = getLangPref();
184 $classLinks.each(function(index) {
185 var langDescr = $(this).attr(lang + "-description");
186 if (typeof langDescr !== 'undefined' && langDescr !== false) {
187 // if there's a class description in the selected language, use that
188 $classDescriptions[index] = langDescr;
189 } else {
190 // otherwise, use the default english description
191 $classDescriptions[index] = $(this).attr("description");
192 }
193 });
Dirk Dougherty541b4942014-02-14 18:31:53 -0800194
195 var $olClasses = $('<ol class="class-list"></ol>');
196 var $liClass;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800197 var $h2Title;
198 var $pSummary;
199 var $olLessons;
200 var $liLesson;
201 $classLinks.each(function(index) {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -0800202 $liClass = $('<li class="clearfix"></li>');
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800203 $h2Title = $('<a class="title" href="' + $(this).attr('href') + '"><h2 class="norule">' + $(this).html() + '</h2><span></span></a>');
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700204 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800205
206 $olLessons = $('<ol class="lesson-list"></ol>');
207
208 $lessons = $(this).closest('li').find('ul li a');
209
210 if ($lessons.length) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800211 $lessons.each(function(index) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800212 $olLessons.append('<li><a href="' + $(this).attr('href') + '">' + $(this).html() + '</a></li>');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800213 });
214 } else {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800215 $pSummary.addClass('article');
216 }
217
Dirk Dougherty0dc81b92015-12-08 14:49:52 -0800218 $liClass.append($h2Title).append($pSummary).append($olLessons);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800219 $olClasses.append($liClass);
220 });
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800221 $('#classes').append($olClasses);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800222 }
223
224 // Set up expand/collapse behavior
225 initExpandableNavItems("#nav");
226
Dirk Dougherty541b4942014-02-14 18:31:53 -0800227 // Set up play-on-hover <video> tags.
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800228 $('video.play-on-hover').bind('click', function() {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800229 $(this).get(0).load(); // in case the video isn't seekable
230 $(this).get(0).play();
231 });
232
233 // Set up tooltips
234 var TOOLTIP_MARGIN = 10;
235 $('acronym,.tooltip-link').each(function() {
236 var $target = $(this);
237 var $tooltip = $('<div>')
238 .addClass('tooltip-box')
239 .append($target.attr('title'))
240 .hide()
241 .appendTo('body');
242 $target.removeAttr('title');
243
244 $target.hover(function() {
245 // in
246 var targetRect = $target.offset();
247 targetRect.width = $target.width();
248 targetRect.height = $target.height();
249
250 $tooltip.css({
251 left: targetRect.left,
252 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
253 });
254 $tooltip.addClass('below');
255 $tooltip.show();
256 }, function() {
257 // out
258 $tooltip.hide();
259 });
260 });
261
262 // Set up <h2> deeplinks
263 $('h2').click(function() {
264 var id = $(this).attr('id');
265 if (id) {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -0800266 if (history && history.replaceState) {
267 // Change url without scrolling.
268 history.replaceState({}, '', '#' + id);
269 } else {
270 document.location.hash = id;
271 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800272 }
273 });
274
275 //Loads the +1 button
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800276 //var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
277 //po.src = 'https://apis.google.com/js/plusone.js';
278 //var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800279});
280// END of the onload event
281
Dirk Dougherty541b4942014-02-14 18:31:53 -0800282function initExpandableNavItems(rootTag) {
283 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
284 var section = $(this).closest('li.nav-section');
285 if (section.hasClass('expanded')) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800286 /* hide me and descendants */
Dirk Dougherty541b4942014-02-14 18:31:53 -0800287 section.find('ul').slideUp(250, function() {
288 // remove 'expanded' class from my section and any children
289 section.closest('li').removeClass('expanded');
290 $('li.nav-section', section).removeClass('expanded');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800291 });
292 } else {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800293 /* show me */
Dirk Dougherty541b4942014-02-14 18:31:53 -0800294 // first hide all other siblings
295 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
296 $others.removeClass('expanded').children('ul').slideUp(250);
297
298 // now expand me
299 section.closest('li').addClass('expanded');
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800300 section.children('ul').slideDown(250);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800301 }
302 });
303
304 // Stop expand/collapse behavior when clicking on nav section links
305 // (since we're navigating away from the page)
306 // This selector captures the first instance of <a>, but not those with "#" as the href.
307 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
308 window.location.href = $(this).attr('href');
309 return false;
310 });
311}
312
313/** Highlight the current page in sidenav, expanding children as appropriate */
314function highlightSidenav() {
315 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
316 if ($("ul#nav li.selected").length) {
317 unHighlightSidenav();
318 }
319 // look for URL in sidenav, including the hash
320 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
321
322 // If the selNavLink is still empty, look for it without the hash
323 if ($selNavLink.length == 0) {
324 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
325 }
326
327 var $selListItem;
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800328 var breadcrumb = [];
329
Dirk Dougherty541b4942014-02-14 18:31:53 -0800330 if ($selNavLink.length) {
331 // Find this page's <li> in sidenav and set selected
332 $selListItem = $selNavLink.closest('li');
333 $selListItem.addClass('selected');
334
335 // Traverse up the tree and expand all parent nav-sections
336 $selNavLink.parents('li.nav-section').each(function() {
337 $(this).addClass('expanded');
338 $(this).children('ul').show();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800339
340 var link = $(this).find('a').first();
341
342 if (!$(this).is($selListItem)) {
343 breadcrumb.unshift(link)
344 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800345 });
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800346
347 $('#nav').scrollIntoView($selNavLink);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800348 }
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800349
350 breadcrumb.forEach(function(link) {
351 link.dacCrumbs();
352 });
Dirk Dougherty541b4942014-02-14 18:31:53 -0800353}
354
355function unHighlightSidenav() {
356 $("ul#nav li.selected").removeClass("selected");
357 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
358}
359
Dirk Dougherty541b4942014-02-14 18:31:53 -0800360var agent = navigator['userAgent'].toLowerCase();
361// If a mobile phone, set flag and do mobile setup
362if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
363 (agent.indexOf("blackberry") != -1) ||
364 (agent.indexOf("webos") != -1) ||
365 (agent.indexOf("mini") != -1)) { // opera mini browsers
366 isMobile = true;
367}
368
Dirk Dougherty541b4942014-02-14 18:31:53 -0800369$(document).ready(function() {
370 $("pre:not(.no-pretty-print)").addClass("prettyprint");
371 prettyPrint();
372});
373
Dirk Dougherty541b4942014-02-14 18:31:53 -0800374/* Show popup dialogs */
375function showDialog(id) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800376 $dialog = $("#" + id);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800377 $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>');
378 $dialog.wrapInner('<div/>');
379 $dialog.removeClass("hide");
380}
381
Dirk Dougherty541b4942014-02-14 18:31:53 -0800382/* ######### COOKIES! ########## */
383
384function readCookie(cookie) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800385 var myCookie = cookie_namespace + "_" + cookie + "=";
Dirk Dougherty541b4942014-02-14 18:31:53 -0800386 if (document.cookie) {
387 var index = document.cookie.indexOf(myCookie);
388 if (index != -1) {
389 var valStart = index + myCookie.length;
390 var valEnd = document.cookie.indexOf(";", valStart);
391 if (valEnd == -1) {
392 valEnd = document.cookie.length;
393 }
394 var val = document.cookie.substring(valStart, valEnd);
395 return val;
396 }
397 }
398 return 0;
399}
400
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700401function writeCookie(cookie, val, section) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800402 if (val == undefined) return;
403 section = section == null ? "_" : "_" + section + "_";
404 var age = 2 * 365 * 24 * 60 * 60; // set max-age to 2 years
405 var cookieValue = cookie_namespace + section + cookie + "=" + val +
406 "; max-age=" + age + "; path=/";
Dirk Dougherty541b4942014-02-14 18:31:53 -0800407 document.cookie = cookieValue;
408}
409
410/* ######### END COOKIES! ########## */
411
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700412/*
413 * Manages secion card states and nav resize to conclude loading
Dirk Dougherty08032402014-02-15 10:14:35 -0800414 */
Dirk Dougherty08032402014-02-15 10:14:35 -0800415(function() {
416 $(document).ready(function() {
417
Dirk Dougherty08032402014-02-15 10:14:35 -0800418 // Stack hover states
419 $('.section-card-menu').each(function(index, el) {
420 var height = $(el).height();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800421 $(el).css({height:height + 'px', position:'relative'});
Dirk Dougherty08032402014-02-15 10:14:35 -0800422 var $cardInfo = $(el).find('.card-info');
423
424 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
425 });
426
Dirk Dougherty08032402014-02-15 10:14:35 -0800427 });
428
429})();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800430
Dirk Dougherty541b4942014-02-14 18:31:53 -0800431/* MISC LIBRARY FUNCTIONS */
432
Dirk Dougherty541b4942014-02-14 18:31:53 -0800433function toggle(obj, slide) {
434 var ul = $("ul:first", obj);
435 var li = ul.parent();
436 if (li.hasClass("closed")) {
437 if (slide) {
438 ul.slideDown("fast");
439 } else {
440 ul.show();
441 }
442 li.removeClass("closed");
443 li.addClass("open");
444 $(".toggle-img", li).attr("title", "hide pages");
445 } else {
446 ul.slideUp("fast");
447 li.removeClass("open");
448 li.addClass("closed");
449 $(".toggle-img", li).attr("title", "show pages");
450 }
451}
452
Dirk Dougherty541b4942014-02-14 18:31:53 -0800453function buildToggleLists() {
454 $(".toggle-list").each(
455 function(i) {
456 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
457 $(this).addClass("closed");
458 });
459}
460
Dirk Dougherty541b4942014-02-14 18:31:53 -0800461function hideNestedItems(list, toggle) {
462 $list = $(list);
463 // hide nested lists
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800464 if ($list.hasClass('showing')) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800465 $("li ol", $list).hide('fast');
466 $list.removeClass('showing');
467 // show nested lists
468 } else {
469 $("li ol", $list).show('fast');
470 $list.addClass('showing');
471 }
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800472 $(".more,.less", $(toggle)).toggle();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800473}
474
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700475/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
476function setupIdeDocToggle() {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800477 $("select.ide").change(function() {
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700478 var selected = $(this).find("option:selected").attr("value");
479 $(".select-ide").hide();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800480 $(".select-ide." + selected).show();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800481
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700482 $("select.ide").val(selected);
483 });
484}
Dirk Dougherty541b4942014-02-14 18:31:53 -0800485
Dirk Dougherty541b4942014-02-14 18:31:53 -0800486/* Used to hide and reveal supplemental content, such as long code samples.
487 See the companion CSS in android-developer-docs.css */
488function toggleContent(obj) {
489 var div = $(obj).closest(".toggle-content");
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800490 var toggleMe = $(".toggle-content-toggleme:eq(0)", div);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800491 if (div.hasClass("closed")) { // if it's closed, open it
492 toggleMe.slideDown();
493 $(".toggle-content-text:eq(0)", obj).toggle();
494 div.removeClass("closed").addClass("open");
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800495 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot +
496 "assets/images/triangle-opened.png");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800497 } else { // if it's open, close it
498 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
499 $(".toggle-content-text:eq(0)", obj).toggle();
500 div.removeClass("open").addClass("closed");
501 div.find(".toggle-content").removeClass("open").addClass("closed")
502 .find(".toggle-content-toggleme").hide();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800503 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot +
504 "assets/images/triangle-closed.png");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800505 });
506 }
507 return false;
508}
509
Dirk Dougherty541b4942014-02-14 18:31:53 -0800510/* New version of expandable content */
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800511function toggleExpandable(link, id) {
512 if ($(id).is(':visible')) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800513 $(id).slideUp();
514 $(link).removeClass('expanded');
515 } else {
516 $(id).slideDown();
517 $(link).addClass('expanded');
518 }
519}
520
521function hideExpandable(ids) {
522 $(ids).slideUp();
523 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
524}
525
Dirk Dougherty541b4942014-02-14 18:31:53 -0800526/*
527 * Slideshow 1.0
528 * Used on /index.html and /develop/index.html for carousel
529 *
530 * Sample usage:
531 * HTML -
532 * <div class="slideshow-container">
533 * <a href="" class="slideshow-prev">Prev</a>
534 * <a href="" class="slideshow-next">Next</a>
535 * <ul>
536 * <li class="item"><img src="images/marquee1.jpg"></li>
537 * <li class="item"><img src="images/marquee2.jpg"></li>
538 * <li class="item"><img src="images/marquee3.jpg"></li>
539 * <li class="item"><img src="images/marquee4.jpg"></li>
540 * </ul>
541 * </div>
542 *
543 * <script type="text/javascript">
544 * $('.slideshow-container').dacSlideshow({
545 * auto: true,
546 * btnPrev: '.slideshow-prev',
547 * btnNext: '.slideshow-next'
548 * });
549 * </script>
550 *
551 * Options:
552 * btnPrev: optional identifier for previous button
553 * btnNext: optional identifier for next button
554 * btnPause: optional identifier for pause button
555 * auto: whether or not to auto-proceed
556 * speed: animation speed
557 * autoTime: time between auto-rotation
558 * easing: easing function for transition
559 * start: item to select by default
560 * scroll: direction to scroll in
561 * pagination: whether or not to include dotted pagination
562 *
563 */
564
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800565(function($) {
566 $.fn.dacSlideshow = function(o) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800567
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800568 //Options - see above
569 o = $.extend({
570 btnPrev: null,
571 btnNext: null,
572 btnPause: null,
573 auto: true,
574 speed: 500,
575 autoTime: 12000,
576 easing: null,
577 start: 0,
578 scroll: 1,
579 pagination: true
Dirk Dougherty541b4942014-02-14 18:31:53 -0800580
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800581 }, o || {});
Dirk Dougherty541b4942014-02-14 18:31:53 -0800582
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800583 //Set up a carousel for each
584 return this.each(function() {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800585
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800586 var running = false;
587 var animCss = o.vertical ? "top" : "left";
588 var sizeCss = o.vertical ? "height" : "width";
589 var div = $(this);
590 var ul = $("ul", div);
591 var tLi = $("li", ul);
592 var tl = tLi.size();
593 var timer = null;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800594
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800595 var li = $("li", ul);
596 var itemLength = li.size();
597 var curr = o.start;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800598
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800599 li.css({float: o.vertical ? "none" : "left"});
600 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
601 div.css({position: "relative", "z-index": "2", left: "0px"});
Dirk Dougherty541b4942014-02-14 18:31:53 -0800602
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800603 var liSize = o.vertical ? height(li) : width(li);
604 var ulSize = liSize * itemLength;
605 var divSize = liSize;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800606
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800607 li.css({width: li.width(), height: li.height()});
608 ul.css(sizeCss, ulSize + "px").css(animCss, -(curr * liSize));
Dirk Dougherty541b4942014-02-14 18:31:53 -0800609
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800610 div.css(sizeCss, divSize + "px");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800611
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800612 //Pagination
613 if (o.pagination) {
614 var pagination = $("<div class='pagination'></div>");
615 var pag_ul = $("<ul></ul>");
616 if (tl > 1) {
617 for (var i = 0; i < tl; i++) {
618 var li = $("<li>" + i + "</li>");
619 pag_ul.append(li);
620 if (i == o.start) li.addClass('active');
621 li.click(function() {
622 go(parseInt($(this).text()));
623 })
624 }
625 pagination.append(pag_ul);
626 div.append(pagination);
627 }
628 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800629
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800630 //Previous button
631 if (o.btnPrev)
Dirk Dougherty541b4942014-02-14 18:31:53 -0800632 $(o.btnPrev).click(function(e) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800633 e.preventDefault();
634 return go(curr - o.scroll);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800635 });
636
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800637 //Next button
638 if (o.btnNext)
Dirk Dougherty541b4942014-02-14 18:31:53 -0800639 $(o.btnNext).click(function(e) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800640 e.preventDefault();
641 return go(curr + o.scroll);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800642 });
643
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800644 //Pause button
645 if (o.btnPause)
Dirk Dougherty541b4942014-02-14 18:31:53 -0800646 $(o.btnPause).click(function(e) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800647 e.preventDefault();
648 if ($(this).hasClass('paused')) {
649 startRotateTimer();
650 } else {
651 pauseRotateTimer();
652 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800653 });
654
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800655 //Auto rotation
656 if (o.auto) startRotateTimer();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800657
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800658 function startRotateTimer() {
659 clearInterval(timer);
660 timer = setInterval(function() {
661 if (curr == tl - 1) {
662 go(0);
663 } else {
664 go(curr + o.scroll);
665 }
666 }, o.autoTime);
667 $(o.btnPause).removeClass('paused');
668 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800669
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800670 function pauseRotateTimer() {
671 clearInterval(timer);
672 $(o.btnPause).addClass('paused');
673 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800674
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800675 //Go to an item
676 function go(to) {
677 if (!running) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800678
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800679 if (to < 0) {
680 to = itemLength - 1;
681 } else if (to > itemLength - 1) {
682 to = 0;
683 }
684 curr = to;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800685
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800686 running = true;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800687
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800688 ul.animate(
689 animCss == "left" ? {left: -(curr * liSize)} : {top: -(curr * liSize)} , o.speed, o.easing,
Dirk Dougherty541b4942014-02-14 18:31:53 -0800690 function() {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800691 running = false;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800692 }
693 );
694
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800695 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
696 $((curr - o.scroll < 0 && o.btnPrev) ||
697 (curr + o.scroll > itemLength && o.btnNext) ||
698 []
699 ).addClass("disabled");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800700
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800701 var nav_items = $('li', pagination);
702 nav_items.removeClass('active');
703 nav_items.eq(to).addClass('active');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800704
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800705 }
706 if (o.auto) startRotateTimer();
707 return false;
708 };
709 });
710 };
Dirk Dougherty541b4942014-02-14 18:31:53 -0800711
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800712 function css(el, prop) {
713 return parseInt($.css(el[0], prop)) || 0;
714 };
715 function width(el) {
716 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
717 };
718 function height(el) {
719 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
720 };
Dirk Dougherty541b4942014-02-14 18:31:53 -0800721
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800722})(jQuery);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800723
724/*
725 * dacSlideshow 1.0
726 * Used on develop/index.html for side-sliding tabs
727 *
728 * Sample usage:
729 * HTML -
730 * <div class="slideshow-container">
731 * <a href="" class="slideshow-prev">Prev</a>
732 * <a href="" class="slideshow-next">Next</a>
733 * <ul>
734 * <li class="item"><img src="images/marquee1.jpg"></li>
735 * <li class="item"><img src="images/marquee2.jpg"></li>
736 * <li class="item"><img src="images/marquee3.jpg"></li>
737 * <li class="item"><img src="images/marquee4.jpg"></li>
738 * </ul>
739 * </div>
740 *
741 * <script type="text/javascript">
742 * $('.slideshow-container').dacSlideshow({
743 * auto: true,
744 * btnPrev: '.slideshow-prev',
745 * btnNext: '.slideshow-next'
746 * });
747 * </script>
748 *
749 * Options:
750 * btnPrev: optional identifier for previous button
751 * btnNext: optional identifier for next button
752 * auto: whether or not to auto-proceed
753 * speed: animation speed
754 * autoTime: time between auto-rotation
755 * easing: easing function for transition
756 * start: item to select by default
757 * scroll: direction to scroll in
758 * pagination: whether or not to include dotted pagination
759 *
760 */
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800761(function($) {
762 $.fn.dacTabbedList = function(o) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800763
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800764 //Options - see above
765 o = $.extend({
766 speed : 250,
767 easing: null,
768 nav_id: null,
769 frame_id: null
770 }, o || {});
Dirk Dougherty541b4942014-02-14 18:31:53 -0800771
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800772 //Set up a carousel for each
773 return this.each(function() {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800774
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800775 var curr = 0;
776 var running = false;
777 var animCss = "margin-left";
778 var sizeCss = "width";
779 var div = $(this);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800780
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800781 var nav = $(o.nav_id, div);
782 var nav_li = $("li", nav);
783 var nav_size = nav_li.size();
784 var frame = div.find(o.frame_id);
785 var content_width = $(frame).find('ul').width();
786 //Buttons
787 $(nav_li).click(function(e) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800788 go($(nav_li).index($(this)));
789 })
790
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800791 //Go to an item
792 function go(to) {
793 if (!running) {
794 curr = to;
795 running = true;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800796
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800797 frame.animate({'margin-left' : -(curr * content_width)}, o.speed, o.easing,
Dirk Dougherty541b4942014-02-14 18:31:53 -0800798 function() {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800799 running = false;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800800 }
801 );
802
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800803 nav_li.removeClass('active');
804 nav_li.eq(to).addClass('active');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800805
Dirk Dougherty541b4942014-02-14 18:31:53 -0800806 }
807 return false;
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800808 };
Dirk Dougherty541b4942014-02-14 18:31:53 -0800809 });
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800810 };
Dirk Dougherty541b4942014-02-14 18:31:53 -0800811
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800812 function css(el, prop) {
813 return parseInt($.css(el[0], prop)) || 0;
814 };
815 function width(el) {
816 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
817 };
818 function height(el) {
819 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
820 };
Dirk Dougherty541b4942014-02-14 18:31:53 -0800821
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800822})(jQuery);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800823
824/* ######################################################## */
825/* ################# JAVADOC REFERENCE ################### */
826/* ######################################################## */
827
828/* Initialize some droiddoc stuff, but only if we're in the reference */
829if (location.pathname.indexOf("/reference") == 0) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800830 if (!(location.pathname.indexOf("/reference-gms/packages.html") == 0) &&
831 !(location.pathname.indexOf("/reference-gcm/packages.html") == 0) &&
832 !(location.pathname.indexOf("/reference/com/google") == 0)) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800833 $(document).ready(function() {
834 // init available apis based on user pref
835 changeApiLevel();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800836 });
Dirk Dougherty541b4942014-02-14 18:31:53 -0800837 }
838}
839
840var API_LEVEL_COOKIE = "api_level";
841var minLevel = 1;
842var maxLevel = 1;
843
Dirk Dougherty541b4942014-02-14 18:31:53 -0800844function buildApiLevelSelector() {
845 maxLevel = SINCE_DATA.length;
846 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
847 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
848
849 minLevel = parseInt($("#doc-api-level").attr("class"));
850 // Handle provisional api levels; the provisional level will always be the highest possible level
851 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
852 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
853 if (isNaN(minLevel) && minLevel.length) {
854 minLevel = maxLevel;
855 }
856 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800857 for (var i = maxLevel - 1; i >= 0; i--) {
858 var option = $("<option />").attr("value", "" + SINCE_DATA[i]).append("" + SINCE_DATA[i]);
859 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
Dirk Dougherty541b4942014-02-14 18:31:53 -0800860 select.append(option);
861 }
862
863 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800864 var selectedLevelItem = $("#apiLevelSelector option[value='" + userApiLevel + "']").get(0);
865 selectedLevelItem.setAttribute('selected', true);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800866}
867
868function changeApiLevel() {
869 maxLevel = SINCE_DATA.length;
870 var selectedLevel = maxLevel;
871
872 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
873 toggleVisisbleApis(selectedLevel, "body");
874
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700875 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800876
877 if (selectedLevel < minLevel) {
smain@google.coma0a6d322016-02-05 17:34:22 -0800878 $("#naMessage").show().html("<div><p><strong>This API" +
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800879 " requires API level " + minLevel + " or higher.</strong></p>" +
880 "<p>This document is hidden because your selected API level for the documentation is " +
881 selectedLevel + ". You can change the documentation API level with the selector " +
882 "above the left navigation.</p>" +
883 "<p>For more information about specifying the API level your app requires, " +
884 "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'" +
885 ">Supporting Different Platform Versions</a>.</p>" +
886 "<input type='button' value='OK, make this page visible' " +
887 "title='Change the API level to " + minLevel + "' " +
888 "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />" +
889 "</div>");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800890 } else {
891 $("#naMessage").hide();
892 }
893}
894
895function toggleVisisbleApis(selectedLevel, context) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800896 var apis = $(".api", context);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800897 apis.each(function(i) {
898 var obj = $(this);
899 var className = obj.attr("class");
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800900 var apiLevelIndex = className.lastIndexOf("-") + 1;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800901 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
902 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
903 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
904 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
905 return;
906 }
907 apiLevel = parseInt(apiLevel);
908
909 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
910 var selectedLevelNum = parseInt(selectedLevel)
911 var apiLevelNum = parseInt(apiLevel);
912 if (isNaN(apiLevelNum)) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800913 apiLevelNum = maxLevel;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800914 }
915
916 // Grey things out that aren't available and give a tooltip title
917 if (apiLevelNum > selectedLevelNum) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800918 obj.addClass("absent").attr("title", "Requires API Level \"" +
919 apiLevel + "\" or higher. To reveal, change the target API level " +
920 "above the left navigation.");
921 } else obj.removeClass("absent").removeAttr("title");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800922 });
923}
924
Dirk Dougherty541b4942014-02-14 18:31:53 -0800925/* ################# SIDENAV TREE VIEW ################### */
Dirk Dougherty541b4942014-02-14 18:31:53 -0800926/* TODO: eliminate redundancy with non-google functions */
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800927function init_google_navtree(navtree_id, toroot, root_nodes) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800928 var me = new Object();
929 me.toroot = toroot;
930 me.node = new Object();
931
932 me.node.li = document.getElementById(navtree_id);
Dirk Dougherty0dc81b92015-12-08 14:49:52 -0800933 if (!me.node.li) {
934 return;
935 }
936
Dirk Dougherty541b4942014-02-14 18:31:53 -0800937 me.node.children_data = root_nodes;
938 me.node.children = new Array();
939 me.node.children_ul = document.createElement("ul");
940 me.node.get_children_ul = function() { return me.node.children_ul; };
941 //me.node.children_ul.className = "children_ul";
942 me.node.li.appendChild(me.node.children_ul);
943 me.node.depth = 0;
944
945 get_google_node(me, me.node);
946}
947
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800948function new_google_node(me, mom, text, link, children_data, api_level) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800949 var node = new Object();
950 var child;
951 node.children = Array();
952 node.children_data = children_data;
953 node.depth = mom.depth + 1;
954 node.get_children_ul = function() {
955 if (!node.children_ul) {
956 node.children_ul = document.createElement("ul");
957 node.children_ul.className = "tree-list-children";
958 node.li.appendChild(node.children_ul);
959 }
960 return node.children_ul;
961 };
962 node.li = document.createElement("li");
963
964 mom.get_children_ul().appendChild(node.li);
965
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800966 if (link) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800967 child = document.createElement("a");
968
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800969 } else {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800970 child = document.createElement("span");
971 child.className = "tree-list-subtitle";
972
973 }
974 if (children_data != null) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800975 node.li.className = "nav-section";
Dirk Dougherty541b4942014-02-14 18:31:53 -0800976 node.label_div = document.createElement("div");
977 node.label_div.className = "nav-section-header-ref";
978 node.li.appendChild(node.label_div);
979 get_google_node(me, node);
980 node.label_div.appendChild(child);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800981 } else {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800982 node.li.appendChild(child);
983 }
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800984 if (link) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800985 child.href = me.toroot + link;
986 }
987 node.label = document.createTextNode(text);
988 child.appendChild(node.label);
989
990 node.children_ul = null;
991
992 return node;
993}
994
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800995function get_google_node(me, mom) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800996 mom.children_visited = true;
997 var linkText;
998 for (var i in mom.children_data) {
999 var node_data = mom.children_data[i];
1000 linkText = node_data[0];
1001
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001002 if (linkText.match("^" + "com.google.android") == "com.google.android") {
Dirk Dougherty541b4942014-02-14 18:31:53 -08001003 linkText = linkText.substr(19, linkText.length);
1004 }
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001005 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
1006 node_data[2], node_data[3]);
Dirk Dougherty541b4942014-02-14 18:31:53 -08001007 }
1008}
1009
Dirk Dougherty541b4942014-02-14 18:31:53 -08001010/****** NEW version of script to build google and sample navs dynamically ******/
1011// TODO: update Google reference docs to tolerate this new implementation
1012
1013var NODE_NAME = 0;
1014var NODE_HREF = 1;
1015var NODE_GROUP = 2;
1016var NODE_TAGS = 3;
1017var NODE_CHILDREN = 4;
1018
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001019function init_google_navtree2(navtree_id, data) {
1020 var $containerUl = $("#" + navtree_id);
Dirk Dougherty541b4942014-02-14 18:31:53 -08001021 for (var i in data) {
1022 var node_data = data[i];
1023 $containerUl.append(new_google_node2(node_data));
1024 }
1025
1026 // Make all third-generation list items 'sticky' to prevent them from collapsing
1027 $containerUl.find('li li li.nav-section').addClass('sticky');
1028
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001029 initExpandableNavItems("#" + navtree_id);
Dirk Dougherty541b4942014-02-14 18:31:53 -08001030}
1031
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001032function new_google_node2(node_data) {
Dirk Dougherty541b4942014-02-14 18:31:53 -08001033 var linkText = node_data[NODE_NAME];
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001034 if (linkText.match("^" + "com.google.android") == "com.google.android") {
Dirk Dougherty541b4942014-02-14 18:31:53 -08001035 linkText = linkText.substr(19, linkText.length);
1036 }
1037 var $li = $('<li>');
1038 var $a;
1039 if (node_data[NODE_HREF] != null) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001040 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >' +
1041 linkText + '</a>');
Dirk Dougherty541b4942014-02-14 18:31:53 -08001042 } else {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001043 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >' +
1044 linkText + '/</a>');
Dirk Dougherty541b4942014-02-14 18:31:53 -08001045 }
1046 var $childUl = $('<ul>');
1047 if (node_data[NODE_CHILDREN] != null) {
1048 $li.addClass("nav-section");
1049 $a = $('<div class="nav-section-header">').append($a);
1050 if (node_data[NODE_HREF] == null) $a.addClass('empty');
1051
1052 for (var i in node_data[NODE_CHILDREN]) {
1053 var child_node_data = node_data[NODE_CHILDREN][i];
1054 $childUl.append(new_google_node2(child_node_data));
1055 }
1056 $li.append($childUl);
1057 }
1058 $li.prepend($a);
1059
1060 return $li;
1061}
1062
Dirk Dougherty541b4942014-02-14 18:31:53 -08001063function showGoogleRefTree() {
1064 init_default_google_navtree(toRoot);
1065 init_default_gcm_navtree(toRoot);
1066}
1067
1068function init_default_google_navtree(toroot) {
1069 // load json file for navtree data
1070 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001071 // when the file is loaded, initialize the tree
1072 if (jqxhr.status === 200) {
1073 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
1074 highlightSidenav();
1075 }
Dirk Dougherty541b4942014-02-14 18:31:53 -08001076 });
1077}
1078
1079function init_default_gcm_navtree(toroot) {
1080 // load json file for navtree data
1081 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001082 // when the file is loaded, initialize the tree
1083 if (jqxhr.status === 200) {
1084 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
1085 highlightSidenav();
1086 }
Dirk Dougherty541b4942014-02-14 18:31:53 -08001087 });
1088}
1089
1090/* TOGGLE INHERITED MEMBERS */
1091
1092/* Toggle an inherited class (arrow toggle)
1093 * @param linkObj The link that was clicked.
1094 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
1095 * 'null' to simply toggle.
1096 */
1097function toggleInherited(linkObj, expand) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001098 var base = linkObj.getAttribute("id");
1099 var list = document.getElementById(base + "-list");
1100 var summary = document.getElementById(base + "-summary");
1101 var trigger = document.getElementById(base + "-trigger");
1102 var a = $(linkObj);
1103 if ((expand == null && a.hasClass("closed")) || expand) {
1104 list.style.display = "none";
1105 summary.style.display = "block";
smain@google.coma0a6d322016-02-05 17:34:22 -08001106 trigger.src = toRoot + "assets/images/styles/disclosure_up.png";
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001107 a.removeClass("closed");
1108 a.addClass("opened");
1109 } else if ((expand == null && a.hasClass("opened")) || (expand == false)) {
1110 list.style.display = "block";
1111 summary.style.display = "none";
smain@google.coma0a6d322016-02-05 17:34:22 -08001112 trigger.src = toRoot + "assets/images/styles/disclosure_down.png";
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001113 a.removeClass("opened");
1114 a.addClass("closed");
1115 }
1116 return false;
Dirk Dougherty541b4942014-02-14 18:31:53 -08001117}
1118
1119/* Toggle all inherited classes in a single table (e.g. all inherited methods)
1120 * @param linkObj The link that was clicked.
1121 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
1122 * 'null' to simply toggle.
1123 */
1124function toggleAllInherited(linkObj, expand) {
1125 var a = $(linkObj);
1126 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
1127 var expandos = $(".jd-expando-trigger", table);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001128 if ((expand == null && a.text() == "[Expand]") || expand) {
Dirk Dougherty541b4942014-02-14 18:31:53 -08001129 expandos.each(function(i) {
1130 toggleInherited(this, true);
1131 });
1132 a.text("[Collapse]");
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001133 } else if ((expand == null && a.text() == "[Collapse]") || (expand == false)) {
Dirk Dougherty541b4942014-02-14 18:31:53 -08001134 expandos.each(function(i) {
1135 toggleInherited(this, false);
1136 });
1137 a.text("[Expand]");
1138 }
1139 return false;
1140}
1141
1142/* Toggle all inherited members in the class (link in the class title)
1143 */
1144function toggleAllClassInherited() {
1145 var a = $("#toggleAllClassInherited"); // get toggle link from class title
1146 var toggles = $(".toggle-all", $("#body-content"));
1147 if (a.text() == "[Expand All]") {
1148 toggles.each(function(i) {
1149 toggleAllInherited(this, true);
1150 });
1151 a.text("[Collapse All]");
1152 } else {
1153 toggles.each(function(i) {
1154 toggleAllInherited(this, false);
1155 });
1156 a.text("[Expand All]");
1157 }
1158 return false;
1159}
1160
1161/* Expand all inherited members in the class. Used when initiating page search */
1162function ensureAllInheritedExpanded() {
1163 var toggles = $(".toggle-all", $("#body-content"));
1164 toggles.each(function(i) {
1165 toggleAllInherited(this, true);
1166 });
1167 $("#toggleAllClassInherited").text("[Collapse All]");
1168}
1169
Dirk Dougherty541b4942014-02-14 18:31:53 -08001170/* HANDLE KEY EVENTS
1171 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
1172 */
1173var agent = navigator['userAgent'].toLowerCase();
1174var mac = agent.indexOf("macintosh") != -1;
1175
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001176$(document).keydown(function(e) {
1177 var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
Dirk Dougherty541b4942014-02-14 18:31:53 -08001178 if (control && e.which == 70) { // 70 is "F"
1179 ensureAllInheritedExpanded();
1180 }
1181});
1182
Dirk Dougherty541b4942014-02-14 18:31:53 -08001183/* On-demand functions */
1184
1185/** Move sample code line numbers out of PRE block and into non-copyable column */
1186function initCodeLineNumbers() {
1187 var numbers = $("#codesample-block a.number");
1188 if (numbers.length) {
1189 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
1190 }
1191
1192 $(document).ready(function() {
1193 // select entire line when clicked
1194 $("span.code-line").click(function() {
1195 if (!shifted) {
1196 selectText(this);
1197 }
1198 });
1199 // invoke line link on double click
1200 $(".code-line").dblclick(function() {
1201 document.location.hash = $(this).attr('id');
1202 });
1203 // highlight the line when hovering on the number
1204 $("#codesample-line-numbers a.number").mouseover(function() {
1205 var id = $(this).attr('href');
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001206 $(id).css('background', '#e7e7e7');
Dirk Dougherty541b4942014-02-14 18:31:53 -08001207 });
1208 $("#codesample-line-numbers a.number").mouseout(function() {
1209 var id = $(this).attr('href');
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001210 $(id).css('background', 'none');
Dirk Dougherty541b4942014-02-14 18:31:53 -08001211 });
1212 });
1213}
1214
1215// create SHIFT key binder to avoid the selectText method when selecting multiple lines
1216var shifted = false;
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001217$(document).bind('keyup keydown', function(e) {
1218 shifted = e.shiftKey; return true;
1219});
Dirk Dougherty541b4942014-02-14 18:31:53 -08001220
1221// courtesy of jasonedelman.com
1222function selectText(element) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001223 var doc = document ,
1224 range, selection
1225 ;
1226 if (doc.body.createTextRange) { //ms
1227 range = doc.body.createTextRange();
1228 range.moveToElementText(element);
1229 range.select();
1230 } else if (window.getSelection) { //all others
1231 selection = window.getSelection();
1232 range = doc.createRange();
1233 range.selectNodeContents(element);
1234 selection.removeAllRanges();
1235 selection.addRange(range);
1236 }
Dirk Dougherty541b4942014-02-14 18:31:53 -08001237}
1238
Dirk Dougherty541b4942014-02-14 18:31:53 -08001239/** Display links and other information about samples that match the
1240 group specified by the URL */
1241function showSamples() {
1242 var group = $("#samples").attr('class');
1243 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
1244
1245 var $ul = $("<ul>");
1246 $selectedLi = $("#nav li.selected");
1247
1248 $selectedLi.children("ul").children("li").each(function() {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001249 var $li = $("<li>").append($(this).find("a").first().clone());
1250 $ul.append($li);
Dirk Dougherty541b4942014-02-14 18:31:53 -08001251 });
1252
1253 $("#samples").append($ul);
1254
1255}
Dirk Dougherty08032402014-02-15 10:14:35 -08001256
Dirk Dougherty08032402014-02-15 10:14:35 -08001257/* ########################################################## */
1258/* ################### RESOURCE CARDS ##################### */
1259/* ########################################################## */
1260
1261/** Handle resource queries, collections, and grids (sections). Requires
1262 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
1263
1264(function() {
Dirk Dougherty08032402014-02-15 10:14:35 -08001265 $(document).ready(function() {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001266 // Need to initialize hero carousel before other sections for dedupe
1267 // to work correctly.
1268 $('[data-carousel-query]').dacCarouselQuery();
1269
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001270 // Iterate over all instances and initialize a resource widget.
1271 $('.resource-widget').resourceWidget();
Dirk Dougherty08032402014-02-15 10:14:35 -08001272 });
1273
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001274 $.fn.widgetOptions = function() {
1275 return {
1276 cardSizes: (this.data('cardsizes') || '').split(','),
1277 maxResults: parseInt(this.data('maxresults'), 10) || Infinity,
1278 initialResults: this.data('initialResults'),
1279 itemsPerPage: this.data('itemsPerPage'),
1280 sortOrder: this.data('sortorder'),
1281 query: this.data('query'),
1282 section: this.data('section'),
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001283 /* Added by LFL 6/6/14 */
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001284 resourceStyle: this.data('resourcestyle') || 'card',
1285 stackSort: this.data('stacksort') || 'true',
1286 // For filter based resources
1287 allowDuplicates: this.data('allow-duplicates') || 'false'
Dirk Dougherty08032402014-02-15 10:14:35 -08001288 };
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001289 };
Dirk Dougherty08032402014-02-15 10:14:35 -08001290
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001291 $.fn.deprecateOldGridStyles = function() {
1292 var m = this.get(0).className.match(/\bcol-(\d+)\b/);
1293 if (m && !this.is('.cols > *')) {
1294 this.removeClass('col-' + m[1]);
1295 }
1296 return this;
1297 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001298
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001299 /*
1300 * Three types of resource layouts:
1301 * Flow - Uses a fixed row-height flow using float left style.
1302 * Carousel - Single card slideshow all same dimension absolute.
1303 * Stack - Uses fixed columns and flexible element height.
1304 */
1305 function initResourceWidget(widget, resources, opts) {
1306 var $widget = $(widget).deprecateOldGridStyles();
1307 var isFlow = $widget.hasClass('resource-flow-layout');
1308 var isCarousel = $widget.hasClass('resource-carousel-layout');
1309 var isStack = $widget.hasClass('resource-stack-layout');
1310
1311 opts = opts || $widget.widgetOptions();
1312 resources = resources || metadata.query(opts);
1313
1314 if (opts.maxResults !== undefined) {
1315 resources = resources.slice(0, opts.maxResults);
1316 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001317
1318 if (isFlow) {
1319 drawResourcesFlowWidget($widget, opts, resources);
1320 } else if (isCarousel) {
1321 drawResourcesCarouselWidget($widget, opts, resources);
1322 } else if (isStack) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001323 opts.numStacks = $widget.data('numstacks');
1324 drawResourcesStackWidget($widget, opts, resources);
Dirk Dougherty08032402014-02-15 10:14:35 -08001325 }
1326 }
1327
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001328 $.fn.resourceWidget = function(resources, options) {
1329 return this.each(function() {
1330 initResourceWidget(this, resources, options);
1331 });
1332 };
1333
Dirk Dougherty08032402014-02-15 10:14:35 -08001334 /* Initializes a Resource Carousel Widget */
1335 function drawResourcesCarouselWidget($widget, opts, resources) {
1336 $widget.empty();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001337 var plusone = false; // stop showing plusone buttons on cards
Dirk Dougherty08032402014-02-15 10:14:35 -08001338
1339 $widget.addClass('resource-card slideshow-container')
1340 .append($('<a>').addClass('slideshow-prev').text('Prev'))
1341 .append($('<a>').addClass('slideshow-next').text('Next'));
1342
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001343 var css = {'width': $widget.width() + 'px',
1344 'height': $widget.height() + 'px'};
Dirk Dougherty08032402014-02-15 10:14:35 -08001345
1346 var $ul = $('<ul>');
1347
1348 for (var i = 0; i < resources.length; ++i) {
Dirk Dougherty08032402014-02-15 10:14:35 -08001349 var $card = $('<a>')
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001350 .attr('href', cleanUrl(resources[i].url))
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001351 .decorateResourceCard(resources[i], plusone);
Dirk Dougherty08032402014-02-15 10:14:35 -08001352
1353 $('<li>').css(css)
1354 .append($card)
1355 .appendTo($ul);
1356 }
1357
1358 $('<div>').addClass('frame')
1359 .append($ul)
1360 .appendTo($widget);
1361
1362 $widget.dacSlideshow({
1363 auto: true,
1364 btnPrev: '.slideshow-prev',
1365 btnNext: '.slideshow-next'
1366 });
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001367 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001368
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001369 /* Initializes a Resource Card Stack Widget (column-based layout)
1370 Modified by LFL 6/6/14
1371 */
Dirk Dougherty08032402014-02-15 10:14:35 -08001372 function drawResourcesStackWidget($widget, opts, resources, sections) {
1373 // Don't empty widget, grab all items inside since they will be the first
1374 // items stacked, followed by the resource query
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001375 var plusone = false; // stop showing plusone buttons on cards
Dirk Dougherty08032402014-02-15 10:14:35 -08001376 var cards = $widget.find('.resource-card').detach().toArray();
1377 var numStacks = opts.numStacks || 1;
1378 var $stacks = [];
Dirk Dougherty08032402014-02-15 10:14:35 -08001379
1380 for (var i = 0; i < numStacks; ++i) {
1381 $stacks[i] = $('<div>').addClass('resource-card-stack')
1382 .appendTo($widget);
1383 }
1384
1385 var sectionResources = [];
1386
1387 // Extract any subsections that are actually resource cards
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001388 if (sections) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001389 for (i = 0; i < sections.length; ++i) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001390 if (!sections[i].sections || !sections[i].sections.length) {
1391 // Render it as a resource card
1392 sectionResources.push(
1393 $('<a>')
1394 .addClass('resource-card section-card')
1395 .attr('href', cleanUrl(sections[i].resource.url))
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001396 .decorateResourceCard(sections[i].resource, plusone)[0]
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001397 );
Dirk Dougherty08032402014-02-15 10:14:35 -08001398
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001399 } else {
1400 cards.push(
1401 $('<div>')
1402 .addClass('resource-card section-card-menu')
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001403 .decorateResourceSection(sections[i], plusone)[0]
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001404 );
1405 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001406 }
1407 }
1408
1409 cards = cards.concat(sectionResources);
1410
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001411 for (i = 0; i < resources.length; ++i) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001412 var $card = createResourceElement(resources[i], opts);
1413
1414 if (opts.resourceStyle.indexOf('related') > -1) {
1415 $card.addClass('related-card');
1416 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001417
1418 cards.push($card[0]);
1419 }
1420
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001421 if (opts.stackSort !== 'false') {
1422 for (i = 0; i < cards.length; ++i) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001423 // Find the stack with the shortest height, but give preference to
1424 // left to right order.
1425 var minHeight = $stacks[0].height();
1426 var minIndex = 0;
Dirk Dougherty08032402014-02-15 10:14:35 -08001427
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001428 for (var j = 1; j < numStacks; ++j) {
1429 var height = $stacks[j].height();
1430 if (height < minHeight - 45) {
1431 minHeight = height;
1432 minIndex = j;
1433 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001434 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001435
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001436 $stacks[minIndex].append($(cards[i]));
1437 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001438 }
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001439 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001440
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001441 /*
1442 Create a resource card using the given resource object and a list of html
1443 configured options. Returns a jquery object containing the element.
1444 */
1445 function createResourceElement(resource, opts, plusone) {
1446 var $el;
1447
1448 // The difference here is that generic cards are not entirely clickable
1449 // so its a div instead of an a tag, also the generic one is not given
1450 // the resource-card class so it appears with a transparent background
1451 // and can be styled in whatever way the css setup.
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001452 if (opts.resourceStyle === 'generic') {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001453 $el = $('<div>')
1454 .addClass('resource')
1455 .attr('href', cleanUrl(resource.url))
1456 .decorateResource(resource, opts);
1457 } else {
1458 var cls = 'resource resource-card';
1459
1460 $el = $('<a>')
1461 .addClass(cls)
1462 .attr('href', cleanUrl(resource.url))
1463 .decorateResourceCard(resource, plusone);
1464 }
1465
1466 return $el;
1467 }
1468
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001469 function createResponsiveFlowColumn(cardSize) {
1470 var cardWidth = parseInt(cardSize.match(/(\d+)/)[1], 10);
1471 var column = $('<div>').addClass('col-' + (cardWidth / 3) + 'of6');
1472 if (cardWidth < 9) {
1473 column.addClass('col-tablet-1of2');
1474 } else if (cardWidth > 9 && cardWidth < 18) {
1475 column.addClass('col-tablet-1of1');
1476 }
1477 if (cardWidth < 18) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001478 column.addClass('col-mobile-1of1');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001479 }
1480 return column;
1481 }
1482
Dirk Dougherty08032402014-02-15 10:14:35 -08001483 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
1484 function drawResourcesFlowWidget($widget, opts, resources) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001485 // We'll be doing our own modifications to opts.
1486 opts = $.extend({}, opts);
1487
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001488 $widget.empty().addClass('cols');
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001489 if (opts.itemsPerPage) {
1490 $('<div class="col-1of1 dac-section-links dac-text-center">')
1491 .append(
1492 $('<div class="dac-section-link dac-show-less" data-toggle="show-less">Less<i class="dac-sprite dac-auto-unfold-less"></i></div>'),
1493 $('<div class="dac-section-link dac-show-more" data-toggle="show-more">More<i class="dac-sprite dac-auto-unfold-more"></i></div>')
1494 )
1495 .appendTo($widget);
1496 }
1497
1498 $widget.data('options.resourceflow', opts);
1499 $widget.data('resources.resourceflow', resources);
1500
1501 drawResourceFlowPage($widget, opts, resources);
1502 }
1503
1504 function drawResourceFlowPage($widget, opts, resources) {
1505 var cardSizes = opts.cardSizes || ['6x6']; // 2015-08-09: dynamic card sizes are deprecated
1506 var i = opts.currentIndex || 0;
1507 var j = 0;
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001508 var plusone = false; // stop showing plusone buttons on cards
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001509 var firstPage = i === 0;
1510 var initialResults = opts.initialResults || opts.itemsPerPage || resources.length;
1511 var max = firstPage ? initialResults : i + opts.itemsPerPage;
1512 max = Math.min(resources.length, max);
Dirk Dougherty08032402014-02-15 10:14:35 -08001513
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001514 var page = $('<div class="resource-flow-page">');
1515 if (opts.itemsPerPage) {
1516 $widget.find('.dac-section-links').before(page);
1517 } else {
1518 $widget.append(page);
1519 }
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001520
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001521 while (i < max) {
Dirk Dougherty08032402014-02-15 10:14:35 -08001522 var cardSize = cardSizes[j++ % cardSizes.length];
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001523 cardSize = cardSize.replace(/^\s+|\s+$/, '');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001524
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001525 var column = createResponsiveFlowColumn(cardSize).appendTo(page);
Dirk Dougherty08032402014-02-15 10:14:35 -08001526
1527 // A stack has a third dimension which is the number of stacked items
1528 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
1529 var stackCount = 0;
1530 var $stackDiv = null;
1531
1532 if (isStack) {
1533 // Create a stack container which should have the dimensions defined
1534 // by the product of the items inside.
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001535 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1] +
1536 'x' + isStack[2] * isStack[3]) .appendTo(column);
Dirk Dougherty08032402014-02-15 10:14:35 -08001537 }
1538
1539 // Build each stack item or just a single item
1540 do {
1541 var resource = resources[i];
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001542
1543 var $card = createResourceElement(resources[i], opts, plusone);
1544
1545 $card.addClass('resource-card-' + cardSize +
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001546 ' resource-card-' + resource.type.toLowerCase());
Dirk Dougherty08032402014-02-15 10:14:35 -08001547
1548 if (isStack) {
1549 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001550 if (++stackCount === parseInt(isStack[3])) {
Dirk Dougherty08032402014-02-15 10:14:35 -08001551 $card.addClass('resource-card-row-stack-last');
1552 stackCount = 0;
1553 }
1554 } else {
1555 stackCount = 0;
1556 }
1557
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001558 $card.appendTo($stackDiv || column);
Dirk Dougherty08032402014-02-15 10:14:35 -08001559
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001560 } while (++i < max && stackCount > 0);
1561
1562 // Record number of pages viewed in analytics.
1563 if (!firstPage) {
1564 var clicks = Math.ceil((i - initialResults) / opts.itemsPerPage);
1565 ga('send', 'event', 'Cards', 'Click More', clicks);
1566 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001567 }
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001568
1569 opts.currentIndex = i;
1570 $widget.toggleClass('dac-has-more', i < resources.length);
1571 $widget.toggleClass('dac-has-less', !firstPage);
1572
1573 $widget.trigger('dac:domchange');
1574 if (opts.onRenderPage) {
1575 opts.onRenderPage(page);
1576 }
1577 }
1578
1579 function drawResourceFlowReset($widget, opts, resources) {
1580 $widget.find('.resource-flow-page')
1581 .slice(1)
1582 .remove();
1583 $widget.toggleClass('dac-has-more', true);
1584 $widget.toggleClass('dac-has-less', false);
1585
1586 opts.currentIndex = Math.min(opts.initialResults, resources.length);
1587
1588 ga('send', 'event', 'Cards', 'Click Less');
1589 }
1590
1591 /* A decorator for event functions which finds the surrounding widget and it's options */
1592 function wrapWithWidget(func) {
1593 return function(e) {
1594 if (e) e.preventDefault();
1595
1596 var $widget = $(this).closest('.resource-flow-layout');
1597 var opts = $widget.data('options.resourceflow');
1598 var resources = $widget.data('resources.resourceflow');
1599 func($widget, opts, resources);
1600 };
Dirk Dougherty08032402014-02-15 10:14:35 -08001601 }
1602
1603 /* Build a site map of resources using a section as a root. */
1604 function buildSectionList(opts) {
1605 if (opts.section && SECTION_BY_ID[opts.section]) {
1606 return SECTION_BY_ID[opts.section].sections || [];
1607 }
1608 return [];
1609 }
1610
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001611 function cleanUrl(url) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001612 if (url && url.indexOf('//') === -1) {
1613 url = toRoot + url;
1614 }
1615
1616 return url;
1617 }
1618
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001619 // Delegated events for resources.
1620 $(document).on('click', '.resource-flow-layout [data-toggle="show-more"]', wrapWithWidget(drawResourceFlowPage));
1621 $(document).on('click', '.resource-flow-layout [data-toggle="show-less"]', wrapWithWidget(drawResourceFlowReset));
Dirk Dougherty08032402014-02-15 10:14:35 -08001622})();
1623
1624(function($) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001625 // A mapping from category and type values to new values or human presentable strings.
1626 var SECTION_MAP = {
1627 googleplay: 'google play'
1628 };
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001629
1630 /*
1631 Utility method for creating dom for the description area of a card.
1632 Used in decorateResourceCard and decorateResource.
1633 */
1634 function buildResourceCardDescription(resource, plusone) {
1635 var $description = $('<div>').addClass('description ellipsis');
1636
1637 $description.append($('<div>').addClass('text').html(resource.summary));
1638
1639 if (resource.cta) {
1640 $description.append($('<a>').addClass('cta').html(resource.cta));
1641 }
1642
1643 if (plusone) {
1644 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
1645 "//developer.android.com/" + resource.url;
1646
1647 $description.append($('<div>').addClass('util')
1648 .append($('<div>').addClass('g-plusone')
1649 .attr('data-size', 'small')
1650 .attr('data-align', 'right')
1651 .attr('data-href', plusurl)));
1652 }
1653
1654 return $description;
1655 }
1656
Dirk Dougherty08032402014-02-15 10:14:35 -08001657 /* Simple jquery function to create dom for a standard resource card */
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001658 $.fn.decorateResourceCard = function(resource, plusone) {
1659 var section = resource.category || resource.type;
1660 section = (SECTION_MAP[section] || section).toLowerCase();
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001661 var imgUrl = resource.image ||
1662 'assets/images/resource-card-default-android.jpg';
1663
1664 if (imgUrl.indexOf('//') === -1) {
1665 imgUrl = toRoot + imgUrl;
Dirk Dougherty08032402014-02-15 10:14:35 -08001666 }
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001667
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001668 if (resource.type === 'youtube' || resource.type === 'video') {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001669 $('<div>').addClass('play-button')
1670 .append($('<i class="dac-sprite dac-play-white">'))
1671 .appendTo(this);
1672 }
1673
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001674 $('<div>').addClass('card-bg')
1675 .css('background-image', 'url(' + (imgUrl || toRoot +
1676 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Dougherty08032402014-02-15 10:14:35 -08001677 .appendTo(this);
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001678
1679 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
1680 .append($('<div>').addClass('section').text(section))
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001681 .append($('<div>').addClass('title' + (resource.title_highlighted ? ' highlighted' : ''))
1682 .html(resource.title_highlighted || resource.title))
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001683 .append(buildResourceCardDescription(resource, plusone))
1684 .appendTo(this);
Dirk Dougherty08032402014-02-15 10:14:35 -08001685
1686 return this;
1687 };
1688
1689 /* Simple jquery function to create dom for a resource section card (menu) */
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001690 $.fn.decorateResourceSection = function(section, plusone) {
Dirk Dougherty08032402014-02-15 10:14:35 -08001691 var resource = section.resource;
1692 //keep url clean for matching and offline mode handling
1693 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
1694 var $base = $('<a>')
1695 .addClass('card-bg')
1696 .attr('href', resource.url)
1697 .append($('<div>').addClass('card-section-icon')
1698 .append($('<div>').addClass('icon'))
1699 .append($('<div>').addClass('section').html(resource.title)))
1700 .appendTo(this);
1701
1702 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
1703
1704 if (section.sections && section.sections.length) {
1705 // Recurse the section sub-tree to find a resource image.
1706 var stack = [section];
1707
1708 while (stack.length) {
1709 if (stack[0].resource.image) {
1710 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
1711 break;
1712 }
1713
1714 if (stack[0].sections) {
1715 stack = stack.concat(stack[0].sections);
1716 }
1717
1718 stack.shift();
1719 }
1720
1721 var $ul = $('<ul>')
1722 .appendTo($cardInfo);
1723
1724 var max = section.sections.length > 3 ? 3 : section.sections.length;
1725
1726 for (var i = 0; i < max; ++i) {
1727
1728 var subResource = section.sections[i];
Dirk Dougherty318fb972014-04-08 18:46:53 -07001729 if (!plusone) {
1730 $('<li>')
1731 .append($('<a>').attr('href', subResource.url)
1732 .append($('<div>').addClass('title').html(subResource.title))
1733 .append($('<div>').addClass('description ellipsis')
1734 .append($('<div>').addClass('text').html(subResource.summary))
1735 .append($('<div>').addClass('util'))))
1736 .appendTo($ul);
1737 } else {
1738 $('<li>')
1739 .append($('<a>').attr('href', subResource.url)
1740 .append($('<div>').addClass('title').html(subResource.title))
1741 .append($('<div>').addClass('description ellipsis')
1742 .append($('<div>').addClass('text').html(subResource.summary))
1743 .append($('<div>').addClass('util')
1744 .append($('<div>').addClass('g-plusone')
1745 .attr('data-size', 'small')
1746 .attr('data-align', 'right')
1747 .attr('data-href', resource.url)))))
1748 .appendTo($ul);
1749 }
Dirk Dougherty08032402014-02-15 10:14:35 -08001750 }
1751
1752 // Add a more row
1753 if (max < section.sections.length) {
1754 $('<li>')
1755 .append($('<a>').attr('href', resource.url)
1756 .append($('<div>')
1757 .addClass('title')
1758 .text('More')))
1759 .appendTo($ul);
1760 }
1761 } else {
1762 // No sub-resources, just render description?
1763 }
1764
1765 return this;
1766 };
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001767
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001768 /* Render other types of resource styles that are not cards. */
1769 $.fn.decorateResource = function(resource, opts) {
1770 var imgUrl = resource.image ||
1771 'assets/images/resource-card-default-android.jpg';
1772 var linkUrl = resource.url;
1773
1774 if (imgUrl.indexOf('//') === -1) {
1775 imgUrl = toRoot + imgUrl;
1776 }
1777
1778 if (linkUrl && linkUrl.indexOf('//') === -1) {
1779 linkUrl = toRoot + linkUrl;
1780 }
1781
1782 $(this).append(
1783 $('<div>').addClass('image')
1784 .css('background-image', 'url(' + imgUrl + ')'),
1785 $('<div>').addClass('info').append(
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001786 $('<h4>').addClass('title').html(resource.title_highlighted || resource.title),
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001787 $('<p>').addClass('summary').html(resource.summary),
1788 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
1789 )
1790 );
1791
1792 return this;
1793 };
Dirk Dougherty08032402014-02-15 10:14:35 -08001794})(jQuery);
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001795
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001796/*
1797 Fullscreen Carousel
1798
1799 The following allows for an area at the top of the page that takes over the
1800 entire browser height except for its top offset and an optional bottom
1801 padding specified as a data attribute.
1802
1803 HTML:
1804
1805 <div class="fullscreen-carousel">
1806 <div class="fullscreen-carousel-content">
1807 <!-- content here -->
1808 </div>
1809 <div class="fullscreen-carousel-content">
1810 <!-- content here -->
1811 </div>
1812
1813 etc ...
1814
1815 </div>
1816
1817 Control over how the carousel takes over the screen can mostly be defined in
1818 a css file. Setting min-height on the .fullscreen-carousel-content elements
1819 will prevent them from shrinking to far vertically when the browser is very
1820 short, and setting max-height on the .fullscreen-carousel itself will prevent
1821 the area from becoming to long in the case that the browser is stretched very
1822 tall.
1823
1824 There is limited functionality for having multiple sections since that request
1825 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
1826 scroll between multiple content areas.
1827*/
1828
1829(function() {
1830 $(document).ready(function() {
1831 $('.fullscreen-carousel').each(function() {
1832 initWidget(this);
1833 });
1834 });
1835
1836 function initWidget(widget) {
1837 var $widget = $(widget);
1838
1839 var topOffset = $widget.offset().top;
1840 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
1841 var maxHeight = 0;
1842 var minHeight = 0;
1843 var $content = $widget.find('.fullscreen-carousel-content');
1844 var $nextArrow = $widget.find('.next-arrow');
1845 var $prevArrow = $widget.find('.prev-arrow');
1846 var $curSection = $($content[0]);
1847
1848 if ($content.length <= 1) {
1849 $nextArrow.hide();
1850 $prevArrow.hide();
1851 } else {
1852 $nextArrow.click(function() {
1853 var index = ($content.index($curSection) + 1);
1854 $curSection.hide();
1855 $curSection = $($content[index >= $content.length ? 0 : index]);
1856 $curSection.show();
1857 });
1858
1859 $prevArrow.click(function() {
1860 var index = ($content.index($curSection) - 1);
1861 $curSection.hide();
1862 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
1863 $curSection.show();
1864 });
1865 }
1866
1867 // Just hide all content sections except first.
1868 $content.each(function(index) {
1869 if ($(this).height() > minHeight) minHeight = $(this).height();
1870 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
1871 });
1872
1873 // Register for changes to window size, and trigger.
1874 $(window).resize(resizeWidget);
1875 resizeWidget();
1876
1877 function resizeWidget() {
1878 var height = $(window).height() - topOffset - padBottom;
1879 $widget.width($(window).width());
1880 $widget.height(height < minHeight ? minHeight :
1881 (maxHeight && height > maxHeight ? maxHeight : height));
1882 }
1883 }
1884})();
1885
Dirk Doughertyff233cc2015-05-04 14:37:05 -07001886/*
1887 Tab Carousel
1888
1889 The following allows tab widgets to be installed via the html below. Each
1890 tab content section should have a data-tab attribute matching one of the
1891 nav items'. Also each tab content section should have a width matching the
1892 tab carousel.
1893
1894 HTML:
1895
1896 <div class="tab-carousel">
1897 <ul class="tab-nav">
1898 <li><a href="#" data-tab="handsets">Handsets</a>
1899 <li><a href="#" data-tab="wearable">Wearable</a>
1900 <li><a href="#" data-tab="tv">TV</a>
1901 </ul>
1902
1903 <div class="tab-carousel-content">
1904 <div data-tab="handsets">
1905 <!--Full width content here-->
1906 </div>
1907
1908 <div data-tab="wearable">
1909 <!--Full width content here-->
1910 </div>
1911
1912 <div data-tab="tv">
1913 <!--Full width content here-->
1914 </div>
1915 </div>
1916 </div>
1917
1918*/
1919(function() {
1920 $(document).ready(function() {
1921 $('.tab-carousel').each(function() {
1922 initWidget(this);
1923 });
1924 });
1925
1926 function initWidget(widget) {
1927 var $widget = $(widget);
1928 var $nav = $widget.find('.tab-nav');
1929 var $anchors = $nav.find('[data-tab]');
1930 var $li = $nav.find('li');
1931 var $contentContainer = $widget.find('.tab-carousel-content');
1932 var $tabs = $contentContainer.find('[data-tab]');
1933 var $curTab = $($tabs[0]); // Current tab is first tab.
1934 var width = $widget.width();
1935
1936 // Setup nav interactivity.
1937 $anchors.click(function(evt) {
1938 evt.preventDefault();
1939 var query = '[data-tab=' + $(this).data('tab') + ']';
1940 transitionWidget($tabs.filter(query));
1941 });
1942
1943 // Add highlight for navigation on first item.
1944 var $highlight = $('<div>').addClass('highlight')
1945 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
1946 .appendTo($nav);
1947
1948 // Store height since we will change contents to absolute.
1949 $contentContainer.height($contentContainer.height());
1950
1951 // Absolutely position tabs so they're ready for transition.
1952 $tabs.each(function(index) {
1953 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
1954 });
1955
1956 function transitionWidget($toTab) {
1957 if (!$curTab.is($toTab)) {
1958 var curIndex = $tabs.index($curTab[0]);
1959 var toIndex = $tabs.index($toTab[0]);
1960 var dir = toIndex > curIndex ? 1 : -1;
1961
1962 // Animate content sections.
1963 $toTab.css({left:(width * dir) + 'px'});
1964 $curTab.animate({left:(width * -dir) + 'px'});
1965 $toTab.animate({left:'0'});
1966
1967 // Animate navigation highlight.
1968 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
1969 width:$($li[toIndex]).outerWidth() + 'px'})
1970
1971 // Store new current section.
1972 $curTab = $toTab;
1973 }
1974 }
1975 }
1976})();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001977
1978/**
1979 * Auto TOC
1980 *
1981 * Upgrades h2s on the page to have a rule and be toggle-able on mobile.
1982 */
1983(function($) {
1984 var upgraded = false;
1985 var h2Titles;
1986
1987 function initWidget() {
1988 // add HRs below all H2s (except for a few other h2 variants)
1989 // Consider doing this with css instead.
1990 h2Titles = $('h2').not('#qv h2, #tb h2, .sidebox h2, #devdoc-nav h2, h2.norule');
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001991 h2Titles.css({paddingBottom:0}).after('<hr/>');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08001992
1993 // Exit early if on older browser.
1994 if (!window.matchMedia) {
1995 return;
1996 }
1997
1998 // Only run logic in mobile layout.
1999 var query = window.matchMedia('(max-width: 719px)');
2000 if (query.matches) {
2001 makeTogglable();
2002 } else {
2003 query.addListener(makeTogglable);
2004 }
2005 }
2006
2007 function makeTogglable() {
2008 // Only run this logic once.
2009 if (upgraded) { return; }
2010 upgraded = true;
2011
2012 // Only make content h2s togglable.
2013 var contentTitles = h2Titles.filter('#jd-content *');
2014
2015 // If there are more than 1
2016 if (contentTitles.size() < 2) {
2017 return;
2018 }
2019
2020 contentTitles.each(function() {
2021 // Find all the relevant nodes.
2022 var $title = $(this);
2023 var $hr = $title.next();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08002024 var $contents = allNextUntil($hr[0], 'h2, .next-docs');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002025 var $section = $($title)
2026 .add($hr)
2027 .add($title.prev('a[name]'))
2028 .add($contents);
2029 var $anchor = $section.first().prev();
2030 var anchorMethod = 'after';
2031 if ($anchor.length === 0) {
2032 $anchor = $title.parent();
2033 anchorMethod = 'prepend';
2034 }
2035
2036 // Some h2s are in their own container making it pretty hard to find the end, so skip.
2037 if ($contents.length === 0) {
2038 return;
2039 }
2040
2041 // Remove from DOM before messing with it. DOM is slow!
2042 $section.detach();
2043
2044 // Add mobile-only expand arrows.
2045 $title.prepend('<span class="dac-visible-mobile-inline-block">' +
2046 '<i class="dac-toggle-expand dac-sprite dac-expand-more-black"></i>' +
2047 '<i class="dac-toggle-collapse dac-sprite dac-expand-less-black"></i>' +
2048 '</span>')
2049 .attr('data-toggle', 'section');
2050
2051 // Wrap in magic markup.
2052 $section = $section.wrapAll('<div class="dac-toggle dac-mobile">').parent();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08002053
2054 // extra div used for max-height calculation.
2055 $contents.wrapAll('<div class="dac-toggle-content dac-expand"><div>');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002056
2057 // Pre-expand section if requested.
2058 if ($title.hasClass('is-expanded')) {
2059 $section.addClass('is-expanded');
2060 }
2061
2062 // Pre-expand section if targetted by hash.
2063 if (location.hash && $section.find(location.hash).length) {
2064 $section.addClass('is-expanded');
2065 }
2066
2067 // Add it back to the dom.
2068 $anchor[anchorMethod].call($anchor, $section);
2069 });
2070 }
2071
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08002072 // Similar to $.fn.nextUntil() except we need all nodes, jQuery skips text nodes.
2073 function allNextUntil(elem, until) {
2074 var matched = [];
2075
2076 while ((elem = elem.nextSibling) && elem.nodeType !== 9) {
2077 if (elem.nodeType === 1 && jQuery(elem).is(until)) {
2078 break;
2079 }
2080 matched.push(elem);
2081 }
2082 return $(matched);
2083 }
2084
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002085 $(function() {
2086 initWidget();
2087 });
2088})(jQuery);
2089
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08002090(function($, window) {
2091 'use strict';
2092
2093 // Blogger API info
2094 var apiUrl = 'https://www.googleapis.com/blogger/v3';
2095 var apiKey = 'AIzaSyCFhbGnjW06dYwvRCU8h_zjdpS4PYYbEe8';
2096
2097 // Blog IDs can be found in the markup of the blog posts
2098 var blogs = {
2099 'android-developers': {
2100 id: '6755709643044947179',
2101 title: 'Android Developers Blog'
2102 }
2103 };
2104 var monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
2105 'July', 'August', 'September', 'October', 'November', 'December'];
2106
2107 var BlogReader = (function() {
2108 var reader;
2109
2110 function BlogReader() {
2111 this.doneSetup = false;
2112 }
2113
2114 /**
2115 * Initialize the blog reader and modal.
2116 */
2117 BlogReader.prototype.setup = function() {
2118 $('#jd-content').append(
2119 '<div id="blog-reader" data-modal="blog-reader" class="dac-modal dac-has-small-header">' +
2120 '<div class="dac-modal-container">' +
2121 '<div class="dac-modal-window">' +
2122 '<header class="dac-modal-header">' +
2123 '<div class="dac-modal-header-actions">' +
2124 '<a href="" class="dac-modal-header-open" target="_blank">' +
2125 '<i class="dac-sprite dac-open-in-new"></i>' +
2126 '</a>' +
2127 '<button class="dac-modal-header-close" data-modal-toggle>' +
2128 '</button>' +
2129 '</div>' +
2130 '<h2 class="norule dac-modal-header-title"></h2>' +
2131 '</header>' +
2132 '<div class="dac-modal-content dac-blog-reader">' +
2133 '<time class="dac-blog-reader-date" pubDate></time>' +
2134 '<h3 class="dac-blog-reader-title"></h3>' +
2135 '<div class="dac-blog-reader-text clearfix"></div>' +
2136 '</div>' +
2137 '</div>' +
2138 '</div>' +
2139 '</div>');
2140
2141 this.blogReader = $('#blog-reader').dacModal();
2142
2143 this.doneSetup = true;
2144 };
2145
2146 BlogReader.prototype.openModal_ = function(blog, post) {
2147 var published = new Date(post.published);
2148 var formattedDate = monthNames[published.getMonth()] + ' ' + published.getDay() + ' ' + published.getFullYear();
2149 this.blogReader.find('.dac-modal-header-open').attr('href', post.url);
2150 this.blogReader.find('.dac-modal-header-title').text(blog.title);
2151 this.blogReader.find('.dac-blog-reader-title').html(post.title);
2152 this.blogReader.find('.dac-blog-reader-date').html(formattedDate);
2153 this.blogReader.find('.dac-blog-reader-text').html(post.content);
2154 this.blogReader.trigger('modal-open');
2155 };
2156
2157 /**
2158 * Show a blog post in a modal
2159 * @param {string} blogName - The name of the Blogspot blog.
2160 * @param {string} postPath - The path to the blog post.
2161 * @param {bool} secondTry - Has it failed once?
2162 */
2163 BlogReader.prototype.showPost = function(blogName, postPath, secondTry) {
2164 var blog = blogs[blogName];
2165 var postUrl = 'https://' + blogName + '.blogspot.com' + postPath;
2166
2167 var url = apiUrl + '/blogs/' + blog.id + '/posts/bypath?path=' + encodeURIComponent(postPath) + '&key=' + apiKey;
2168 $.ajax(url, {timeout: 650}).done(this.openModal_.bind(this, blog)).fail(function(error) {
2169 // Retry once if we get an error
2170 if (error.status === 500 && !secondTry) {
2171 this.showPost(blogName, postPath, true);
2172 } else {
2173 window.location.href = postUrl;
2174 }
2175 }.bind(this));
2176 };
2177
2178 return {
2179 getReader: function() {
2180 if (!reader) {
2181 reader = new BlogReader();
2182 }
2183 return reader;
2184 }
2185 };
2186 })();
2187
2188 var blogReader = BlogReader.getReader();
2189
2190 function wrapLinkWithReader(e) {
2191 var el = $(e.currentTarget);
2192 if (el.hasClass('dac-modal-header-open')) {
2193 return;
2194 }
2195
2196 // Only catch links on blogspot.com
2197 var matches = el.attr('href').match(/https?:\/\/([^\.]*).blogspot.com([^$]*)/);
2198 if (matches && matches.length === 3) {
2199 var blogName = matches[1];
2200 var postPath = matches[2];
2201
2202 // Check if we have information about the blog
2203 if (!blogs[blogName]) {
2204 return;
2205 }
2206
2207 // Setup the first time it's used
2208 if (!blogReader.doneSetup) {
2209 blogReader.setup();
2210 }
2211
2212 e.preventDefault();
2213 blogReader.showPost(blogName, postPath);
2214 }
2215 }
2216
2217 $(document).on('click.blog-reader', 'a[href*="blogspot.com/"]', wrapLinkWithReader);
2218})(jQuery, window);
2219
2220(function($) {
2221 $.fn.debounce = function(func, wait, immediate) {
2222 var timeout;
2223
2224 return function() {
2225 var context = this;
2226 var args = arguments;
2227
2228 var later = function() {
2229 timeout = null;
2230 if (!immediate) {
2231 func.apply(context, args);
2232 }
2233 };
2234
2235 var callNow = immediate && !timeout;
2236 clearTimeout(timeout);
2237 timeout = setTimeout(later, wait);
2238
2239 if (callNow) {
2240 func.apply(context, args);
2241 }
2242 };
2243 };
2244})(jQuery);
2245
2246/* Calculate the vertical area remaining */
2247(function($) {
2248 $.fn.ellipsisfade = function() {
2249 // Only fetch line-height of first element to avoid recalculate style.
2250 // Will be NaN if no elements match, which is ok.
2251 var lineHeight = parseInt(this.css('line-height'), 10);
2252
2253 this.each(function() {
2254 // get element text
2255 var $this = $(this);
2256 var remainingHeight = $this.parent().parent().height();
2257 $this.parent().siblings().each(function() {
2258 var elHeight;
2259 if ($(this).is(':visible')) {
2260 elHeight = $(this).outerHeight(true);
2261 remainingHeight = remainingHeight - elHeight;
2262 }
2263 });
2264
2265 var adjustedRemainingHeight = ((remainingHeight) / lineHeight >> 0) * lineHeight;
2266 $this.parent().css({height: adjustedRemainingHeight});
2267 $this.css({height: 'auto'});
2268 });
2269
2270 return this;
2271 };
2272
2273 /* Pass the line height to ellipsisfade() to adjust the height of the
2274 text container to show the max number of lines possible, without
2275 showing lines that are cut off. This works with the css ellipsis
2276 classes to fade last text line and apply an ellipsis char. */
2277 function updateEllipsis(context) {
2278 if (!(context instanceof jQuery)) {
2279 context = $('html');
2280 }
2281
2282 context.find('.card-info .text').ellipsisfade();
2283 }
2284
2285 $(window).on('resize', $.fn.debounce(updateEllipsis, 500));
2286 $(updateEllipsis);
2287 $('html').on('dac:domchange', function(e) { updateEllipsis($(e.target)); });
2288})(jQuery);
2289
2290/* Filter */
2291(function($) {
2292 'use strict';
2293
2294 /**
2295 * A single filter item content.
2296 * @type {string} - Element template.
2297 * @private
2298 */
2299 var ITEM_STR_ = '<input type="checkbox" value="{{value}}" class="dac-form-checkbox" id="{{id}}">' +
2300 '<label for="{{id}}" class="dac-form-checkbox-button"></label>' +
2301 '<label for="{{id}}" class="dac-form-label">{{name}}</label>';
2302
2303 /**
2304 * Template for a chip element.
2305 * @type {*|HTMLElement}
2306 * @private
2307 */
2308 var CHIP_BASE_ = $('<li class="dac-filter-chip">' +
2309 '<button class="dac-filter-chip-close">' +
2310 '<i class="dac-sprite dac-close-black dac-filter-chip-close-icon"></i>' +
2311 '</button>' +
2312 '</li>');
2313
2314 /**
2315 * Component to handle narrowing down resources.
2316 * @param {HTMLElement} el - The DOM element.
2317 * @param {Object} options
2318 * @constructor
2319 */
2320 function Filter(el, options) {
2321 this.el = $(el);
2322 this.options = $.extend({}, Filter.DEFAULTS_, options);
2323 this.init();
2324 }
2325
2326 Filter.DEFAULTS_ = {
2327 activeClass: 'dac-active',
2328 chipsDataAttr: 'filter-chips',
2329 nameDataAttr: 'filter-name',
2330 countDataAttr: 'filter-count',
2331 tabViewDataAttr: 'tab-view',
2332 valueDataAttr: 'filter-value'
2333 };
2334
2335 /**
2336 * Draw resource cards.
2337 * @param {Array} resources
2338 * @private
2339 */
2340 Filter.prototype.draw_ = function(resources) {
2341 var that = this;
2342
2343 if (resources.length === 0) {
2344 this.containerEl_.html('<p class="dac-filter-message">Nothing matches selected filters.</p>');
2345 return;
2346 }
2347
2348 // Draw resources.
2349 that.containerEl_.resourceWidget(resources, that.data_.options);
2350 };
2351
2352 /**
2353 * Initialize a Filter component.
2354 */
2355 Filter.prototype.init = function() {
2356 this.containerEl_ = $(this.options.filter);
2357
2358 // Setup data settings
2359 this.data_ = {};
2360 this.data_.chips = {};
2361 this.data_.options = this.containerEl_.widgetOptions();
2362 this.data_.all = window.metadata.query(this.data_.options);
2363
2364 // Initialize filter UI
2365 this.initUi();
2366 };
2367
2368 /**
2369 * Generate a chip for a given filter item.
2370 * @param {Object} item - A single filter option (checkbox container).
2371 * @returns {HTMLElement} A new Chip element.
2372 */
2373 Filter.prototype.chipForItem = function(item) {
2374 var chip = CHIP_BASE_.clone();
2375 chip.prepend(this.data_.chips[item.data('filter-value')]);
2376 chip.data('item.dac-filter', item);
2377 item.data('chip.dac-filter', chip);
2378 this.addToItemValue(item, 1);
2379 return chip[0];
2380 };
2381
2382 /**
2383 * Update count of checked filter items.
2384 * @param {Object} item - A single filter option (checkbox container).
2385 * @param {Number} value - Either -1 or 1.
2386 */
2387 Filter.prototype.addToItemValue = function(item, value) {
2388 var tab = item.parent().data(this.options.tabViewDataAttr);
2389 var countEl = this.countEl_.filter('[data-' + this.options.countDataAttr + '="' + tab + '"]');
2390 var count = value + parseInt(countEl.text(), 10);
2391 countEl.text(count);
2392 countEl.toggleClass('dac-disabled', count === 0);
2393 };
2394
2395 /**
2396 * Set event listeners.
2397 * @private
2398 */
2399 Filter.prototype.setEventListeners_ = function() {
2400 this.chipsEl_.on('click.dac-filter', '.dac-filter-chip-close', this.closeChipHandler_.bind(this));
2401 this.tabViewEl_.on('change.dac-filter', ':checkbox', this.toggleCheckboxHandler_.bind(this));
2402 };
2403
2404 /**
2405 * Check filter items that are active by default.
2406 */
2407 Filter.prototype.activateInitialFilters_ = function() {
2408 var id = (new Date()).getTime();
2409 var initiallyCheckedValues = this.data_.options.query.replace(/,\s*/g, '+').split('+');
2410 var chips = document.createDocumentFragment();
2411 var that = this;
2412
2413 this.items_.each(function(i) {
2414 var item = $(this);
2415 var opts = item.data();
2416 that.data_.chips[opts.filterValue] = opts.filterName;
2417
2418 var checkbox = $(ITEM_STR_.replace(/\{\{name\}\}/g, opts.filterName)
2419 .replace(/\{\{value\}\}/g, opts.filterValue)
2420 .replace(/\{\{id\}\}/g, 'filter-' + id + '-' + (i + 1)));
2421
2422 if (initiallyCheckedValues.indexOf(opts.filterValue) > -1) {
2423 checkbox[0].checked = true;
2424 chips.appendChild(that.chipForItem(item));
2425 }
2426
2427 item.append(checkbox);
2428 });
2429
2430 this.chipsEl_.append(chips);
2431 };
2432
2433 /**
2434 * Initialize the Filter view
2435 */
2436 Filter.prototype.initUi = function() {
2437 // Cache DOM elements
2438 this.chipsEl_ = this.el.find('[data-' + this.options.chipsDataAttr + ']');
2439 this.countEl_ = this.el.find('[data-' + this.options.countDataAttr + ']');
2440 this.tabViewEl_ = this.el.find('[data-' + this.options.tabViewDataAttr + ']');
2441 this.items_ = this.el.find('[data-' + this.options.nameDataAttr + ']');
2442
2443 // Setup UI
2444 this.draw_(this.data_.all);
2445 this.activateInitialFilters_();
2446 this.setEventListeners_();
2447 };
2448
2449 /**
2450 * @returns {[types|Array, tags|Array, category|Array]}
2451 */
2452 Filter.prototype.getActiveClauses = function() {
2453 var tags = [];
2454 var types = [];
2455 var categories = [];
2456
2457 this.items_.find(':checked').each(function(i, checkbox) {
2458 // Currently, there is implicit business logic here that `tag` is AND'ed together
2459 // while `type` is OR'ed. So , and + do the same thing here. It would be great to
2460 // reuse the same query engine for filters, but it would need more powerful syntax.
2461 // Probably parenthesis, to support "tag:dog + tag:cat + (type:video, type:blog)"
2462 var expression = $(checkbox).val();
2463 var regex = /(\w+):(\w+)/g;
2464 var match;
2465
2466 while (match = regex.exec(expression)) {
2467 switch (match[1]) {
2468 case 'category':
2469 categories.push(match[2]);
2470 break;
2471 case 'tag':
2472 tags.push(match[2]);
2473 break;
2474 case 'type':
2475 types.push(match[2]);
2476 break;
2477 }
2478 }
2479 });
2480
2481 return [types, tags, categories];
2482 };
2483
2484 /**
2485 * Actual filtering logic.
2486 * @returns {Array}
2487 */
2488 Filter.prototype.filteredResources = function() {
2489 var data = this.getActiveClauses();
2490 var types = data[0];
2491 var tags = data[1];
2492 var categories = data[2];
2493 var resources = [];
2494 var resource = {};
2495 var tag = '';
2496 var shouldAddResource = true;
2497
2498 for (var resourceIndex = 0; resourceIndex < this.data_.all.length; resourceIndex++) {
2499 resource = this.data_.all[resourceIndex];
2500 shouldAddResource = types.indexOf(resource.type) > -1;
2501
2502 if (categories && categories.length > 0) {
2503 shouldAddResource = shouldAddResource && categories.indexOf(resource.category) > -1;
2504 }
2505
2506 for (var tagIndex = 0; shouldAddResource && tagIndex < tags.length; tagIndex++) {
2507 tag = tags[tagIndex];
2508 shouldAddResource = resource.tags.indexOf(tag) > -1;
2509 }
2510
2511 if (shouldAddResource) {
2512 resources.push(resource);
2513 }
2514 }
2515
2516 return resources;
2517 };
2518
2519 /**
2520 * Close Chip Handler
2521 * @param {Event} event - Click event
2522 * @private
2523 */
2524 Filter.prototype.closeChipHandler_ = function(event) {
2525 var chip = $(event.currentTarget).parent();
2526 var checkbox = chip.data('item.dac-filter').find(':first-child')[0];
2527 checkbox.checked = false;
2528 this.changeStateForCheckbox(checkbox);
2529 };
2530
2531 /**
2532 * Handle filter item state change.
2533 * @param {Event} event - Change event
2534 * @private
2535 */
2536 Filter.prototype.toggleCheckboxHandler_ = function(event) {
2537 this.changeStateForCheckbox(event.currentTarget);
2538 };
2539
2540 /**
2541 * Redraw resource view based on new state.
2542 * @param checkbox
2543 */
2544 Filter.prototype.changeStateForCheckbox = function(checkbox) {
2545 var item = $(checkbox).parent();
2546
2547 if (checkbox.checked) {
2548 this.chipsEl_.append(this.chipForItem(item));
2549 ga('send', 'event', 'Filters', 'Check', $(checkbox).val());
2550 } else {
2551 item.data('chip.dac-filter').remove();
2552 this.addToItemValue(item, -1);
2553 ga('send', 'event', 'Filters', 'Uncheck', $(checkbox).val());
2554 }
2555
2556 this.draw_(this.filteredResources());
2557 };
2558
2559 /**
2560 * jQuery plugin
2561 */
2562 $.fn.dacFilter = function() {
2563 return this.each(function() {
2564 var el = $(this);
2565 new Filter(el, el.data());
2566 });
2567 };
2568
2569 /**
2570 * Data Attribute API
2571 */
2572 $(function() {
2573 $('[data-filter]').dacFilter();
2574 });
2575})(jQuery);
2576
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002577(function($) {
2578 'use strict';
2579
2580 /**
2581 * Toggle Floating Label state.
2582 * @param {HTMLElement} el - The DOM element.
2583 * @param options
2584 * @constructor
2585 */
2586 function FloatingLabel(el, options) {
2587 this.el = $(el);
2588 this.options = $.extend({}, FloatingLabel.DEFAULTS_, options);
2589 this.group = this.el.closest('.dac-form-input-group');
2590 this.input = this.group.find('.dac-form-input');
2591
2592 this.checkValue_ = this.checkValue_.bind(this);
2593 this.checkValue_();
2594
2595 this.input.on('focus', function() {
2596 this.group.addClass('dac-focused');
2597 }.bind(this));
2598 this.input.on('blur', function() {
2599 this.group.removeClass('dac-focused');
2600 this.checkValue_();
2601 }.bind(this));
2602 this.input.on('keyup', this.checkValue_);
2603 }
2604
2605 /**
2606 * The label is moved out of the textbox when it has a value.
2607 */
2608 FloatingLabel.prototype.checkValue_ = function() {
2609 if (this.input.val().length) {
2610 this.group.addClass('dac-has-value');
2611 } else {
2612 this.group.removeClass('dac-has-value');
2613 }
2614 };
2615
2616 /**
2617 * jQuery plugin
2618 * @param {object} options - Override default options.
2619 */
2620 $.fn.dacFloatingLabel = function(options) {
2621 return this.each(function() {
2622 new FloatingLabel(this, options);
2623 });
2624 };
2625
2626 $(document).on('ready.aranja', function() {
2627 $('.dac-form-floatlabel').each(function() {
2628 $(this).dacFloatingLabel($(this).data());
2629 });
2630 });
2631})(jQuery);
2632
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002633(function($) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08002634 'use strict';
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002635
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08002636 /**
2637 * @param {HTMLElement} el - The DOM element.
2638 * @param {Object} options
2639 * @constructor
2640 */
2641 function Crumbs(selected, options) {
2642 this.options = $.extend({}, Crumbs.DEFAULTS_, options);
2643 this.el = $(this.options.container);
2644
2645 // Do not build breadcrumbs for landing site.
2646 if (!selected || location.pathname === '/index.html' || location.pathname === '/') {
2647 return;
2648 }
2649
2650 // Cache navigation resources
2651 this.selected = $(selected);
2652 this.selectedParent = this.selected.closest('.dac-nav-secondary').siblings('a');
2653
2654 // Build the breadcrumb list.
2655 this.init();
2656 }
2657
2658 Crumbs.DEFAULTS_ = {
2659 container: '.dac-header-crumbs',
2660 crumbItem: $('<li class="dac-header-crumbs-item">'),
2661 linkClass: 'dac-header-crumbs-link'
2662 };
2663
2664 Crumbs.prototype.init = function() {
2665 Crumbs.buildCrumbForLink(this.selected.clone()).appendTo(this.el);
2666
2667 if (this.selectedParent.length) {
2668 Crumbs.buildCrumbForLink(this.selectedParent.clone()).prependTo(this.el);
2669 }
2670
2671 // Reveal the breadcrumbs
2672 this.el.addClass('dac-has-content');
2673 };
2674
2675 /**
2676 * Build a HTML structure for a breadcrumb.
2677 * @param {string} link
2678 * @return {jQuery}
2679 */
2680 Crumbs.buildCrumbForLink = function(link) {
2681 link.find('br').replaceWith(' ');
2682
2683 var crumbLink = $('<a>')
2684 .attr('class', Crumbs.DEFAULTS_.linkClass)
2685 .attr('href', link.attr('href'))
2686 .text(link.text());
2687
2688 return Crumbs.DEFAULTS_.crumbItem.clone().append(crumbLink);
2689 };
2690
2691 /**
2692 * jQuery plugin
2693 */
2694 $.fn.dacCrumbs = function(options) {
2695 return this.each(function() {
2696 new Crumbs(this, options);
2697 });
2698 };
2699})(jQuery);
2700
2701(function($) {
2702 'use strict';
2703
2704 /**
2705 * @param {HTMLElement} el - The DOM element.
2706 * @param {Object} options
2707 * @constructor
2708 */
2709 function SearchInput(el, options) {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002710 this.el = $(el);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08002711 this.options = $.extend({}, SearchInput.DEFAULTS_, options);
2712 this.body = $('body');
2713 this.input = this.el.find('input');
2714 this.close = this.el.find(this.options.closeButton);
2715 this.clear = this.el.find(this.options.clearButton);
2716 this.icon = this.el.find('.' + this.options.iconClass);
2717 this.init();
2718 }
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002719
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08002720 SearchInput.DEFAULTS_ = {
2721 activeClass: 'dac-active',
2722 activeIconClass: 'dac-search',
2723 closeButton: '[data-search-close]',
2724 clearButton: '[data-search-clear]',
2725 hiddenClass: 'dac-hidden',
2726 iconClass: 'dac-header-search-icon',
2727 searchModeClass: 'dac-search-mode',
2728 transitionDuration: 250
2729 };
2730
2731 SearchInput.prototype.init = function() {
2732 this.input.on('focus.dac-search', this.setActiveState.bind(this))
2733 .on('input.dac-search', this.checkInputValue.bind(this));
2734 this.close.on('click.dac-search', this.unsetActiveStateHandler_.bind(this));
2735 this.clear.on('click.dac-search', this.clearInput.bind(this));
2736 };
2737
2738 SearchInput.prototype.setActiveState = function() {
2739 var that = this;
2740
2741 this.clear.addClass(this.options.hiddenClass);
2742 this.body.addClass(this.options.searchModeClass);
2743 this.checkInputValue();
2744
2745 // Set icon to black after background has faded to white.
2746 setTimeout(function() {
2747 that.icon.addClass(that.options.activeIconClass);
2748 }, this.options.transitionDuration);
2749 };
2750
2751 SearchInput.prototype.unsetActiveStateHandler_ = function(event) {
2752 event.preventDefault();
2753 this.unsetActiveState();
2754 };
2755
2756 SearchInput.prototype.unsetActiveState = function() {
2757 this.icon.removeClass(this.options.activeIconClass);
2758 this.clear.addClass(this.options.hiddenClass);
2759 this.body.removeClass(this.options.searchModeClass);
2760 };
2761
2762 SearchInput.prototype.clearInput = function(event) {
2763 event.preventDefault();
2764 this.input.val('');
2765 this.clear.addClass(this.options.hiddenClass);
2766 };
2767
2768 SearchInput.prototype.checkInputValue = function() {
2769 if (this.input.val().length) {
2770 this.clear.removeClass(this.options.hiddenClass);
2771 } else {
2772 this.clear.addClass(this.options.hiddenClass);
2773 }
2774 };
2775
2776 /**
2777 * jQuery plugin
2778 * @param {object} options - Override default options.
2779 */
2780 $.fn.dacSearchInput = function() {
2781 return this.each(function() {
2782 var el = $(this);
2783 el.data('search-input.dac', new SearchInput(el, el.data()));
2784 });
2785 };
2786
2787 /**
2788 * Data Attribute API
2789 */
2790 $(function() {
2791 $('[data-search]').dacSearchInput();
2792 });
2793})(jQuery);
2794
2795/* global METADATA */
2796(function($) {
2797 function DacCarouselQuery(el) {
2798 el = $(el);
2799
2800 var opts = el.data();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002801 opts.maxResults = parseInt(opts.maxResults || '100', 10);
2802 opts.query = opts.carouselQuery;
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08002803 var resources = window.metadata.query(opts);
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002804
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08002805 el.empty();
2806 $(resources).each(function() {
2807 var resource = $.extend({}, this, METADATA.carousel[this.url]);
2808 el.dacHero(resource);
2809 });
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002810
2811 // Pagination element.
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08002812 el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002813
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08002814 el.dacCarousel();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08002815 }
2816
2817 // jQuery plugin
2818 $.fn.dacCarouselQuery = function() {
2819 return this.each(function() {
2820 var el = $(this);
2821 var data = el.data('dac.carouselQuery');
2822
2823 if (!data) { el.data('dac.carouselQuery', (data = new DacCarouselQuery(el))); }
2824 });
2825 };
2826
2827 // Data API
2828 $(function() {
2829 $('[data-carousel-query]').dacCarouselQuery();
2830 });
2831})(jQuery);
2832
2833(function($) {
2834 /**
2835 * A CSS based carousel, inspired by SequenceJS.
2836 * @param {jQuery} el
2837 * @param {object} options
2838 * @constructor
2839 */
2840 function DacCarousel(el, options) {
2841 this.el = $(el);
2842 this.options = options = $.extend({}, DacCarousel.OPTIONS, this.el.data(), options || {});
2843 this.frames = this.el.find(options.frameSelector);
2844 this.count = this.frames.size();
2845 this.current = options.start;
2846
2847 this.initPagination();
2848 this.initEvents();
2849 this.initFrame();
2850 }
2851
2852 DacCarousel.OPTIONS = {
2853 auto: true,
2854 autoTime: 10000,
2855 autoMinTime: 5000,
2856 btnPrev: '[data-carousel-prev]',
2857 btnNext: '[data-carousel-next]',
2858 frameSelector: 'article',
2859 loop: true,
2860 start: 0,
2861 swipeThreshold: 160,
2862 pagination: '[data-carousel-pagination]'
2863 };
2864
2865 DacCarousel.prototype.initPagination = function() {
2866 this.pagination = $([]);
2867 if (!this.options.pagination) { return; }
2868
2869 var pagination = $('<ul class="dac-pagination">');
2870 var parent = this.el;
2871 if (typeof this.options.pagination === 'string') { parent = this.el.find(this.options.pagination); }
2872
2873 if (this.count > 1) {
2874 for (var i = 0; i < this.count; i++) {
2875 var li = $('<li class="dac-pagination-item">').text(i);
2876 if (i === this.options.start) { li.addClass('active'); }
2877 li.click(this.go.bind(this, i));
2878
2879 pagination.append(li);
2880 }
2881 this.pagination = pagination.children();
2882 parent.append(pagination);
2883 }
2884 };
2885
2886 DacCarousel.prototype.initEvents = function() {
2887 var that = this;
2888
2889 this.touch = {
2890 start: {x: 0, y: 0},
2891 end: {x: 0, y: 0}
2892 };
2893
2894 this.el.on('touchstart', this.touchstart_.bind(this));
2895 this.el.on('touchend', this.touchend_.bind(this));
2896 this.el.on('touchmove', this.touchmove_.bind(this));
2897
2898 this.el.hover(function() {
2899 that.pauseRotateTimer();
2900 }, function() {
2901 that.startRotateTimer();
2902 });
2903
2904 $(this.options.btnPrev).click(function(e) {
2905 e.preventDefault();
2906 that.prev();
2907 });
2908
2909 $(this.options.btnNext).click(function(e) {
2910 e.preventDefault();
2911 that.next();
2912 });
2913 };
2914
2915 DacCarousel.prototype.touchstart_ = function(event) {
2916 var t = event.originalEvent.touches[0];
2917 this.touch.start = {x: t.screenX, y: t.screenY};
2918 };
2919
2920 DacCarousel.prototype.touchend_ = function() {
2921 var deltaX = this.touch.end.x - this.touch.start.x;
2922 var deltaY = Math.abs(this.touch.end.y - this.touch.start.y);
2923 var shouldSwipe = (deltaY < Math.abs(deltaX)) && (Math.abs(deltaX) >= this.options.swipeThreshold);
2924
2925 if (shouldSwipe) {
2926 if (deltaX > 0) {
2927 this.prev();
2928 } else {
2929 this.next();
2930 }
2931 }
2932 };
2933
2934 DacCarousel.prototype.touchmove_ = function(event) {
2935 var t = event.originalEvent.touches[0];
2936 this.touch.end = {x: t.screenX, y: t.screenY};
2937 };
2938
2939 DacCarousel.prototype.initFrame = function() {
2940 this.frames.removeClass('active').eq(this.options.start).addClass('active');
2941 };
2942
2943 DacCarousel.prototype.startRotateTimer = function() {
2944 if (!this.options.auto || this.rotateTimer) { return; }
2945 this.rotateTimer = setTimeout(this.next.bind(this), this.options.autoTime);
2946 };
2947
2948 DacCarousel.prototype.pauseRotateTimer = function() {
2949 clearTimeout(this.rotateTimer);
2950 this.rotateTimer = null;
2951 };
2952
2953 DacCarousel.prototype.prev = function() {
2954 this.go(this.current - 1);
2955 };
2956
2957 DacCarousel.prototype.next = function() {
2958 this.go(this.current + 1);
2959 };
2960
2961 DacCarousel.prototype.go = function(next) {
2962 // Figure out what the next slide is.
2963 while (this.count > 0 && next >= this.count) { next -= this.count; }
2964 while (next < 0) { next += this.count; }
2965
2966 // Cancel if we're already on that slide.
2967 if (next === this.current) { return; }
2968
2969 // Prepare next slide.
2970 this.frames.eq(next).removeClass('out');
2971
2972 // Recalculate styles before starting slide transition.
2973 this.el.resolveStyles();
2974 // Update pagination
2975 this.pagination.removeClass('active').eq(next).addClass('active');
2976
2977 // Transition out current frame
2978 this.frames.eq(this.current).toggleClass('active out');
2979
2980 // Transition in a new frame
2981 this.frames.eq(next).toggleClass('active');
2982
2983 this.current = next;
2984 };
2985
2986 // Helper which resolves new styles for an element, so it can start transitioning
2987 // from the new values.
2988 $.fn.resolveStyles = function() {
2989 /*jshint expr:true*/
2990 this[0] && this[0].offsetTop;
2991 return this;
2992 };
2993
2994 // jQuery plugin
2995 $.fn.dacCarousel = function() {
2996 this.each(function() {
2997 var $el = $(this);
2998 $el.data('dac-carousel', new DacCarousel(this));
2999 });
3000 return this;
3001 };
3002
3003 // Data API
3004 $(function() {
3005 $('[data-carousel]').dacCarousel();
3006 });
3007})(jQuery);
3008
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003009/* global toRoot */
3010
3011(function($) {
3012 // Ordering matters
3013 var TAG_MAP = [
3014 {from: 'developerstory', to: 'Android Developer Story'},
3015 {from: 'googleplay', to: 'Google Play'}
3016 ];
3017
3018 function DacHero(el, resource, isSearch) {
3019 var slide = $('<article>');
3020 slide.addClass(isSearch ? 'dac-search-hero' : 'dac-expand dac-hero');
3021 var image = cleanUrl(resource.heroImage || resource.image);
3022 var fullBleed = image && !resource.heroColor;
3023
3024 if (!isSearch) {
3025 // Configure background
3026 slide.css({
3027 backgroundImage: fullBleed ? 'url(' + image + ')' : '',
3028 backgroundColor: resource.heroColor || ''
3029 });
3030
3031 // Should copy be inverted
3032 slide.toggleClass('dac-invert', resource.heroInvert || fullBleed);
3033 slide.toggleClass('dac-darken', fullBleed);
3034
3035 // Should be clickable
3036 slide.append($('<a class="dac-hero-carousel-action">').attr('href', cleanUrl(resource.url)));
3037 }
3038
3039 var cols = $('<div class="cols dac-hero-content">');
3040
3041 // inline image column
3042 var rightCol = $('<div class="col-1of2 col-push-1of2 dac-hero-figure">')
3043 .appendTo(cols);
3044
3045 if ((!fullBleed || isSearch) && image) {
3046 rightCol.append($('<img>').attr('src', image));
3047 }
3048
3049 // info column
3050 $('<div class="col-1of2 col-pull-1of2">')
3051 .append($('<div class="dac-hero-tag">').text(formatTag(resource)))
3052 .append($('<h1 class="dac-hero-title">').text(formatTitle(resource)))
3053 .append($('<p class="dac-hero-description">').text(resource.summary))
3054 .append($('<a class="dac-hero-cta">')
3055 .text(formatCTA(resource))
3056 .attr('href', cleanUrl(resource.url))
3057 .prepend($('<span class="dac-sprite dac-auto-chevron">'))
3058 )
3059 .appendTo(cols);
3060
3061 slide.append(cols.wrap('<div class="wrap">').parent());
3062 el.append(slide);
3063 }
3064
3065 function cleanUrl(url) {
3066 if (url && url.indexOf('//') === -1) {
3067 url = toRoot + url;
3068 }
3069 return url;
3070 }
3071
3072 function formatTag(resource) {
3073 // Hmm, need a better more scalable solution for this.
3074 for (var i = 0, mapping; mapping = TAG_MAP[i]; i++) {
3075 if (resource.tags.indexOf(mapping.from) > -1) {
3076 return mapping.to;
3077 }
3078 }
3079 return resource.type;
3080 }
3081
3082 function formatTitle(resource) {
3083 return resource.title.replace(/android developer story: /i, '');
3084 }
3085
3086 function formatCTA(resource) {
3087 return resource.type === 'youtube' ? 'Watch the video' : 'Learn more';
3088 }
3089
3090 // jQuery plugin
3091 $.fn.dacHero = function(resource, isSearch) {
3092 return this.each(function() {
3093 var el = $(this);
3094 return new DacHero(el, resource, isSearch);
3095 });
3096 };
3097})(jQuery);
3098
3099(function($) {
3100 'use strict';
3101
3102 function highlightString(label, query) {
3103 query = query || '';
3104 //query = query.replace('<wbr>', '').replace('.', '\\.');
3105 var queryRE = new RegExp('(' + query + ')', 'ig');
3106 return label.replace(queryRE, '<em>$1</em>');
3107 }
3108
3109 $.fn.highlightMatches = function(query) {
3110 return this.each(function() {
3111 var el = $(this);
3112 var label = el.html();
3113 var highlighted = highlightString(label, query);
3114 el.html(highlighted);
3115 el.addClass('highlighted');
3116 });
3117 };
3118})(jQuery);
3119
3120/**
3121 * History tracking.
3122 * Track visited urls in localStorage.
3123 */
3124(function($) {
3125 var PAGES_TO_STORE_ = 100;
3126 var MIN_NUMBER_OF_PAGES_TO_DISPLAY_ = 6;
3127 var CONTAINER_SELECTOR_ = '.dac-search-results-history-wrap';
3128
3129 /**
3130 * Generate resource cards for visited pages.
3131 * @param {HTMLElement} el
3132 * @constructor
3133 */
3134 function HistoryQuery(el) {
3135 this.el = $(el);
3136
3137 // Only show history component if enough pages have been visited.
3138 if (getVisitedPages().length < MIN_NUMBER_OF_PAGES_TO_DISPLAY_) {
3139 this.el.closest(CONTAINER_SELECTOR_).addClass('dac-hidden');
3140 return;
3141 }
3142
3143 // Rename query
3144 this.el.data('query', this.el.data('history-query'));
3145
3146 // jQuery method to populate cards.
3147 this.el.resourceWidget();
3148 }
3149
3150 /**
3151 * Fetch from localStorage an array of visted pages
3152 * @returns {Array}
3153 */
3154 function getVisitedPages() {
3155 var visited = localStorage.getItem('visited-pages');
3156 return visited ? JSON.parse(visited) : [];
3157 }
3158
3159 /**
3160 * Return a page corresponding to cuurent pathname. If none exists, create one.
3161 * @param {Array} pages
3162 * @param {String} path
3163 * @returns {Object} Page
3164 */
3165 function getPageForPath(pages, path) {
3166 var page;
3167
3168 // Backwards lookup for current page, last pages most likely to be visited again.
3169 for (var i = pages.length - 1; i >= 0; i--) {
3170 if (pages[i].path === path) {
3171 page = pages[i];
3172
3173 // Remove page object from pages list to ensure correct ordering.
3174 pages.splice(i, 1);
3175
3176 return page;
3177 }
3178 }
3179
3180 // If storage limit is exceeded, remove last visited path.
3181 if (pages.length >= PAGES_TO_STORE_) {
3182 pages.shift();
3183 }
3184
3185 return {path: path};
3186 }
3187
3188 /**
3189 * Add current page to back of visited array, increase hit count by 1.
3190 */
3191 function addCurrectPage() {
3192 var path = location.pathname;
3193
3194 // Do not track frontpage visits.
3195 if (path === '/' || path === '/index.html') {return;}
3196
3197 var pages = getVisitedPages();
3198 var page = getPageForPath(pages, path);
3199
3200 // New page visits have no hit count.
3201 page.hit = ~~page.hit + 1;
3202
3203 // Most recently visted pages are located at the end of the visited array.
3204 pages.push(page);
3205
3206 localStorage.setItem('visited-pages', JSON.stringify(pages));
3207 }
3208
3209 /**
3210 * Hit count compare function.
3211 * @param {Object} a - page
3212 * @param {Object} b - page
3213 * @returns {number}
3214 */
3215 function byHit(a, b) {
3216 if (a.hit > b.hit) {
3217 return -1;
3218 } else if (a.hit < b.hit) {
3219 return 1;
3220 }
3221
3222 return 0;
3223 }
3224
3225 /**
3226 * Return a list of visited urls in a given order.
3227 * @param {String} order - (recent|most-visited)
3228 * @returns {Array}
3229 */
3230 $.dacGetVisitedUrls = function(order) {
3231 var pages = getVisitedPages();
3232
3233 if (order === 'recent') {
3234 pages.reverse();
3235 } else {
3236 pages.sort(byHit);
3237 }
3238
3239 return pages.map(function(page) {
3240 return page.path.replace(/^\//, '');
3241 });
3242 };
3243
3244 // jQuery plugin
3245 $.fn.dacHistoryQuery = function() {
3246 return this.each(function() {
3247 var el = $(this);
3248 var data = el.data('dac.recentlyVisited');
3249
3250 if (!data) {
3251 el.data('dac.recentlyVisited', (data = new HistoryQuery(el)));
3252 }
3253 });
3254 };
3255
3256 $(function() {
3257 $('[data-history-query]').dacHistoryQuery();
3258 // Do not block page rendering.
3259 setTimeout(addCurrectPage, 0);
3260 });
3261})(jQuery);
3262
3263/* ############################################ */
3264/* ########## LOCALIZATION ############ */
3265/* ############################################ */
3266/**
3267 * Global helpers.
3268 */
3269function getBaseUri(uri) {
3270 var intlUrl = (uri.substring(0, 6) === '/intl/');
3271 if (intlUrl) {
3272 var base = uri.substring(uri.indexOf('intl/') + 5, uri.length);
3273 base = base.substring(base.indexOf('/') + 1, base.length);
3274 return '/' + base;
3275 } else {
3276 return uri;
3277 }
3278}
3279
3280function changeLangPref(targetLang, submit) {
3281 window.writeCookie('pref_lang', targetLang, null);
3282//DD
3283 $('#language').find('option[value="' + targetLang + '"]').attr('selected', true);
3284 // ####### TODO: Remove this condition once we're stable on devsite #######
3285 // This condition is only needed if we still need to support legacy GAE server
3286 if (window.devsite) {
3287 // Switch language when on Devsite server
3288 if (submit) {
3289 $('#setlang').submit();
3290 }
3291 } else {
3292 // Switch language when on legacy GAE server
3293 if (submit) {
3294 window.location = getBaseUri(location.pathname);
3295 }
3296 }
3297}
3298// Redundant usage to appease jshint.
3299window.changeLangPref = changeLangPref;
3300
3301(function() {
3302 /**
3303 * Whitelisted locales. Should match choices in language dropdown. Repeated here
3304 * as a lot of i18n logic happens before page load and dropdown is ready.
3305 */
3306 var LANGUAGES = [
3307 'en',
3308 'es',
3309 'in',
3310 'ja',
3311 'ko',
3312 'pt-br',
3313 'ru',
3314 'vi',
3315 'zh-cn',
3316 'zh-tw'
3317 ];
3318
3319 /**
3320 * Master list of translated strings for template files.
3321 */
3322 var PHRASES = {
3323 'newsletter': {
3324 'title': 'Get the latest Android developer news and tips that will help you find success on Google Play.',
3325 'requiredHint': '* Required Fields',
3326 'name': 'Full name',
3327 'email': 'Email address',
3328 'company': 'Company / developer name',
3329 'appUrl': 'One of your Play Store app URLs',
3330 'business': {
3331 'label': 'Which best describes your business:',
3332 'apps': 'Apps',
3333 'games': 'Games',
3334 'both': 'Apps & Games'
3335 },
3336 'confirmMailingList': 'Add me to the mailing list for the monthly newsletter and occasional emails about ' +
3337 'development and Google Play opportunities.',
3338 'privacyPolicy': 'I acknowledge that the information provided in this form will be subject to Google\'s ' +
3339 '<a href="https://www.google.com/policies/privacy/" target="_blank">privacy policy</a>.',
3340 'languageVal': 'English',
3341 'successTitle': 'Hooray!',
3342 'successDetails': 'You have successfully signed up for the latest Android developer news and tips.',
3343 'languageValTarget': {
3344 'en': 'English',
3345 'ar': 'Arabic (العربيّة)',
3346 'in': 'Indonesian (Bahasa)',
3347 'fr': 'French (français)',
3348 'de': 'German (Deutsch)',
3349 'ja': 'Japanese (日本語)',
3350 'ko': 'Korean (한국어)',
3351 'ru': 'Russian (Русский)',
3352 'es': 'Spanish (español)',
3353 'th': 'Thai (ภาษาไทย)',
3354 'tr': 'Turkish (Türkçe)',
3355 'vi': 'Vietnamese (tiếng Việt)',
3356 'pt-br': 'Brazilian Portuguese (Português Brasileiro)',
3357 'zh-cn': 'Simplified Chinese (简体中文)',
3358 'zh-tw': 'Traditional Chinese (繁體中文)',
3359 },
3360 'resetLangTitle': "Browse this site in %{targetLang}?",
3361 'resetLangTextIntro': 'You requested a page in %{targetLang}, but your language preference for this site is %{lang}.',
3362 'resetLangTextCta': 'Would you like to change your language preference and browse this site in %{targetLang}? ' +
3363 'If you want to change your language preference later, use the language menu at the bottom of each page.',
3364 'resetLangButtonYes': 'Change Language',
3365 'resetLangButtonNo': 'Not Now'
3366 }
3367 };
3368
3369 /**
3370 * Current locale.
3371 */
3372 var locale = (function() {
smain@google.com1c8d4a42016-02-26 16:30:50 -08003373 var lang = window.readCookie('pref_lang');
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003374 if (lang === 0 || LANGUAGES.indexOf(lang) === -1) {
3375 lang = 'en';
3376 }
3377 return lang;
3378 })();
3379 var localeTarget = (function() {
3380 var localeTarget = locale;
3381 if (location.pathname.substring(0,6) == "/intl/") {
3382 var target = location.pathname.split('/')[2];
3383 if (!(target === 0) || (LANGUAGES.indexOf(target) === -1)) {
3384 localeTarget = target;
3385 }
3386 }
3387 return localeTarget;
3388 })();
3389
3390 /**
3391 * Global function shims for backwards compatibility
3392 */
3393 window.changeNavLang = function() {
3394 // Already done.
3395 };
3396
3397 window.loadLangPref = function() {
3398 // Languages pref already loaded.
3399 };
3400
3401 window.getLangPref = function() {
3402 return locale;
3403 };
3404
3405 window.getLangTarget = function() {
3406 return localeTarget;
3407 };
3408
3409 // Expose polyglot instance for advanced localization.
3410 var polyglot = window.polyglot = new window.Polyglot({
3411 locale: locale,
3412 phrases: PHRASES
3413 });
3414
3415 // When DOM is ready.
3416 $(function() {
3417 // Mark current locale in language picker.
3418 $('#language').find('option[value="' + locale + '"]').attr('selected', true);
3419
3420 $('html').dacTranslate().on('dac:domchange', function(e) {
3421 $(e.target).dacTranslate();
3422 });
3423 });
3424
3425 $.fn.dacTranslate = function() {
3426 // Translate strings in template markup:
3427
3428 // OLD
3429 // Having all translations in HTML does not scale well and bloats every page.
3430 // Need to migrate this to data-l JS translations below.
3431 if (locale !== 'en') {
3432 var $links = this.find('a[' + locale + '-lang]');
3433 $links.each(function() { // for each link with a translation
3434 var $link = $(this);
3435 // put the desired language from the attribute as the text
3436 $link.text($link.attr(locale + '-lang'));
3437 });
3438 }
3439
3440 // NEW
3441 // A simple declarative api for JS translations. Feel free to extend as appropriate.
3442
3443 // Miscellaneous string compilations
3444 // Build full strings from localized substrings:
3445 var myLocaleTarget = window.getLangTarget();
3446 var myTargetLang = window.polyglot.t("newsletter.languageValTarget." + myLocaleTarget);
3447 var myLang = window.polyglot.t("newsletter.languageVal");
3448 var myTargetLangTitleString = window.polyglot.t("newsletter.resetLangTitle", {targetLang: myTargetLang});
3449 var myResetLangTextIntro = window.polyglot.t("newsletter.resetLangTextIntro", {targetLang: myTargetLang, lang: myLang});
3450 var myResetLangTextCta = window.polyglot.t("newsletter.resetLangTextCta", {targetLang: myTargetLang});
3451 //var myResetLangButtonYes = window.polyglot.t("newsletter.resetLangButtonYes", {targetLang: myTargetLang});
3452
3453 // Inject strings as text values in dialog components:
3454 $("#langform .dac-modal-header-title").text(myTargetLangTitleString);
3455 $("#langform #resetLangText").text(myResetLangTextIntro);
3456 $("#langform #resetLangCta").text(myResetLangTextCta);
3457 //$("#resetLangButtonYes").attr("data-t", window.polyglot.t(myResetLangButtonYes));
3458
3459 // Text: <div data-t="nav.home"></div>
3460 // HTML: <div data-t="privacy" data-t-html></html>
3461 this.find('[data-t]').each(function() {
3462 var el = $(this);
3463 var data = el.data();
3464 if (data.t) {
3465 el[data.tHtml === '' ? 'html' : 'text'](polyglot.t(data.t));
3466 }
3467 });
3468
3469 return this;
3470 };
3471})();
3472/* ########## END LOCALIZATION ############ */
3473
3474// Translations. These should eventually be moved into language-specific files and loaded on demand.
3475// jshint nonbsp:false
3476switch (window.getLangPref()) {
3477 case 'ar':
3478 window.polyglot.extend({
3479 'newsletter': {
3480 'title': 'Google Play. يمكنك الحصول على آخر الأخبار والنصائح من مطوّري تطبيقات Android، مما يساعدك ' +
3481 'على تحقيق النجاح على',
3482 'requiredHint': '* حقول مطلوبة',
3483 'name': '. الاسم بالكامل ',
3484 'email': '. عنوان البريد الإلكتروني ',
3485 'company': '. اسم الشركة / اسم مطوّر البرامج',
3486 'appUrl': '. أحد عناوين URL لتطبيقاتك في متجر Play',
3487 'business': {
3488 'label': '. ما العنصر الذي يوضح طبيعة نشاطك التجاري بدقة؟ ',
3489 'apps': 'التطبيقات',
3490 'games': 'الألعاب',
3491 'both': 'التطبيقات والألعاب'
3492 },
3493 'confirmMailingList': 'إضافتي إلى القائمة البريدية للنشرة الإخبارية الشهرية والرسائل الإلكترونية التي يتم' +
3494 ' إرسالها من حين لآخر بشأن التطوير وفرص Google Play.',
3495 'privacyPolicy': 'أقر بأن المعلومات المقدَّمة في هذا النموذج تخضع لسياسة خصوصية ' +
3496 '<a href="https://www.google.com/intl/ar/policies/privacy/" target="_blank">Google</a>.',
3497 'languageVal': 'Arabic (العربيّة)',
3498 'successTitle': 'رائع!',
3499 'successDetails': 'لقد اشتركت بنجاح للحصول على آخر الأخبار والنصائح من مطوّري برامج Android.'
3500 }
3501 });
3502 break;
3503 case 'zh-cn':
3504 window.polyglot.extend({
3505 'newsletter': {
3506 'title': '获取最新的 Android 开发者资讯和提示,助您在 Google Play 上取得成功。',
3507 'requiredHint': '* 必填字段',
3508 'name': '全名',
3509 'email': '电子邮件地址',
3510 'company': '公司/开发者名称',
3511 'appUrl': '您的某个 Play 商店应用网址',
3512 'business': {
3513 'label': '哪一项能够最准确地描述您的业务?',
3514 'apps': '应用',
3515 'games': '游戏',
3516 'both': '应用和游戏'
3517 },
3518 'confirmMailingList': '将我添加到邮寄名单,以便接收每月简报以及不定期发送的关于开发和 Google Play 商机的电子邮件。',
3519 'privacyPolicy': '我确认自己了解在此表单中提供的信息受 <a href="https://www.google.com/intl/zh-CN/' +
3520 'policies/privacy/" target="_blank">Google</a> 隐私权政策的约束。',
3521 'languageVal': 'Simplified Chinese (简体中文)',
3522 'successTitle': '太棒了!',
3523 'successDetails': '您已成功订阅最新的 Android 开发者资讯和提示。'
3524 }
3525 });
3526 break;
3527 case 'zh-tw':
3528 window.polyglot.extend({
3529 'newsletter': {
3530 'title': '獲得 Android 開發人員的最新消息和各項秘訣,讓您在 Google Play 上輕鬆邁向成功之路。',
3531 'requiredHint': '* 必要欄位',
3532 'name': '全名',
3533 'email': '電子郵件地址',
3534 'company': '公司/開發人員名稱',
3535 'appUrl': '您其中一個 Play 商店應用程式的網址',
3536 'business': {
3537 'label': '為您的商家選取最合適的產品類別。',
3538 'apps': '應用程式',
3539 'games': '遊戲',
3540 'both': '應用程式和遊戲'
3541 },
3542 'confirmMailingList': '我想加入 Google Play 的郵寄清單,以便接收每月電子報和 Google Play 不定期寄送的電子郵件,' +
3543 '瞭解關於開發和 Google Play 商機的資訊。',
3544 'privacyPolicy': '我瞭解,我在這張表單中提供的資訊將受到 <a href="' +
3545 'https://www.google.com/intl/zh-TW/policies/privacy/" target="_blank">Google</a> 隱私權政策.',
3546 'languageVal': 'Traditional Chinese (繁體中文)',
3547 'successTitle': '太棒了!',
3548 'successDetails': '您已經成功訂閱 Android 開發人員的最新消息和各項秘訣。'
3549 }
3550 });
3551 break;
3552 case 'fr':
3553 window.polyglot.extend({
3554 'newsletter': {
3555 'title': 'Recevez les dernières actualités destinées aux développeurs Android, ainsi que des conseils qui ' +
3556 'vous mèneront vers le succès sur Google Play.',
3557 'requiredHint': '* Champs obligatoires',
3558 'name': 'Nom complet',
3559 'email': 'Adresse e-mail',
3560 'company': 'Nom de la société ou du développeur',
3561 'appUrl': 'Une de vos URL Play Store',
3562 'business': {
3563 'label': 'Quelle option décrit le mieux votre activité ?',
3564 'apps': 'Applications',
3565 'games': 'Jeux',
3566 'both': 'Applications et jeux'
3567 },
3568 'confirmMailingList': 'Ajoutez-moi à la liste de diffusion de la newsletter mensuelle et tenez-moi informé ' +
3569 'par des e-mails occasionnels de l\'évolution et des opportunités de Google Play.',
3570 'privacyPolicy': 'Je comprends que les renseignements fournis dans ce formulaire seront soumis aux <a href="' +
3571 'https://www.google.com/intl/fr/policies/privacy/" target="_blank">règles de confidentialité</a> de Google.',
3572 'languageVal': 'French (français)',
3573 'successTitle': 'Super !',
3574 'successDetails': 'Vous êtes bien inscrit pour recevoir les actualités et les conseils destinés aux ' +
3575 'développeurs Android.'
3576 }
3577 });
3578 break;
3579 case 'de':
3580 window.polyglot.extend({
3581 'newsletter': {
3582 'title': 'Abonniere aktuelle Informationen und Tipps für Android-Entwickler und werde noch erfolgreicher ' +
3583 'bei Google Play.',
3584 'requiredHint': '* Pflichtfelder',
3585 'name': 'Vollständiger Name',
3586 'email': 'E-Mail-Adresse',
3587 'company': 'Unternehmens-/Entwicklername',
3588 'appUrl': 'Eine der URLs deiner Play Store App',
3589 'business': {
3590 'label': 'Welche der folgenden Kategorien beschreibt dein Unternehmen am besten?',
3591 'apps': 'Apps',
3592 'games': 'Spiele',
3593 'both': 'Apps und Spiele'
3594 },
3595 'confirmMailingList': 'Meine E-Mail-Adresse soll zur Mailingliste hinzugefügt werden, damit ich den ' +
3596 'monatlichen Newsletter sowie gelegentlich E-Mails zu Entwicklungen und Optionen bei Google Play erhalte.',
3597 'privacyPolicy': 'Ich bestätige, dass die in diesem Formular bereitgestellten Informationen gemäß der ' +
3598 '<a href="https://www.google.com/intl/de/policies/privacy/" target="_blank">Datenschutzerklärung</a> von ' +
3599 'Google verwendet werden dürfen.',
3600 'languageVal': 'German (Deutsch)',
3601 'successTitle': 'Super!',
3602 'successDetails': 'Du hast dich erfolgreich angemeldet und erhältst jetzt aktuelle Informationen und Tipps ' +
3603 'für Android-Entwickler.'
3604 }
3605 });
3606 break;
3607 case 'in':
3608 window.polyglot.extend({
3609 'newsletter': {
3610 'title': 'Receba as dicas e as notícias mais recentes para os desenvolvedores Android e seja bem-sucedido ' +
3611 'no Google Play.',
3612 'requiredHint': '* Bidang Wajib Diisi',
3613 'name': 'Nama lengkap',
3614 'email': 'Alamat email',
3615 'company': 'Nama pengembang / perusahaan',
3616 'appUrl': 'Salah satu URL aplikasi Play Store Anda',
3617 'business': {
3618 'label': 'Dari berikut ini, mana yang paling cocok dengan bisnis Anda?',
3619 'apps': 'Aplikasi',
3620 'games': 'Game',
3621 'both': 'Aplikasi dan Game'
3622 },
3623 'confirmMailingList': 'Tambahkan saya ke milis untuk mendapatkan buletin bulanan dan email sesekali mengenai ' +
3624 'perkembangan dan kesempatan yang ada di Google Play.',
3625 'privacyPolicy': 'Saya memahami bahwa informasi yang diberikan dalam formulir ini tunduk pada <a href="' +
3626 'https://www.google.com/intl/in/policies/privacy/" target="_blank">kebijakan privasi</a> Google.',
3627 'languageVal': 'Indonesian (Bahasa)',
3628 'successTitle': 'Hore!',
3629 'successDetails': 'Anda berhasil mendaftar untuk kiat dan berita pengembang Android terbaru.'
3630 }
3631 });
3632 break;
3633 case 'it':
3634 //window.polyglot.extend({
3635 // 'newsletter': {
3636 // 'title': 'Receba as dicas e as notícias mais recentes para os desenvolvedores Android e seja bem-sucedido ' +
3637 // 'no Google Play.',
3638 // 'requiredHint': '* Campos obrigatórios',
3639 // 'name': 'Nome completo',
3640 // 'email': 'Endereço de Email',
3641 // 'company': 'Nome da empresa / do desenvolvedor',
3642 // 'appUrl': 'URL de um dos seus apps da Play Store',
3643 // 'business': {
3644 // 'label': 'Qual das seguintes opções melhor descreve sua empresa?',
3645 // 'apps': 'Apps',
3646 // 'games': 'Jogos',
3647 // 'both': 'Apps e Jogos'
3648 // },
3649 // 'confirmMailingList': 'Inscreva-me na lista de e-mails para que eu receba o boletim informativo mensal, ' +
3650 // 'bem como e-mails ocasionais sobre o desenvolvimento e as oportunidades do Google Play.',
3651 // 'privacyPolicy': 'Reconheço que as informações fornecidas neste formulário estão sujeitas à <a href="' +
3652 // 'https://www.google.com.br/policies/privacy/" target="_blank">Política de Privacidade</a> do Google.',
3653 // 'languageVal': 'Italian (italiano)',
3654 // 'successTitle': 'Uhu!',
3655 // 'successDetails': 'Você se inscreveu para receber as notícias e as dicas mais recentes para os ' +
3656 // 'desenvolvedores Android.',
3657 // }
3658 //});
3659 break;
3660 case 'ja':
3661 window.polyglot.extend({
3662 'newsletter': {
3663 'title': 'Google Play での成功に役立つ Android デベロッパー向けの最新ニュースやおすすめの情報をお届けします。',
3664 'requiredHint': '* 必須',
3665 'name': '氏名',
3666 'email': 'メールアドレス',
3667 'company': '会社名 / デベロッパー名',
3668 'appUrl': 'Play ストア アプリの URL(いずれか 1 つ)',
3669 'business': {
3670 'label': 'お客様のビジネスに最もよく当てはまるものをお選びください。',
3671 'apps': 'アプリ',
3672 'games': 'ゲーム',
3673 'both': 'アプリとゲーム'
3674 },
3675 'confirmMailingList': '開発や Google Play の最新情報に関する毎月発行のニュースレターや不定期発行のメールを受け取る',
3676 'privacyPolicy': 'このフォームに入力した情報に <a href="https://www.google.com/intl/ja/policies/privacy/" ' +
3677 'target="_blank">Google</a> のプライバシー ポリシーが適用',
3678 'languageVal': 'Japanese (日本語)',
3679 'successTitle': '完了です!',
3680 'successDetails': 'Android デベロッパー向けの最新ニュースやおすすめの情報の配信登録が完了しました。'
3681 }
3682 });
3683 break;
3684 case 'ko':
3685 window.polyglot.extend({
3686 'newsletter': {
3687 'title': 'Google Play에서 성공을 거두는 데 도움이 되는 최신 Android 개발자 소식 및 도움말을 받아 보세요.',
3688 'requiredHint': '* 필수 입력란',
3689 'name': '이름',
3690 'email': '이메일 주소',
3691 'company': '회사/개발자 이름',
3692 'appUrl': 'Play 스토어 앱 URL 중 1개',
3693 'business': {
3694 'label': '다음 중 내 비즈니스를 가장 잘 설명하는 단어는 무엇인가요?',
3695 'apps': '앱',
3696 'games': '게임',
3697 'both': '앱 및 게임'
3698 },
3699 'confirmMailingList': '개발 및 Google Play 관련 소식에 관한 월별 뉴스레터 및 비정기 이메일을 받아보겠습니다.',
3700 'privacyPolicy': '이 양식에 제공한 정보는 <a href="https://www.google.com/intl/ko/policies/privacy/" ' +
3701 'target="_blank">Google의</a> 개인정보취급방침에 따라 사용됨을',
3702 'languageVal':'Korean (한국어)',
3703 'successTitle': '축하합니다!',
3704 'successDetails': '최신 Android 개발자 뉴스 및 도움말을 받아볼 수 있도록 가입을 완료했습니다.'
3705 }
3706 });
3707 break;
3708 case 'pt-br':
3709 window.polyglot.extend({
3710 'newsletter': {
3711 'title': 'Receba as dicas e as notícias mais recentes para os desenvolvedores Android e seja bem-sucedido ' +
3712 'no Google Play.',
3713 'requiredHint': '* Campos obrigatórios',
3714 'name': 'Nome completo',
3715 'email': 'Endereço de Email',
3716 'company': 'Nome da empresa / do desenvolvedor',
3717 'appUrl': 'URL de um dos seus apps da Play Store',
3718 'business': {
3719 'label': 'Qual das seguintes opções melhor descreve sua empresa?',
3720 'apps': 'Apps',
3721 'games': 'Jogos',
3722 'both': 'Apps e Jogos'
3723 },
3724 'confirmMailingList': 'Inscreva-me na lista de e-mails para que eu receba o boletim informativo mensal, ' +
3725 'bem como e-mails ocasionais sobre o desenvolvimento e as oportunidades do Google Play.',
3726 'privacyPolicy': 'Reconheço que as informações fornecidas neste formulário estão sujeitas à <a href="' +
3727 'https://www.google.com.br/policies/privacy/" target="_blank">Política de Privacidade</a> do Google.',
3728 'languageVal': 'Brazilian Portuguese (Português Brasileiro)',
3729 'successTitle': 'Uhu!',
3730 'successDetails': 'Você se inscreveu para receber as notícias e as dicas mais recentes para os ' +
3731 'desenvolvedores Android.'
3732 }
3733 });
3734 break;
3735 case 'ru':
3736 window.polyglot.extend({
3737 'newsletter': {
3738 'title': 'Хотите получать последние новости и советы для разработчиков Google Play? Заполните эту форму.',
3739 'requiredHint': '* Обязательные поля',
3740 'name': 'Полное имя',
3741 'email': 'Адрес электронной почты',
3742 'company': 'Название компании или имя разработчика',
3743 'appUrl': 'Ссылка на любое ваше приложение в Google Play',
3744 'business': {
3745 'label': 'Что вы создаете?',
3746 'apps': 'Приложения',
3747 'games': 'Игры',
3748 'both': 'Игры и приложения'
3749 },
3750 'confirmMailingList': 'Я хочу получать ежемесячную рассылку для разработчиков и другие полезные новости ' +
3751 'Google Play.',
3752 'privacyPolicy': 'Я предоставляю эти данные в соответствии с <a href="' +
3753 'https://www.google.com/intl/ru/policies/privacy/" target="_blank">Политикой конфиденциальности</a> Google.',
3754 'languageVal': 'Russian (Русский)',
3755 'successTitle': 'Поздравляем!',
3756 'successDetails': 'Теперь вы подписаны на последние новости и советы для разработчиков Android.'
3757 }
3758 });
3759 break;
3760 case 'es':
3761 window.polyglot.extend({
3762 'newsletter': {
3763 'title': 'Recibe las últimas noticias y sugerencias para programadores de Android y logra tener éxito en ' +
3764 'Google Play.',
3765 'requiredHint': '* Campos obligatorios',
3766 'name': 'Dirección de correo electrónico',
3767 'email': 'Endereço de Email',
3768 'company': 'Nombre de la empresa o del programador',
3769 'appUrl': 'URL de una de tus aplicaciones de Play Store',
3770 'business': {
3771 'label': '¿Qué describe mejor a tu empresa?',
3772 'apps': 'Aplicaciones',
3773 'games': 'Juegos',
3774 'both': 'Juegos y aplicaciones'
3775 },
3776 'confirmMailingList': 'Deseo unirme a la lista de distribución para recibir el boletín informativo mensual ' +
3777 'y correos electrónicos ocasionales sobre desarrollo y oportunidades de Google Play.',
3778 'privacyPolicy': 'Acepto que la información que proporcioné en este formulario cumple con la <a href="' +
3779 'https://www.google.com/intl/es/policies/privacy/" target="_blank">política de privacidad</a> de Google.',
3780 'languageVal': 'Spanish (español)',
3781 'successTitle': '¡Felicitaciones!',
3782 'successDetails': 'El registro para recibir las últimas noticias y sugerencias para programadores de Android ' +
3783 'se realizó correctamente.'
3784 }
3785 });
3786 break;
3787 case 'th':
3788 window.polyglot.extend({
3789 'newsletter': {
3790 'title': 'รับข่าวสารล่าสุดสำหรับนักพัฒนาซอฟต์แวร์ Android ตลอดจนเคล็ดลับที่จะช่วยให้คุณประสบความสำเร็จบน ' +
3791 'Google Play',
3792 'requiredHint': '* ช่องที่ต้องกรอก',
3793 'name': 'ชื่อและนามสกุล',
3794 'email': 'ที่อยู่อีเมล',
3795 'company': 'ชื่อบริษัท/นักพัฒนาซอฟต์แวร์',
3796 'appUrl': 'URL แอปใดแอปหนึ่งของคุณใน Play สโตร์',
3797 'business': {
3798 'label': 'ข้อใดตรงกับธุรกิจของคุณมากที่สุด',
3799 'apps': 'แอป',
3800 'games': 'เกม',
3801 'both': 'แอปและเกม'
3802 },
3803 'confirmMailingList': 'เพิ่มฉันลงในรายชื่ออีเมลเพื่อรับจดหมายข่าวรายเดือนและอีเมลเป็นครั้งคราวเกี่ยวกับก' +
3804 'ารพัฒนาซอฟต์แวร์และโอกาสใน Google Play',
3805 'privacyPolicy': 'ฉันรับทราบว่าข้อมูลที่ให้ไว้ในแบบฟอร์มนี้จะเป็นไปตามนโยบายส่วนบุคคลของ ' +
3806 '<a href="https://www.google.com/intl/th/policies/privacy/" target="_blank">Google</a>',
3807 'languageVal': 'Thai (ภาษาไทย)',
3808 'successTitle': 'ไชโย!',
3809 'successDetails': 'คุณลงชื่อสมัครรับข่าวสารและเคล็ดลับล่าสุดสำหรับนักพัฒนาซอฟต์แวร์ Android เสร็จเรียบร้อยแล้ว'
3810 }
3811 });
3812 break;
3813 case 'tr':
3814 window.polyglot.extend({
3815 'newsletter': {
3816 'title': 'Google Play\'de başarılı olmanıza yardımcı olacak en son Android geliştirici haberleri ve ipuçları.',
3817 'requiredHint': '* Zorunlu Alanlar',
3818 'name': 'Tam ad',
3819 'email': 'E-posta adresi',
3820 'company': 'Şirket / geliştirici adı',
3821 'appUrl': 'Play Store uygulama URL\'lerinizden biri',
3822 'business': {
3823 'label': 'İşletmenizi en iyi hangisi tanımlar?',
3824 'apps': 'Uygulamalar',
3825 'games': 'Oyunlar',
3826 'both': 'Uygulamalar ve Oyunlar'
3827 },
3828 'confirmMailingList': 'Beni, geliştirme ve Google Play fırsatlarıyla ilgili ara sıra gönderilen e-posta ' +
3829 'iletilerine ilişkin posta listesine ve aylık haber bültenine ekle.',
3830 'privacyPolicy': 'Bu formda sağlanan bilgilerin Google\'ın ' +
3831 '<a href="https://www.google.com/intl/tr/policies/privacy/" target="_blank">Gizlilik Politikası\'na</a> ' +
3832 'tabi olacağını kabul ediyorum.',
3833 'languageVal': 'Turkish (Türkçe)',
3834 'successTitle': 'Yaşasın!',
3835 'successDetails': 'En son Android geliştirici haberleri ve ipuçlarına başarıyla kaydoldunuz.'
3836 }
3837 });
3838 break;
3839 case 'vi':
3840 window.polyglot.extend({
3841 'newsletter': {
3842 '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 ' +
3843 'Google Play.',
3844 'requiredHint': '* Các trường bắt buộc',
3845 'name': 'Tên đầy đủ',
3846 'email': 'Địa chỉ email',
3847 'company': 'Tên công ty/nhà phát triển',
3848 'appUrl': 'Một trong số các URL ứng dụng trên cửa hàng Play của bạn',
3849 'business': {
3850 'label': 'Lựa chọn nào sau đây mô tả chính xác nhất doanh nghiệp của bạn?',
3851 'apps': 'Ứng dụng',
3852 'games': 'Trò chơi',
3853 'both': 'Ứng dụng và trò chơi'
3854 },
3855 '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 ' +
3856 'triển và cơ hội của Google Play.',
3857 '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 ' +
3858 'của <a href="https://www.google.com/intl/vi/policies/privacy/" target="_blank">Google</a>.',
3859 'languageVal': 'Vietnamese (tiếng Việt)',
3860 'successTitle': 'Thật tuyệt!',
3861 '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.'
3862 }
3863 });
3864 break;
3865}
3866
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003867(function($) {
3868 'use strict';
3869
3870 function Modal(el, options) {
3871 this.el = $(el);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003872 this.options = $.extend({}, options);
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003873 this.isOpen = false;
3874
3875 this.el.on('click', function(event) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003876 if (!$.contains(this.el.find('.dac-modal-window')[0], event.target)) {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003877 return this.el.trigger('modal-close');
3878 }
3879 }.bind(this));
3880
3881 this.el.on('modal-open', this.open_.bind(this));
3882 this.el.on('modal-close', this.close_.bind(this));
3883 this.el.on('modal-toggle', this.toggle_.bind(this));
3884 }
3885
3886 Modal.prototype.toggle_ = function() {
3887 this.el.trigger('modal-' + (this.isOpen ? 'close' : 'open'));
3888 };
3889
3890 Modal.prototype.close_ = function() {
3891 this.el.removeClass('dac-active');
3892 $('body').removeClass('dac-modal-open');
3893 this.isOpen = false;
Dirk Doughertyc607a4d2016-01-28 08:32:47 -08003894 // When closing the modal for Android Studio downloads, reload the page
3895 // because otherwise we might get stuck with post-download dialog state
3896 if ($("[data-modal='studio_tos']").length) {
3897 location.reload();
3898 }
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003899 };
3900
3901 Modal.prototype.open_ = function() {
3902 this.el.addClass('dac-active');
3903 $('body').addClass('dac-modal-open');
3904 this.isOpen = true;
3905 };
3906
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003907 function onClickToggleModal(event) {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003908 event.preventDefault();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003909 var toggle = $(event.currentTarget);
3910 var options = toggle.data();
3911 var modal = options.modalToggle ? $('[data-modal="' + options.modalToggle + '"]') :
3912 toggle.closest('[data-modal]');
3913 modal.trigger('modal-toggle');
3914 }
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003915
3916 /**
3917 * jQuery plugin
3918 * @param {object} options - Override default options.
3919 */
3920 $.fn.dacModal = function(options) {
3921 return this.each(function() {
3922 new Modal(this, options);
3923 });
3924 };
3925
3926 $.fn.dacToggleModal = function(options) {
3927 return this.each(function() {
3928 new ToggleModal(this, options);
3929 });
3930 };
3931
3932 /**
3933 * Data Attribute API
3934 */
3935 $(document).on('ready.aranja', function() {
3936 $('[data-modal]').each(function() {
3937 $(this).dacModal($(this).data());
3938 });
3939
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003940 $('html').on('click.modal', '[data-modal-toggle]', onClickToggleModal);
3941
3942 // Check if url anchor is targetting a toggle to open the modal.
3943 if (location.hash) {
3944 $(location.hash + '[data-modal-toggle]').trigger('click');
3945 }
3946
3947 if (window.getLangTarget() !== window.getLangPref()) {
3948 $('#langform').trigger('modal-open');
3949 $("#langform button.yes").attr("onclick","window.changeLangPref('" + window.getLangTarget() + "', true); return false;");
3950 $("#langform button.no").attr("onclick","window.changeLangPref('" + window.getLangPref() + "', true); return false;");
3951 }
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003952 });
3953})(jQuery);
3954
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003955/* Fullscreen - Toggle fullscreen mode for reference pages */
3956(function($) {
3957 'use strict';
3958
3959 /**
3960 * @param {HTMLElement} el - The DOM element.
3961 * @constructor
3962 */
3963 function Fullscreen(el) {
3964 this.el = $(el);
3965 this.html = $('html');
3966 this.icon = this.el.find('.dac-sprite');
3967 this.isFullscreen = window.readCookie(Fullscreen.COOKIE_) === 'true';
3968 this.activate_();
3969 this.el.on('click.dac-fullscreen', this.toggleHandler_.bind(this));
3970 }
3971
3972 /**
3973 * Cookie name for storing the state
3974 * @type {string}
3975 * @private
3976 */
3977 Fullscreen.COOKIE_ = 'fullscreen';
3978
3979 /**
3980 * Classes to modify the DOM
3981 * @type {{mode: string, fullscreen: string, fullscreenExit: string}}
3982 * @private
3983 */
3984 Fullscreen.CLASSES_ = {
3985 mode: 'dac-fullscreen-mode',
3986 fullscreen: 'dac-fullscreen',
3987 fullscreenExit: 'dac-fullscreen-exit'
3988 };
3989
3990 /**
3991 * Event listener for toggling fullscreen mode
3992 * @param {MouseEvent} event
3993 * @private
3994 */
3995 Fullscreen.prototype.toggleHandler_ = function(event) {
3996 event.stopPropagation();
3997 this.toggle(!this.isFullscreen, true);
3998 };
3999
4000 /**
4001 * Change the DOM based on current state.
4002 * @private
4003 */
4004 Fullscreen.prototype.activate_ = function() {
4005 this.icon.toggleClass(Fullscreen.CLASSES_.fullscreen, !this.isFullscreen);
4006 this.icon.toggleClass(Fullscreen.CLASSES_.fullscreenExit, this.isFullscreen);
4007 this.html.toggleClass(Fullscreen.CLASSES_.mode, this.isFullscreen);
4008 };
4009
4010 /**
4011 * Toggle fullscreen mode and store the state in a cookie.
4012 */
4013 Fullscreen.prototype.toggle = function() {
4014 this.isFullscreen = !this.isFullscreen;
4015 window.writeCookie(Fullscreen.COOKIE_, this.isFullscreen, null);
4016 this.activate_();
4017 };
4018
4019 /**
4020 * jQuery plugin
4021 */
4022 $.fn.dacFullscreen = function() {
4023 return this.each(function() {
4024 new Fullscreen($(this));
4025 });
4026 };
4027})(jQuery);
4028
4029(function($) {
4030 'use strict';
4031
4032 /**
4033 * @param {HTMLElement} selected - The link that is selected in the nav.
4034 * @constructor
4035 */
4036 function HeaderTabs(selected) {
4037
4038 // Don't highlight any tabs on the index page
4039 if (location.pathname === '/index.html' || location.pathname === '/') {
4040 //return;
4041 }
4042
4043 this.selected = $(selected);
4044 this.selectedParent = this.selected.closest('.dac-nav-secondary').siblings('a');
4045 this.links = $('.dac-header-tabs a');
4046
4047 this.selectActiveTab();
4048 }
4049
4050 HeaderTabs.prototype.selectActiveTab = function() {
4051 var section = null;
4052
4053 if (this.selectedParent.length) {
4054 section = this.selectedParent.text();
4055 } else {
4056 section = this.selected.text();
4057 }
4058
4059 if (section) {
4060 this.links.removeClass('selected');
4061
4062 this.links.filter(function() {
4063 return $(this).text() === $.trim(section);
4064 }).addClass('selected');
4065 }
4066 };
4067
4068 /**
4069 * jQuery plugin
4070 */
4071 $.fn.dacHeaderTabs = function() {
4072 return this.each(function() {
4073 new HeaderTabs(this);
4074 });
4075 };
4076})(jQuery);
4077
4078(function($) {
4079 'use strict';
4080 var icon = $('<i/>').addClass('dac-sprite dac-nav-forward');
4081 var config = JSON.parse(window.localStorage.getItem('global-navigation') || '{}');
4082 var forwardLink = $('<span/>')
4083 .addClass('dac-nav-link-forward')
4084 .html(icon)
4085 .on('click', swap_);
4086
4087 /**
4088 * @constructor
4089 */
4090 function Nav(navigation) {
4091 $('.dac-nav-list').dacCurrentPage().dacHeaderTabs().dacSidebarToggle($('body'));
4092
4093 navigation.find('[data-reference-tree]').dacReferenceNav();
4094
4095 setupViews_(navigation.children().eq(0).children());
4096
4097 initCollapsedNavs(navigation.find('.dac-nav-sub-slider'));
4098
4099 $('#dac-main-navigation').scrollIntoView('.selected')
4100 }
4101
4102 function updateStore(icon) {
4103 var navClass = getCurrentLandingPage_(icon);
4104 var isExpanded = icon.hasClass('dac-expand-less-black');
4105 var expandedNavs = config.expanded || [];
4106 if (isExpanded) {
4107 expandedNavs.push(navClass);
4108 } else {
4109 expandedNavs = expandedNavs.filter(function(item) {
4110 return item !== navClass;
4111 });
4112 }
4113 config.expanded = expandedNavs;
4114 window.localStorage.setItem('global-navigation', JSON.stringify(config));
4115 }
4116
4117 function toggleSubNav_(icon) {
4118 var isExpanded = icon.hasClass('dac-expand-less-black');
4119 icon.toggleClass('dac-expand-less-black', !isExpanded);
4120 icon.toggleClass('dac-expand-more-black', isExpanded);
4121 icon.data('sub-navigation.dac').slideToggle(200);
4122
4123 updateStore(icon);
4124 }
4125
4126 function handleSubNavToggle_(event) {
4127 event.preventDefault();
4128 var icon = $(event.target);
4129 toggleSubNav_(icon);
4130 }
4131
4132 function getCurrentLandingPage_(icon) {
4133 return icon.closest('li')[0].className.replace('dac-nav-item ', '');
4134 }
4135
4136 // Setup sub navigation collapse/expand
4137 function initCollapsedNavs(toggleIcons) {
4138 toggleIcons.each(setInitiallyActive_($('body')));
4139 toggleIcons.on('click', handleSubNavToggle_);
4140
4141 }
4142
4143 function setInitiallyActive_(body) {
4144 var expandedNavs = config.expanded || [];
4145 return function(i, icon) {
4146 icon = $(icon);
4147 var subNav = icon.next();
4148
4149 if (!subNav.length) {
4150 return;
4151 }
4152
4153 var landingPageClass = getCurrentLandingPage_(icon);
4154 var expanded = expandedNavs.indexOf(landingPageClass) >= 0;
4155 landingPageClass = landingPageClass === 'home' ? 'about' : landingPageClass;
4156
4157 // TODO: Should read from localStorage
4158 var visible = body.hasClass(landingPageClass) || expanded;
4159
4160 icon.data('sub-navigation.dac', subNav);
4161 icon.toggleClass('dac-expand-less-black', visible);
4162 icon.toggleClass('dac-expand-more-black', !visible);
4163 subNav.toggle(visible);
4164 };
4165 }
4166
4167 function setupViews_(views) {
4168 if (views.length === 1) {
4169 // Active tier 1 nav.
4170 views.addClass('dac-active');
4171 } else {
4172 // Activate back button and tier 2 nav.
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004173 views.slice(0, 2).addClass('dac-active');
4174 var selectedNav = views.eq(2).find('.selected').after(forwardLink);
Dirk Dougherty82e929d2016-01-30 14:04:37 -08004175 var langAttr = selectedNav.attr(window.getLangPref() + '-lang');
4176 //form the label from locale attr if possible, else set to selectedNav text value
4177 if ((typeof langAttr !== typeof undefined && langAttr !== false) && (langAttr !== '')) {
4178 $('.dac-nav-back-title').text(langAttr);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004179 } else {
4180 $('.dac-nav-back-title').text(selectedNav.text());
4181 }
4182 }
4183
4184 // Navigation should animate.
4185 setTimeout(function() {
4186 views.removeClass('dac-no-anim');
4187 }, 10);
4188 }
4189
4190 function swap_(event) {
4191 event.preventDefault();
4192 $(event.currentTarget).trigger('swap-content');
4193 }
4194
4195 /**
4196 * jQuery plugin
4197 */
4198 $.fn.dacNav = function() {
4199 return this.each(function() {
4200 new Nav($(this));
4201 });
4202 };
4203})(jQuery);
4204
4205/* global NAVTREE_DATA */
4206(function($) {
4207 /**
4208 * Build the reference navigation with namespace dropdowns.
4209 * @param {jQuery} el - The DOM element.
4210 */
4211 function buildReferenceNav(el) {
4212 var namespaceList = el.find('[data-reference-namespaces]');
4213 var resources = el.find('[data-reference-resources]');
4214 var selected = namespaceList.find('.selected');
4215
4216 // Links should be toggleable.
4217 namespaceList.find('a').addClass('dac-reference-nav-toggle dac-closed');
4218
4219 // Load in all resources
4220 $.getScript('/navtree_data.js', function(data, textStatus, xhr) {
4221 if (xhr.status === 200) {
4222 namespaceList.on('click', 'a.dac-reference-nav-toggle', toggleResourcesHandler);
4223 }
4224 });
4225
4226 // No setup required if no resources are present
4227 if (!resources.length) {
4228 return;
4229 }
4230
4231 // The resources should be a part of selected namespace.
4232 var overview = addResourcesToView(resources, selected);
4233
4234 // Currently viewing Overview
4235 if (location.pathname === overview.attr('href')) {
4236 overview.parent().addClass('selected');
4237 }
4238
4239 // Open currently selected resource
4240 var listsToOpen = selected.children().eq(1);
4241 listsToOpen = listsToOpen.add(listsToOpen.find('.selected').parent()).show();
4242
4243 // Mark dropdowns as open
4244 listsToOpen.prev().removeClass('dac-closed');
4245
4246 // Scroll into view
4247 namespaceList.scrollIntoView(selected);
4248 }
4249
4250 /**
4251 * Handles the toggling of resources.
4252 * @param {Event} event
4253 */
4254 function toggleResourcesHandler(event) {
4255 event.preventDefault();
4256 var el = $(this);
4257
4258 // If resources for given namespace is not present, fetch correct data.
4259 if (this.tagName === 'A' && !this.hasResources) {
4260 addResourcesToView(buildResourcesViewForData(getDataForNamespace(el.text())), el.parent());
4261 }
4262
4263 el.toggleClass('dac-closed').next().slideToggle(200);
4264 }
4265
4266 /**
4267 * @param {String} namespace
4268 * @returns {Array} namespace data
4269 */
4270 function getDataForNamespace(namespace) {
4271 var namespaceData = NAVTREE_DATA.filter(function(data) {
4272 return data[0] === namespace;
4273 });
4274
4275 return namespaceData.length ? namespaceData[0][2] : [];
4276 }
4277
4278 /**
4279 * Build a list item for a resource
4280 * @param {Array} resource
4281 * @returns {String}
4282 */
4283 function buildResourceItem(resource) {
4284 return '<li class="api apilevel-' + resource[3] + '"><a href="/' + resource[1] + '">' + resource[0] + '</a></li>';
4285 }
4286
4287 /**
4288 * Build resources list items.
4289 * @param {Array} resources
4290 * @returns {String}
4291 */
4292 function buildResourceList(resources) {
4293 return '<li><h2>' + resources[0] + '</h2><ul>' + resources[2].map(buildResourceItem).join('') + '</ul>';
4294 }
4295
4296 /**
4297 * Build a resources view
4298 * @param {Array} data
4299 * @returns {jQuery} resources in an unordered list.
4300 */
4301 function buildResourcesViewForData(data) {
4302 return $('<ul>' + data.map(buildResourceList).join('') + '</ul>');
4303 }
4304
4305 /**
4306 * Add resources to a containing view.
4307 * @param {jQuery} resources
4308 * @param {jQuery} view
4309 * @returns {jQuery} the overview link.
4310 */
4311 function addResourcesToView(resources, view) {
4312 var namespace = view.children().eq(0);
4313 var overview = $('<a href="' + namespace.attr('href') + '">Overview</a>');
4314
4315 // Mark namespace with content;
4316 namespace[0].hasResources = true;
4317
4318 // Add correct classes / event listeners to resources.
4319 resources.prepend($('<li>').html(overview))
4320 .find('a')
4321 .addClass('dac-reference-nav-resource')
4322 .end()
4323 .find('h2')
4324 .addClass('dac-reference-nav-toggle dac-closed')
4325 .on('click', toggleResourcesHandler)
4326 .end()
4327 .add(resources.find('ul'))
4328 .addClass('dac-reference-nav-resources')
4329 .end()
4330 .appendTo(view);
4331
4332 return overview;
4333 }
4334
4335 /**
4336 * jQuery plugin
4337 */
4338 $.fn.dacReferenceNav = function() {
4339 return this.each(function() {
4340 buildReferenceNav($(this));
4341 });
4342 };
4343})(jQuery);
4344
4345/** Scroll a container to make a target element visible
4346 This is called when the page finished loading. */
4347$.fn.scrollIntoView = function(target) {
4348 if ('string' === typeof target) {
4349 target = this.find(target);
4350 }
4351 if (this.is(':visible')) {
4352 if (target.length == 0) {
4353 // If no selected item found, exit
4354 return;
4355 }
4356
4357 // get the target element's offset from its container nav by measuring the element's offset
4358 // relative to the document then subtract the container nav's offset relative to the document
4359 var targetOffset = target.offset().top - this.offset().top;
4360 var containerHeight = this.height();
4361 if (targetOffset > containerHeight * .8) { // multiply nav height by .8 so we move up the item
4362 // if it's more than 80% down the nav
4363 // scroll the item up by an amount equal to 80% the container height
4364 this.scrollTop(targetOffset - (containerHeight * .8));
4365 }
4366 }
4367};
4368
4369(function($) {
4370 $.fn.dacCurrentPage = function() {
4371 // Highlight the header tabs...
4372 // highlight Design tab
4373 var baseurl = getBaseUri(window.location.pathname);
4374 var urlSegments = baseurl.split('/');
4375 var navEl = this;
4376 var body = $('body');
4377 var subNavEl = navEl.find('.dac-nav-secondary');
4378 var parentNavEl;
4379 var selected;
4380 // In NDK docs, highlight appropriate sub-nav
4381 if (body.hasClass('ndk')) {
4382 if (body.hasClass('guide')) {
4383 selected = navEl.find('> li.guides > a').addClass('selected');
4384 } else if (body.hasClass('reference')) {
4385 selected = navEl.find('> li.reference > a').addClass('selected');
smain@google.coma0a6d322016-02-05 17:34:22 -08004386 } else if (body.hasClass('samples')) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004387 selected = navEl.find('> li.samples > a').addClass('selected');
smain@google.coma0a6d322016-02-05 17:34:22 -08004388 } else if (body.hasClass('downloads')) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004389 selected = navEl.find('> li.downloads > a').addClass('selected');
4390 }
4391 } else if (body.hasClass('design')) {
4392 selected = navEl.find('> li.design > a').addClass('selected');
4393 // highlight Home nav
4394 } else if (body.hasClass('about')) {
4395 parentNavEl = navEl.find('> li.home > a');
4396 parentNavEl.addClass('has-subnav');
4397 // In Home docs, also highlight appropriate sub-nav
4398 if (urlSegments[1] === 'wear' || urlSegments[1] === 'tv' ||
4399 urlSegments[1] === 'auto') {
4400 selected = subNavEl.find('li.' + urlSegments[1] + ' > a').addClass('selected');
4401 } else if (urlSegments[1] === 'about') {
4402 selected = subNavEl.find('li.versions > a').addClass('selected');
4403 } else {
4404 selected = parentNavEl.removeClass('has-subnav').addClass('selected');
4405 }
4406 // highlight Develop nav
4407 } else if (body.hasClass('develop') || body.hasClass('google')) {
4408 parentNavEl = navEl.find('> li.develop > a');
4409 parentNavEl.addClass('has-subnav');
4410 // In Develop docs, also highlight appropriate sub-nav
4411 if (urlSegments[1] === 'training') {
4412 selected = subNavEl.find('li.training > a').addClass('selected');
4413 } else if (urlSegments[1] === 'guide') {
4414 selected = subNavEl.find('li.guide > a').addClass('selected');
4415 } else if (urlSegments[1] === 'reference') {
4416 // If the root is reference, but page is also part of Google Services, select Google
4417 if (body.hasClass('google')) {
4418 selected = subNavEl.find('li.google > a').addClass('selected');
4419 } else {
4420 selected = subNavEl.find('li.reference > a').addClass('selected');
4421 }
4422 } else if ((urlSegments[1] === 'tools') || (urlSegments[1] === 'sdk')) {
4423 selected = subNavEl.find('li.tools > a').addClass('selected');
4424 } else if (body.hasClass('google')) {
4425 selected = subNavEl.find('li.google > a').addClass('selected');
4426 } else if (body.hasClass('samples')) {
4427 selected = subNavEl.find('li.samples > a').addClass('selected');
4428 } else {
4429 selected = parentNavEl.removeClass('has-subnav').addClass('selected');
4430 }
4431 // highlight Distribute nav
4432 } else if (body.hasClass('distribute')) {
4433 parentNavEl = navEl.find('> li.distribute > a');
4434 parentNavEl.addClass('has-subnav');
4435 // In Distribute docs, also highlight appropriate sub-nav
4436 if (urlSegments[2] === 'users') {
4437 selected = subNavEl.find('li.users > a').addClass('selected');
4438 } else if (urlSegments[2] === 'engage') {
4439 selected = subNavEl.find('li.engage > a').addClass('selected');
4440 } else if (urlSegments[2] === 'monetize') {
4441 selected = subNavEl.find('li.monetize > a').addClass('selected');
4442 } else if (urlSegments[2] === 'analyze') {
4443 selected = subNavEl.find('li.analyze > a').addClass('selected');
4444 } else if (urlSegments[2] === 'tools') {
4445 selected = subNavEl.find('li.disttools > a').addClass('selected');
4446 } else if (urlSegments[2] === 'stories') {
4447 selected = subNavEl.find('li.stories > a').addClass('selected');
4448 } else if (urlSegments[2] === 'essentials') {
4449 selected = subNavEl.find('li.essentials > a').addClass('selected');
4450 } else if (urlSegments[2] === 'googleplay') {
4451 selected = subNavEl.find('li.googleplay > a').addClass('selected');
4452 } else {
4453 selected = parentNavEl.removeClass('has-subnav').addClass('selected');
4454 }
Dirk Dougherty86120022016-02-09 18:00:05 -08004455 } else if (body.hasClass('preview')) {
4456 selected = navEl.find('> li.preview > a').addClass('selected');
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004457 }
4458 return $(selected);
4459 };
4460})(jQuery);
4461
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004462(function($) {
4463 'use strict';
4464
4465 /**
4466 * Toggle the visabilty of the mobile navigation.
4467 * @param {HTMLElement} el - The DOM element.
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004468 * @param {Object} options
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004469 * @constructor
4470 */
4471 function ToggleNav(el, options) {
4472 this.el = $(el);
4473 this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004474 this.body = $(document.body);
4475 this.navigation_ = this.body.find(this.options.navigation);
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004476 this.el.on('click', this.clickHandler_.bind(this));
4477 }
4478
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004479 ToggleNav.BREAKPOINT_ = 980;
4480
4481 /**
4482 * Open on correct sizes
4483 */
4484 function toggleSidebarVisibility(body) {
4485 var wasClosed = ('' + localStorage.getItem('navigation-open')) === 'false';
4486
4487 if (wasClosed) {
4488 body.removeClass(ToggleNav.DEFAULTS_.activeClass);
4489 } else if (window.innerWidth >= ToggleNav.BREAKPOINT_) {
4490 body.addClass(ToggleNav.DEFAULTS_.activeClass);
4491 } else {
4492 body.removeClass(ToggleNav.DEFAULTS_.activeClass);
4493 }
4494 }
4495
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004496 /**
4497 * ToggleNav Default Settings
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004498 * @type {{body: boolean, dimmer: string, navigation: string, activeClass: string}}
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004499 * @private
4500 */
4501 ToggleNav.DEFAULTS_ = {
4502 body: true,
4503 dimmer: '.dac-nav-dimmer',
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004504 animatingClass: 'dac-nav-animating',
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004505 navigation: '[data-dac-nav]',
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004506 activeClass: 'dac-nav-open'
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004507 };
4508
4509 /**
4510 * The actual toggle logic.
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004511 * @param {Event} event
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004512 * @private
4513 */
4514 ToggleNav.prototype.clickHandler_ = function(event) {
4515 event.preventDefault();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004516 var animatingClass = this.options.animatingClass;
4517 var body = this.body;
4518
4519 body.addClass(animatingClass);
4520 body.toggleClass(this.options.activeClass);
4521
4522 setTimeout(function() {
4523 body.removeClass(animatingClass);
4524 }, this.navigation_.transitionDuration());
4525
4526 if (window.innerWidth >= ToggleNav.BREAKPOINT_) {
4527 localStorage.setItem('navigation-open', body.hasClass(this.options.activeClass));
4528 }
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004529 };
4530
4531 /**
4532 * jQuery plugin
4533 * @param {object} options - Override default options.
4534 */
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004535 $.fn.dacToggleMobileNav = function() {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004536 return this.each(function() {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004537 var el = $(this);
4538 new ToggleNav(el, el.data());
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004539 });
4540 };
4541
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004542 $.fn.dacSidebarToggle = function(body) {
4543 toggleSidebarVisibility(body);
4544 $(window).on('resize', toggleSidebarVisibility.bind(null, body));
4545 };
4546
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004547 /**
4548 * Data Attribute API
4549 */
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004550 $(function() {
4551 $('[data-dac-toggle-nav]').dacToggleMobileNav();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004552 });
4553})(jQuery);
4554
4555(function($) {
4556 'use strict';
4557
4558 /**
4559 * Submit the newsletter form to a Google Form.
4560 * @param {HTMLElement} el - The Form DOM element.
4561 * @constructor
4562 */
4563 function NewsletterForm(el) {
4564 this.el = $(el);
4565 this.form = this.el.find('form');
4566 $('<iframe/>').hide()
4567 .attr('name', 'dac-newsletter-iframe')
4568 .attr('src', '')
4569 .insertBefore(this.form);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004570 this.el.find('[data-newsletter-language]').val(window.polyglot.t('newsletter.languageVal'));
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004571 this.form.on('submit', this.submitHandler_.bind(this));
4572 }
4573
4574 /**
4575 * Milliseconds until modal has vanished after modal-close is triggered.
4576 * @type {number}
4577 * @private
4578 */
4579 NewsletterForm.CLOSE_DELAY_ = 300;
4580
4581 /**
4582 * Switch view to display form after close.
4583 * @private
4584 */
4585 NewsletterForm.prototype.closeHandler_ = function() {
4586 setTimeout(function() {
4587 this.el.trigger('swap-reset');
4588 }.bind(this), NewsletterForm.CLOSE_DELAY_);
4589 };
4590
4591 /**
4592 * Reset the modal to initial state.
4593 * @private
4594 */
4595 NewsletterForm.prototype.reset_ = function() {
4596 this.form.trigger('reset');
4597 this.el.one('modal-close', this.closeHandler_.bind(this));
4598 };
4599
4600 /**
4601 * Display a success view on submit.
4602 * @private
4603 */
4604 NewsletterForm.prototype.submitHandler_ = function() {
4605 this.el.one('swap-complete', this.reset_.bind(this));
4606 this.el.trigger('swap-content');
4607 };
4608
4609 /**
4610 * jQuery plugin
4611 * @param {object} options - Override default options.
4612 */
4613 $.fn.dacNewsletterForm = function(options) {
4614 return this.each(function() {
4615 new NewsletterForm(this, options);
4616 });
4617 };
4618
4619 /**
4620 * Data Attribute API
4621 */
4622 $(document).on('ready.aranja', function() {
4623 $('[data-newsletter]').each(function() {
4624 $(this).dacNewsletterForm();
4625 });
4626 });
4627})(jQuery);
4628
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004629/* globals METADATA, YOUTUBE_RESOURCES, BLOGGER_RESOURCES */
4630window.metadata = {};
4631
4632/**
4633 * Prepare metadata and indices for querying.
4634 */
4635window.metadata.prepare = (function() {
4636 // Helper functions.
4637 function mergeArrays() {
4638 return Array.prototype.concat.apply([], arguments);
4639 }
4640
4641 /**
4642 * Creates lookup maps for a resource index.
4643 * I.e. where MAP['some tag'][resource.id] === true when that resource has 'some tag'.
4644 * @param resourceDict
4645 * @returns {{}}
4646 */
4647 function buildResourceLookupMap(resourceDict) {
4648 var map = {};
4649 for (var key in resourceDict) {
4650 var dictForKey = {};
4651 var srcArr = resourceDict[key];
4652 for (var i = 0; i < srcArr.length; i++) {
4653 dictForKey[srcArr[i].index] = true;
4654 }
4655 map[key] = dictForKey;
4656 }
4657 return map;
4658 }
4659
4660 /**
4661 * Merges metadata maps for english and the current language into the global store.
4662 */
4663 function mergeMetadataMap(name, locale) {
4664 if (locale && locale !== 'en' && METADATA[locale]) {
4665 METADATA[name] = $.extend(METADATA.en[name], METADATA[locale][name]);
4666 } else {
4667 METADATA[name] = METADATA.en[name];
4668 }
4669 }
4670
4671 /**
4672 * Index all resources by type, url, tag and category.
4673 * @param resources
4674 */
4675 function createIndices(resources) {
4676 // URL, type, tag and category lookups
4677 var byType = METADATA.byType = {};
4678 var byUrl = METADATA.byUrl = {};
4679 var byTag = METADATA.byTag = {};
4680 var byCategory = METADATA.byCategory = {};
4681
4682 for (var i = 0; i < resources.length; i++) {
4683 var res = resources[i];
4684
4685 // Store index.
4686 res.index = i;
4687
4688 // Index by type.
4689 var type = res.type;
4690 if (type) {
4691 byType[type] = byType[type] || [];
4692 byType[type].push(res);
4693 }
4694
4695 // Index by tag.
4696 var tags = res.tags || [];
4697 for (var j = 0; j < tags.length; j++) {
4698 var tag = tags[j];
4699 if (tag) {
4700 byTag[tag] = byTag[tag] || [];
4701 byTag[tag].push(res);
4702 }
4703 }
4704
4705 // Index by category.
4706 var category = res.category;
4707 if (category) {
4708 byCategory[category] = byCategory[category] || [];
4709 byCategory[category].push(res);
4710 }
4711
4712 // Index by url.
4713 var url = res.url;
4714 if (url) {
4715 res.baseUrl = url.replace(/^intl\/\w+[\/]/, '');
4716 byUrl[res.baseUrl] = res;
4717 }
4718 }
4719 METADATA.hasType = buildResourceLookupMap(byType);
4720 METADATA.hasTag = buildResourceLookupMap(byTag);
4721 METADATA.hasCategory = buildResourceLookupMap(byCategory);
4722 }
4723
4724 return function() {
4725 // Only once.
4726 if (METADATA.all) { return; }
4727
4728 // Get current language.
4729 var locale = getLangPref();
4730
4731 // Merge english resources.
4732 METADATA.all = mergeArrays(
4733 METADATA.en.about,
4734 METADATA.en.design,
4735 METADATA.en.distribute,
4736 METADATA.en.develop,
4737 YOUTUBE_RESOURCES,
4738 BLOGGER_RESOURCES,
4739 METADATA.en.extras
4740 );
4741
4742 // Merge local language resources.
4743 if (locale !== 'en' && METADATA[locale]) {
4744 METADATA.all = mergeArrays(
4745 METADATA.all,
4746 METADATA[locale].about,
4747 METADATA[locale].design,
4748 METADATA[locale].distribute,
4749 METADATA[locale].develop,
4750 METADATA[locale].extras
4751 );
4752 }
4753
4754 mergeMetadataMap('collections', locale);
4755 mergeMetadataMap('searchHeroCollections', locale);
4756 mergeMetadataMap('carousel', locale);
4757
4758 // Create query indicies for resources.
4759 createIndices(METADATA.all, locale);
4760
4761 // Reference metadata.
4762 METADATA.androidReference = window.DATA;
4763 METADATA.googleReference = mergeArrays(window.GMS_DATA, window.GCM_DATA);
4764 };
4765})();
4766
4767/* global METADATA, util */
4768window.metadata.query = (function($) {
4769 var pageMap = {};
4770
4771 function buildResourceList(opts) {
4772 window.metadata.prepare();
4773 var expressions = parseResourceQuery(opts.query || '');
4774 var instanceMap = {};
4775 var results = [];
4776
4777 for (var i = 0; i < expressions.length; i++) {
4778 var clauses = expressions[i];
4779
4780 // Get all resources for first clause
4781 var resources = getResourcesForClause(clauses.shift());
4782
4783 // Concat to final results list
4784 results = results.concat(resources.map(filterResources(clauses, i > 0, instanceMap)).filter(filterEmpty));
4785 }
4786
4787 // Set correct order
4788 if (opts.sortOrder && results.length) {
4789 results = opts.sortOrder === 'random' ? util.shuffle(results) : results.sort(sortResultsByKey(opts.sortOrder));
4790 }
4791
4792 // Slice max results.
4793 if (opts.maxResults !== Infinity) {
4794 results = results.slice(0, opts.maxResults);
4795 }
4796
4797 // Remove page level duplicates
4798 if (opts.allowDuplicates === undefined || opts.allowDuplicates === 'false') {
4799 results = results.filter(removePageLevelDuplicates);
4800
4801 for (var index = 0; index < results.length; ++index) {
4802 pageMap[results[index].index] = 1;
4803 }
4804 }
4805
4806 return results;
4807 }
4808
4809 function filterResources(clauses, removeDuplicates, map) {
4810 return function(resource) {
4811 var resourceIsAllowed = true;
4812
4813 // References must be defined.
4814 if (resource === undefined) {
4815 return;
4816 }
4817
4818 // Get canonical (localized) version of resource if possible.
4819 resource = METADATA.byUrl[resource.baseUrl] || METADATA.byUrl[resource.url] || resource;
4820
4821 // Filter out resources already used
4822 if (removeDuplicates) {
4823 resourceIsAllowed = !map[resource.index];
4824 }
4825
4826 // Must fulfill all criteria
4827 if (clauses.length > 0) {
4828 resourceIsAllowed = resourceIsAllowed && doesResourceMatchClauses(resource, clauses);
4829 }
4830
4831 // Mark resource as used.
4832 if (resourceIsAllowed) {
4833 map[resource.index] = 1;
4834 }
4835
4836 return resourceIsAllowed && resource;
4837 };
4838 }
4839
4840 function filterEmpty(resource) {
4841 return resource;
4842 }
4843
4844 function sortResultsByKey(key) {
4845 var desc = key.charAt(0) === '-';
4846
4847 if (desc) {
4848 key = key.substring(1);
4849 }
4850
4851 return function(x, y) {
4852 return (desc ? -1 : 1) * (parseInt(x[key], 10) - parseInt(y[key], 10));
4853 };
4854 }
4855
4856 function getResourcesForClause(clause) {
4857 switch (clause.attr) {
4858 case 'type':
4859 return METADATA.byType[clause.value];
4860 case 'tag':
4861 return METADATA.byTag[clause.value];
4862 case 'collection':
4863 var resources = METADATA.collections[clause.value] || {};
4864 return getResourcesByUrlCollection(resources.resources);
4865 case 'history':
4866 return getResourcesByUrlCollection($.dacGetVisitedUrls(clause.value));
4867 case 'section':
4868 return getResourcesByUrlCollection([clause.value].sections);
4869 default:
4870 return [];
4871 }
4872 }
4873
4874 function getResourcesByUrlCollection(resources) {
4875 return (resources || []).map(function(url) {
4876 return METADATA.byUrl[url];
4877 });
4878 }
4879
4880 function removePageLevelDuplicates(resource) {
4881 return resource && !pageMap[resource.index];
4882 }
4883
4884 function doesResourceMatchClauses(resource, clauses) {
4885 for (var i = 0; i < clauses.length; i++) {
4886 var map;
4887 switch (clauses[i].attr) {
4888 case 'type':
4889 map = METADATA.hasType[clauses[i].value];
4890 break;
4891 case 'tag':
4892 map = METADATA.hasTag[clauses[i].value];
4893 break;
4894 }
4895
4896 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
4897 return clauses[i].negative;
4898 }
4899 }
4900
4901 return true;
4902 }
4903
4904 function parseResourceQuery(query) {
4905 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
4906 var expressions = [];
4907 var expressionStrs = query.split(',') || [];
4908 for (var i = 0; i < expressionStrs.length; i++) {
4909 var expr = expressionStrs[i] || '';
4910
4911 // Break expression into clauses (clause e.g. 'tag:foo')
4912 var clauses = [];
4913 var clauseStrs = expr.split(/(?=[\+\-])/);
4914 for (var j = 0; j < clauseStrs.length; j++) {
4915 var clauseStr = clauseStrs[j] || '';
4916
4917 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
4918 var parts = clauseStr.split(':');
4919 var clause = {};
4920
4921 clause.attr = parts[0].replace(/^\s+|\s+$/g, '');
4922 if (clause.attr) {
4923 if (clause.attr.charAt(0) === '+') {
4924 clause.attr = clause.attr.substring(1);
4925 } else if (clause.attr.charAt(0) === '-') {
4926 clause.negative = true;
4927 clause.attr = clause.attr.substring(1);
4928 }
4929 }
4930
4931 if (parts.length > 1) {
4932 clause.value = parts[1].replace(/^\s+|\s+$/g, '');
4933 }
4934
4935 clauses.push(clause);
4936 }
4937
4938 if (!clauses.length) {
4939 continue;
4940 }
4941
4942 expressions.push(clauses);
4943 }
4944
4945 return expressions;
4946 }
4947
4948 return buildResourceList;
4949})(jQuery);
4950
4951/* global METADATA, getLangPref */
4952
4953window.metadata.search = (function() {
4954 'use strict';
4955
4956 var currentLang = getLangPref();
4957
4958 function search(query) {
4959 window.metadata.prepare();
4960 return {
4961 android: findDocsMatches(query, METADATA.androidReference),
4962 docs: findDocsMatches(query, METADATA.googleReference),
4963 resources: findResourceMatches(query)
4964 };
4965 }
4966
4967 function findDocsMatches(query, data) {
4968 var results = [];
4969
4970 for (var i = 0; i < data.length; i++) {
4971 var s = data[i];
4972 if (query.length !== 0 && s.label.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
4973 results.push(s);
4974 }
4975 }
4976
4977 rankAutocompleteApiResults(query, results);
4978
4979 return results;
4980 }
4981
4982 function findResourceMatches(query) {
4983 var results = [];
4984
4985 // Search for matching JD docs
4986 if (query.length >= 2) {
4987 /* In some langs, spaces may be optional between certain non-Ascii word-glyphs. For
4988 * those langs, only match query at word boundaries if query includes Ascii chars only.
4989 */
4990 var NO_BOUNDARY_LANGUAGES = ['ja','ko','vi','zh-cn','zh-tw'];
4991 var isAsciiOnly = /^[\u0000-\u007f]*$/.test(query);
4992 var noBoundaries = (NO_BOUNDARY_LANGUAGES.indexOf(window.getLangPref()) !== -1);
4993 var exprBoundary = (!isAsciiOnly && noBoundaries) ? '' : '(?:^|\\s)';
4994 var queryRegex = new RegExp(exprBoundary + query.toLowerCase(), 'g');
smain@google.coma0a6d322016-02-05 17:34:22 -08004995
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004996 var all = METADATA.all;
4997 for (var i = 0; i < all.length; i++) {
4998 // current search comparison, with counters for tag and title,
4999 // used later to improve ranking
5000 var s = all[i];
5001 s.matched_tag = 0;
5002 s.matched_title = 0;
5003 var matched = false;
5004
5005 // Check if query matches any tags; work backwards toward 1 to assist ranking
5006 if (s.keywords) {
5007 for (var j = s.keywords.length - 1; j >= 0; j--) {
5008 // it matches a tag
5009 if (s.keywords[j].toLowerCase().match(queryRegex)) {
5010 matched = true;
5011 s.matched_tag = j + 1; // add 1 to index position
5012 }
5013 }
5014 }
5015
5016 // Check if query matches doc title
5017 if (s.title.toLowerCase().match(queryRegex)) {
5018 matched = true;
5019 s.matched_title = 1;
5020 }
5021
5022 // Remember the doc if it matches either
5023 if (matched) {
5024 results.push(s);
5025 }
5026 }
5027
5028 // Improve the current results
5029 results = lookupBetterResult(results);
5030
5031 // Rank/sort all the matched pages
5032 rankAutocompleteDocResults(results);
5033
5034 return results;
5035 }
5036 }
5037
5038 // Replaces a match with another resource by url, if it exists.
5039 function lookupReplacementByUrl(match, url) {
5040 var replacement = METADATA.byUrl[url];
5041
5042 // Replacement resource does not exists.
5043 if (!replacement) { return; }
5044
5045 replacement.matched_title = Math.max(replacement.matched_title, match.matched_title);
5046 replacement.matched_tag = Math.max(replacement.matched_tag, match.matched_tag);
5047
5048 return replacement;
5049 }
5050
5051 // Find the localized version of a page if it exists.
5052 function lookupLocalizedVersion(match) {
5053 return METADATA.byUrl[match.baseUrl] || METADATA.byUrl[match.url];
5054 }
5055
5056 // Find the main page for a tutorial when matching a subpage.
5057 function lookupTutorialIndex(match) {
5058 // Guard for non index tutorial pages.
5059 if (match.type !== 'training' || match.url.indexOf('index.html') >= 0) { return; }
5060
5061 var indexUrl = match.url.replace(/[^\/]+$/, 'index.html');
5062 return lookupReplacementByUrl(match, indexUrl);
5063 }
5064
5065 // Find related results which are a better match for the user.
5066 function lookupBetterResult(matches) {
5067 var newMatches = [];
5068
5069 matches = matches.filter(function(match) {
5070 var newMatch = match;
5071 newMatch = lookupTutorialIndex(newMatch) || newMatch;
5072 newMatch = lookupLocalizedVersion(newMatch) || newMatch;
5073
5074 if (newMatch !== match) {
5075 newMatches.push(newMatch);
5076 }
5077
5078 return newMatch === match;
5079 });
5080
5081 return toUnique(newMatches.concat(matches));
5082 }
5083
5084 /* Order the jd doc result list based on match quality */
5085 function rankAutocompleteDocResults(matches) {
5086 if (!matches || !matches.length) {
5087 return;
5088 }
5089
5090 var _resultScoreFn = function(match) {
5091 var score = 1.0;
5092
5093 // if the query matched a tag
5094 if (match.matched_tag > 0) {
5095 // multiply score by factor relative to position in tags list (max of 3)
5096 score *= 3 / match.matched_tag;
5097
5098 // if it also matched the title
5099 if (match.matched_title > 0) {
5100 score *= 2;
5101 }
5102 } else if (match.matched_title > 0) {
5103 score *= 3;
5104 }
5105
5106 if (match.lang === currentLang) {
5107 score *= 5;
5108 }
5109
5110 return score;
5111 };
5112
5113 for (var i = 0; i < matches.length; i++) {
5114 matches[i].__resultScore = _resultScoreFn(matches[i]);
5115 }
5116
5117 matches.sort(function(a, b) {
5118 var n = b.__resultScore - a.__resultScore;
5119
5120 if (n === 0) {
5121 // lexicographical sort if scores are the same
5122 n = (a.title < b.title) ? -1 : 1;
5123 }
5124
5125 return n;
5126 });
5127 }
5128
5129 /* Order the result list based on match quality */
5130 function rankAutocompleteApiResults(query, matches) {
5131 query = query || '';
5132 if (!matches || !matches.length) {
5133 return;
5134 }
5135
5136 // helper function that gets the last occurence index of the given regex
5137 // in the given string, or -1 if not found
5138 var _lastSearch = function(s, re) {
5139 if (s === '') {
5140 return -1;
5141 }
5142 var l = -1;
5143 var tmp;
5144 while ((tmp = s.search(re)) >= 0) {
5145 if (l < 0) {
5146 l = 0;
5147 }
5148 l += tmp;
5149 s = s.substr(tmp + 1);
5150 }
5151 return l;
5152 };
5153
5154 // helper function that counts the occurrences of a given character in
5155 // a given string
5156 var _countChar = function(s, c) {
5157 var n = 0;
5158 for (var i = 0; i < s.length; i++) {
5159 if (s.charAt(i) === c) {
5160 ++n;
5161 }
5162 }
5163 return n;
5164 };
5165
5166 var queryLower = query.toLowerCase();
5167 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
5168 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
5169 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
5170
5171 var _resultScoreFn = function(result) {
5172 // scores are calculated based on exact and prefix matches,
5173 // and then number of path separators (dots) from the last
5174 // match (i.e. favoring classes and deep package names)
5175 var score = 1.0;
5176 var labelLower = result.label.toLowerCase();
5177 var t;
5178 var partsAfter;
5179 t = _lastSearch(labelLower, partExactAlnumRE);
5180 if (t >= 0) {
5181 // exact part match
5182 partsAfter = _countChar(labelLower.substr(t + 1), '.');
5183 score *= 200 / (partsAfter + 1);
5184 } else {
5185 t = _lastSearch(labelLower, partPrefixAlnumRE);
5186 if (t >= 0) {
5187 // part prefix match
5188 partsAfter = _countChar(labelLower.substr(t + 1), '.');
5189 score *= 20 / (partsAfter + 1);
5190 }
5191 }
5192
5193 return score;
5194 };
5195
5196 for (var i = 0; i < matches.length; i++) {
5197 // if the API is deprecated, default score is 0; otherwise, perform scoring
5198 if (matches[i].deprecated === 'true') {
5199 matches[i].__resultScore = 0;
5200 } else {
5201 matches[i].__resultScore = _resultScoreFn(matches[i]);
5202 }
5203 }
5204
5205 matches.sort(function(a, b) {
5206 var n = b.__resultScore - a.__resultScore;
5207
5208 if (n === 0) {
5209 // lexicographical sort if scores are the same
5210 n = (a.label < b.label) ? -1 : 1;
5211 }
5212
5213 return n;
5214 });
5215 }
5216
5217 // Destructive but fast toUnique.
5218 // http://stackoverflow.com/a/25082874
5219 function toUnique(array) {
5220 var c;
5221 var b = array.length || 1;
5222
5223 while (c = --b) {
5224 while (c--) {
5225 if (array[b] === array[c]) {
5226 array.splice(c, 1);
5227 }
5228 }
5229 }
5230 return array;
5231 }
5232
5233 return search;
5234})();
5235
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005236(function($) {
5237 'use strict';
5238
5239 /**
5240 * Smoothly scroll to location on current page.
5241 * @param el
5242 * @param options
5243 * @constructor
5244 */
5245 function ScrollButton(el, options) {
5246 this.el = $(el);
5247 this.target = $(this.el.attr('href'));
5248 this.options = $.extend({}, ScrollButton.DEFAULTS_, options);
5249
5250 if (typeof this.options.offset === 'string') {
5251 this.options.offset = $(this.options.offset).height();
5252 }
5253
5254 this.el.on('click', this.clickHandler_.bind(this));
5255 }
5256
5257 /**
5258 * Default options
5259 * @type {{duration: number, easing: string, offset: number, scrollContainer: string}}
5260 * @private
5261 */
5262 ScrollButton.DEFAULTS_ = {
5263 duration: 300,
5264 easing: 'swing',
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005265 offset: '.dac-header',
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005266 scrollContainer: 'html, body'
5267 };
5268
5269 /**
5270 * Scroll logic
5271 * @param event
5272 * @private
5273 */
5274 ScrollButton.prototype.clickHandler_ = function(event) {
5275 if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
5276 return;
5277 }
5278
5279 event.preventDefault();
5280
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005281 var position = this.getTargetPosition();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005282 $(this.options.scrollContainer).animate({
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005283 scrollTop: position - this.options.offset
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005284 }, this.options);
5285 };
5286
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005287 ScrollButton.prototype.getTargetPosition = function() {
5288 if (this.options.scrollContainer === ScrollButton.DEFAULTS_.scrollContainer) {
5289 return this.target.offset().top;
5290 }
5291 var scrollContainer = $(this.options.scrollContainer)[0];
5292 var currentEl = this.target[0];
5293 var pos = 0;
5294 while (currentEl !== scrollContainer && currentEl !== null) {
5295 pos += currentEl.offsetTop;
5296 currentEl = currentEl.offsetParent;
5297 }
5298 return pos;
5299 };
5300
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005301 /**
5302 * jQuery plugin
5303 * @param {object} options - Override default options.
5304 */
5305 $.fn.dacScrollButton = function(options) {
5306 return this.each(function() {
5307 new ScrollButton(this, options);
5308 });
5309 };
5310
5311 /**
5312 * Data Attribute API
5313 */
5314 $(document).on('ready.aranja', function() {
5315 $('[data-scroll-button]').each(function() {
5316 $(this).dacScrollButton($(this).data());
5317 });
5318 });
5319})(jQuery);
5320
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005321/* global getLangPref */
5322(function($) {
5323 var LANG;
5324
5325 function getSearchLang() {
5326 if (!LANG) {
5327 LANG = getLangPref();
5328
5329 // Fix zh-cn to be zh-CN.
5330 LANG = LANG.replace(/-\w+/, function(m) { return m.toUpperCase(); });
5331 }
5332 return LANG;
5333 }
5334
5335 function customSearch(query, start) {
5336 var searchParams = {
5337 // current cse instance:
5338 //cx: '001482626316274216503:zu90b7s047u',
5339 // new cse instance:
5340 cx: '000521750095050289010:zpcpi1ea4s8',
5341 key: 'AIzaSyCFhbGnjW06dYwvRCU8h_zjdpS4PYYbEe8',
5342 q: query,
5343 start: start || 1,
smain@google.com38b0e222016-03-16 11:30:53 -07005344 num: 9,
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005345 hl: getSearchLang(),
5346 fields: 'queries,items(pagemap,link,title,htmlSnippet,formattedUrl)'
5347 };
5348
5349 return $.get('https://content.googleapis.com/customsearch/v1?' + $.param(searchParams));
5350 }
5351
5352 function renderResults(el, results) {
5353 if (!results.items) {
5354 el.append($('<div>').text('No results'));
5355 return;
5356 }
5357
5358 for (var i = 0; i < results.items.length; i++) {
5359 var item = results.items[i];
5360 var hasImage = item.pagemap && item.pagemap.cse_thumbnail;
5361 var sectionMatch = item.link.match(/developer\.android\.com\/(\w*)/);
5362 var section = (sectionMatch && sectionMatch[1]) || 'blog';
5363
5364 var entry = $('<div>').addClass('dac-custom-search-entry cols');
5365
5366 if (hasImage) {
5367 var image = item.pagemap.cse_thumbnail[0];
smain@google.com38b0e222016-03-16 11:30:53 -07005368 entry.append($('<div>').addClass('dac-custom-search-image-wrapper')
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005369 .append($('<div>').addClass('dac-custom-search-image').css('background-image', 'url(' + image.src + ')')));
5370 }
5371
smain@google.com38b0e222016-03-16 11:30:53 -07005372 entry.append($('<div>').addClass('dac-custom-search-text-wrapper')
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005373 .append($('<p>').addClass('dac-custom-search-section').text(section))
5374 .append(
5375 $('<a>').text(item.title).attr('href', item.link).wrap('<h2>').parent().addClass('dac-custom-search-title')
5376 )
5377 .append($('<p>').addClass('dac-custom-search-snippet').html(item.htmlSnippet.replace(/<br>/g, '')))
5378 .append($('<a>').addClass('dac-custom-search-link').text(item.formattedUrl).attr('href', item.link)));
5379
5380 el.append(entry);
5381 }
5382
5383 if (results.queries.nextPage) {
5384 var loadMoreButton = $('<button id="dac-custom-search-load-more">')
5385 .addClass('dac-custom-search-load-more')
5386 .text('Load more')
5387 .click(function() {
5388 loadMoreResults(el, results);
5389 });
5390
5391 el.append(loadMoreButton);
5392 }
5393 }
5394
5395 function loadMoreResults(el, results) {
5396 var query = results.queries.request.searchTerms;
5397 var start = results.queries.nextPage.startIndex;
5398 var loadMoreButton = el.find('#dac-custom-search-load-more');
5399
5400 loadMoreButton.text('Loading more...');
5401
5402 customSearch(query, start).then(function(results) {
5403 loadMoreButton.remove();
5404 renderResults(el, results);
5405 });
5406 }
5407
5408 $.fn.customSearch = function(query) {
5409 var el = $(this);
5410
5411 customSearch(query).then(function(results) {
5412 el.empty();
5413 renderResults(el, results);
5414 });
5415 };
5416})(jQuery);
5417
5418/* global METADATA */
5419
5420(function($) {
5421 $.fn.dacSearchRenderHero = function(resources, query) {
5422 var el = $(this);
5423 el.empty();
5424
5425 var resource = METADATA.searchHeroCollections[query];
5426
5427 if (resource) {
5428 el.dacHero(resource, true);
5429 el.show();
5430
5431 return true;
5432 } else {
5433 el.hide();
5434 }
5435 };
5436})(jQuery);
5437
5438(function($) {
5439 $.fn.dacSearchRenderReferences = function(results, query) {
5440 var referenceCard = $('.suggest-card.reference');
5441 referenceCard.data('searchreferences.dac', {results: results, query: query});
5442 renderResults(referenceCard, results, query, false);
5443 };
5444
smain@google.com38b0e222016-03-16 11:30:53 -07005445 var ROW_COUNT_COLLAPSED = 20;
5446 var ROW_COUNT_EXPANDED = 40;
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005447 var ROW_COUNT_GOOGLE_COLLAPSED = 1;
5448 var ROW_COUNT_GOOGLE_EXPANDED = 8;
5449
5450 function onSuggestionClick(e) {
5451 var normalClick = e.which === 1 && !e.ctrlKey && !e.shiftKey && !e.metaKey;
5452 if (normalClick) {
5453 e.preventDefault();
5454 }
5455
5456 // When user clicks a suggested document, track it
5457 var url = $(e.currentTarget).attr('href');
5458 ga('send', 'event', 'Suggestion Click', 'clicked: ' + url,
5459 'query: ' + $('#search_autocomplete').val().toLowerCase(),
5460 {hitCallback: function() {
5461 if (normalClick) {
5462 document.location = url;
5463 }
5464 }});
5465 }
5466
5467 function buildLink(match) {
5468 var link = $('<a>').attr('href', window.toRoot + match.link);
5469
5470 var label = match.label;
5471 var classNameStart = label.match(/[A-Z]/) ? label.search(/[A-Z]/) : label.lastIndexOf('.') + 1;
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005472 var newLink = '<span class="namespace">' +
5473 label.substr(0, classNameStart) +
smain@google.com1c8d4a42016-02-26 16:30:50 -08005474 '</span>' +
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005475 label.substr(classNameStart, label.length);
5476
5477 link.html(newLink);
5478 return link;
5479 }
5480
5481 function buildSuggestion(match, query) {
5482 var li = $('<li>').addClass('dac-search-results-reference-entry');
5483
5484 var link = buildLink(match);
5485 link.highlightMatches(query);
5486 li.append(link);
5487 return li[0];
5488 }
5489
5490 function buildResults(results, query) {
5491 return results.map(function(match) {
5492 return buildSuggestion(match, query);
5493 });
5494 }
5495
5496 function renderAndroidResults(list, gMatches, query) {
5497 list.empty();
5498
smain@google.com1c8d4a42016-02-26 16:30:50 -08005499 var header = $('<li class="dac-search-results-reference-header">android</li>');
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005500 list.append(header);
5501
5502 if (gMatches.length > 0) {
5503 list.removeClass('no-results');
5504
5505 var resources = buildResults(gMatches, query);
5506 list.append(resources);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005507 return true;
5508 } else {
5509 list.append('<li class="dac-search-results-reference-entry-empty">No results</li>');
5510 }
5511 }
5512
5513 function renderGoogleDocsResults(list, gGoogleMatches, query) {
5514 list = $('.suggest-card.reference ul');
5515
5516 if (gGoogleMatches.length > 0) {
5517 list.append('<li class="dac-search-results-reference-header">in Google Services</li>');
5518
5519 var resources = buildResults(gGoogleMatches, query);
5520 list.append(resources);
5521
5522 return true;
5523 }
5524 }
5525
5526 function renderResults(referenceCard, results, query, expanded) {
5527 var list = referenceCard.find('ul');
5528 list.toggleClass('is-expanded', !!expanded);
5529
5530 // Figure out how many results we can show in our fixed size box.
5531 var total = expanded ? ROW_COUNT_EXPANDED : ROW_COUNT_COLLAPSED;
5532 var googleCount = expanded ? ROW_COUNT_GOOGLE_EXPANDED : ROW_COUNT_GOOGLE_COLLAPSED;
5533 googleCount = Math.max(googleCount, total - results.android.length);
5534 googleCount = Math.min(googleCount, results.docs.length);
5535
5536 if (googleCount > 0) {
5537 // If there are google results, reserve space for its header.
5538 googleCount++;
5539 }
5540
5541 var androidCount = Math.max(0, total - googleCount);
5542 if (androidCount === 0) {
5543 // Reserve space for "No reference results"
5544 googleCount--;
5545 }
5546
5547 renderAndroidResults(list, results.android.slice(0, androidCount), query);
5548 renderGoogleDocsResults(list, results.docs.slice(0, googleCount - 1), query);
5549
5550 var totalResults = results.android.length + results.docs.length;
5551 if (totalResults === 0) {
5552 list.addClass('no-results');
5553 }
5554
5555 // Tweak see more logic to account for references.
5556 var hasMore = totalResults > ROW_COUNT_COLLAPSED && !util.matchesMedia('mobile');
smain@google.com1c8d4a42016-02-26 16:30:50 -08005557 if (hasMore) {
5558 // We can't actually show all matches, only as many as the expanded list
5559 // will fit, so we actually lie if the total results count is more
5560 var moreCount = Math.min(totalResults, ROW_COUNT_EXPANDED + ROW_COUNT_GOOGLE_EXPANDED);
smain@google.com38b0e222016-03-16 11:30:53 -07005561 var $moreLink = $('<li class="dac-search-results-reference-entry-empty " data-toggle="show-more">see more matches</li>');
smain@google.com1c8d4a42016-02-26 16:30:50 -08005562 list.append($moreLink.on('click', onToggleMore));
5563 }
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005564 var searchEl = $('#search-resources');
5565 searchEl.toggleClass('dac-has-more', searchEl.hasClass('dac-has-more') || (hasMore && !expanded));
5566 searchEl.toggleClass('dac-has-less', searchEl.hasClass('dac-has-less') || (hasMore && expanded));
5567 }
5568
5569 function onToggleMore(e) {
5570 var link = $(e.currentTarget);
5571 var referenceCard = $('.suggest-card.reference');
5572 var data = referenceCard.data('searchreferences.dac');
5573
5574 if (util.matchesMedia('mobile')) { return; }
5575
5576 renderResults(referenceCard, data.results, data.query, link.data('toggle') === 'show-more');
5577 }
5578
5579 $(document).on('click', '.dac-search-results-resources [data-toggle="show-more"]', onToggleMore);
5580 $(document).on('click', '.dac-search-results-resources [data-toggle="show-less"]', onToggleMore);
5581 $(document).on('click', '.suggest-card.reference a', onSuggestionClick);
5582})(jQuery);
5583
5584(function($) {
5585 function highlightPage(query, page) {
5586 page.find('.title').highlightMatches(query);
5587 }
5588
5589 $.fn.dacSearchRenderResources = function(gDocsMatches, query) {
5590 this.resourceWidget(gDocsMatches, {
5591 itemsPerPage: 18,
5592 initialResults: 6,
5593 cardSizes: ['6x2'],
5594 onRenderPage: highlightPage.bind(null, query)
5595 });
5596
5597 return this;
5598 };
5599})(jQuery);
5600
5601/*global metadata */
5602
5603(function($, metadata) {
5604 'use strict';
5605
5606 function Search() {
5607 this.body = $('body');
5608 this.lastQuery = null;
5609 this.searchResults = $('#search-results');
5610 this.searchClose = $('[data-search-close]');
5611 this.searchClear = $('[data-search-clear]');
5612 this.searchInput = $('#search_autocomplete');
5613 this.searchResultsContent = $('#dac-search-results-content');
5614 this.searchResultsFor = $('#search-results-for');
5615 this.searchResultsHistory = $('#dac-search-results-history');
5616 this.searchResultsResources = $('#search-resources');
5617 this.searchResultsHero = $('#dac-search-results-hero');
5618 this.searchResultsReference = $('#dac-search-results-reference');
5619 this.searchHeader = $('[data-search]').data('search-input.dac');
5620 }
5621
5622 Search.prototype.init = function() {
5623 if (this.checkRedirectToIndex()) { return; }
5624
5625 this.searchHistory = window.dacStore('search-history');
5626
5627 this.searchInput.focus(this.onSearchChanged.bind(this));
5628 this.searchInput.keydown(this.handleKeyboardShortcut.bind(this));
5629 this.searchInput.on('input', this.onSearchChanged.bind(this));
5630 this.searchClear.click(this.clear.bind(this));
5631 this.searchClose.click(this.close.bind(this));
5632
5633 this.customSearch = $.fn.debounce(function(query) {
5634 $('#dac-custom-search-results').customSearch(query);
5635 }, 1000);
5636
5637 // Start search shortcut (/)
5638 $('body').keyup(function(event) {
5639 if (event.which === 191 && $(event.target).is(':not(:input)')) {
5640 this.searchInput.focus();
5641 }
5642 }.bind(this));
5643
5644 $(window).on('popstate', this.onPopState.bind(this));
5645 $(window).hashchange(this.onHashChange.bind(this));
5646 this.onHashChange();
5647 };
5648
5649 Search.prototype.checkRedirectToIndex = function() {
5650 var query = this.getUrlQuery();
5651 var target = window.getLangTarget();
5652 var prefix = (target !== 'en') ? '/intl/' + target : '';
5653 var pathname = location.pathname.slice(prefix.length);
5654 if (query != null && pathname !== '/index.html') {
5655 location.href = prefix + '/index.html' + location.hash;
5656 return true;
5657 }
5658 };
5659
5660 Search.prototype.handleKeyboardShortcut = function(event) {
5661 // Close (esc)
5662 if (event.which === 27) {
5663 this.searchClose.trigger('click');
5664 event.preventDefault();
5665 }
5666
5667 // Previous result (up arrow)
5668 if (event.which === 38) {
5669 this.previousResult();
5670 event.preventDefault();
5671 }
5672
5673 // Next result (down arrow)
5674 if (event.which === 40) {
5675 this.nextResult();
5676 event.preventDefault();
5677 }
5678
5679 // Navigate to result (enter)
5680 if (event.which === 13) {
5681 this.navigateToResult();
5682 event.preventDefault();
5683 }
5684 };
5685
5686 Search.prototype.goToResult = function(relativeIndex) {
5687 var links = this.searchResults.find('a').filter(':visible');
5688 var selectedLink = this.searchResults.find('.dac-selected');
5689
5690 if (selectedLink.length) {
5691 var found = $.inArray(selectedLink[0], links);
5692
5693 selectedLink.removeClass('dac-selected');
5694 links.eq(found + relativeIndex).addClass('dac-selected');
5695 return true;
5696 } else {
5697 if (relativeIndex > 0) {
5698 links.first().addClass('dac-selected');
5699 }
5700 }
5701 };
5702
5703 Search.prototype.previousResult = function() {
5704 this.goToResult(-1);
5705 };
5706
5707 Search.prototype.nextResult = function() {
5708 this.goToResult(1);
5709 };
5710
5711 Search.prototype.navigateToResult = function() {
5712 var query = this.getQuery();
5713 var selectedLink = this.searchResults.find('.dac-selected');
5714
5715 if (selectedLink.length) {
5716 selectedLink[0].click();
5717 } else {
5718 this.searchHistory.push(query);
5719 this.addQueryToUrl(query);
5720
5721 var isMobileOrTablet = typeof window.orientation !== 'undefined';
5722
5723 if (isMobileOrTablet) {
5724 this.searchInput.blur();
5725 }
5726 }
5727 };
5728
5729 Search.prototype.onHashChange = function() {
5730 var query = this.getUrlQuery();
5731 if (query != null && query !== this.getQuery()) {
5732 this.searchInput.val(query);
5733 this.onSearchChanged();
5734 }
5735 };
5736
5737 Search.prototype.clear = function() {
5738 this.searchInput.val('');
5739 window.location.hash = '';
5740 this.onSearchChanged();
5741 this.searchInput.focus();
5742 };
5743
5744 Search.prototype.close = function() {
5745 this.removeQueryFromUrl();
5746 this.searchInput.blur();
5747 this.hideOverlay();
5748 };
5749
5750 Search.prototype.getUrlQuery = function() {
5751 var queryMatch = location.hash.match(/q=(.*)&?/);
5752 return queryMatch && queryMatch[1] && decodeURI(queryMatch[1]);
5753 };
5754
5755 Search.prototype.getQuery = function() {
5756 return this.searchInput.val().replace(/(^ +)|( +$)/g, '');
5757 };
5758
5759 Search.prototype.onSearchChanged = function() {
5760 var query = this.getQuery();
5761
5762 this.showOverlay();
5763 this.render(query);
5764 };
5765
5766 Search.prototype.render = function(query) {
5767 if (this.lastQuery === query) { return; }
5768
5769 if (query.length < 2) {
5770 query = '';
5771 }
5772
5773 this.lastQuery = query;
5774 this.searchResultsFor.text(query);
5775 this.customSearch(query);
5776 var metadataResults = metadata.search(query);
5777 this.searchResultsResources.dacSearchRenderResources(metadataResults.resources, query);
5778 this.searchResultsReference.dacSearchRenderReferences(metadataResults, query);
5779 var hasHero = this.searchResultsHero.dacSearchRenderHero(metadataResults.resources, query);
5780 var hasQuery = !!query;
5781
5782 this.searchResultsReference.toggle(!hasHero);
5783 this.searchResultsContent.toggle(hasQuery);
5784 this.searchResultsHistory.toggle(!hasQuery);
5785 this.addQueryToUrl(query);
5786 this.pushState();
5787 };
5788
5789 Search.prototype.addQueryToUrl = function(query) {
5790 var hash = 'q=' + encodeURI(query);
5791
5792 if (query) {
5793 if (window.history.replaceState) {
5794 window.history.replaceState(null, '', '#' + hash);
5795 } else {
5796 window.location.hash = hash;
5797 }
5798 }
5799 };
5800
5801 Search.prototype.onPopState = function() {
5802 if (!this.getUrlQuery()) {
5803 this.hideOverlay();
5804 this.searchHeader.unsetActiveState();
5805 }
5806 };
5807
5808 Search.prototype.removeQueryFromUrl = function() {
5809 window.location.hash = '';
5810 };
5811
5812 Search.prototype.pushState = function() {
5813 if (window.history.pushState && !this.lastQuery.length) {
5814 window.history.pushState(null, '');
5815 }
5816 };
5817
5818 Search.prototype.showOverlay = function() {
5819 this.body.addClass('dac-modal-open dac-search-open');
5820 };
5821
5822 Search.prototype.hideOverlay = function() {
5823 this.body.removeClass('dac-modal-open dac-search-open');
5824 };
5825
5826 $(document).on('ready.aranja', function() {
5827 var search = new Search();
5828 search.init();
5829 });
5830})(jQuery, metadata);
5831
5832window.dacStore = (function(window) {
5833 /**
5834 * Creates a new persistent store.
5835 * If localStorage is unavailable, the items are stored in memory.
5836 *
5837 * @constructor
5838 * @param {string} name The name of the store
5839 * @param {number} maxSize The maximum number of items the store can hold.
5840 */
5841 var Store = function(name, maxSize) {
5842 var content = [];
5843
5844 var hasLocalStorage = !!window.localStorage;
5845
5846 if (hasLocalStorage) {
5847 try {
5848 content = JSON.parse(window.localStorage.getItem(name) || []);
5849 } catch (e) {
5850 // Store contains invalid data
5851 window.localStorage.removeItem(name);
5852 }
5853 }
5854
5855 function push(item) {
5856 if (content[0] === item) {
5857 return;
5858 }
5859
5860 content.unshift(item);
5861
5862 if (maxSize) {
5863 content.splice(maxSize, content.length);
5864 }
5865
5866 if (hasLocalStorage) {
5867 window.localStorage.setItem(name, JSON.stringify(content));
5868 }
5869 }
5870
5871 function all() {
5872 // Return a copy
5873 return content.slice();
5874 }
5875
5876 return {
5877 push: push,
5878 all: all
5879 };
5880 };
5881
5882 var stores = {
5883 'search-history': new Store('search-history', 3)
5884 };
5885
5886 /**
5887 * Get a named persistent store.
5888 * @param {string} name
5889 * @return {Store}
5890 */
5891 return function getStore(name) {
5892 return stores[name];
5893 };
5894})(window);
5895
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005896(function($) {
5897 'use strict';
5898
5899 /**
5900 * A component that swaps two dynamic height views with an animation.
5901 * Listens for the following events:
5902 * * swap-content: triggers SwapContent.swap_()
5903 * * swap-reset: triggers SwapContent.reset()
5904 * @param el
5905 * @param options
5906 * @constructor
5907 */
5908 function SwapContent(el, options) {
5909 this.el = $(el);
5910 this.options = $.extend({}, SwapContent.DEFAULTS_, options);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005911 this.options.dynamic = this.options.dynamic === 'true';
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005912 this.containers = this.el.find(this.options.container);
5913 this.initiallyActive = this.containers.children('.' + this.options.activeClass).eq(0);
5914 this.el.on('swap-content', this.swap.bind(this));
5915 this.el.on('swap-reset', this.reset.bind(this));
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005916 this.el.find(this.options.swapButton).on('click', this.swap.bind(this));
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005917 }
5918
5919 /**
5920 * SwapContent's default settings.
5921 * @type {{activeClass: string, container: string, transitionSpeed: number}}
5922 * @private
5923 */
5924 SwapContent.DEFAULTS_ = {
5925 activeClass: 'dac-active',
5926 container: '[data-swap-container]',
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005927 dynamic: 'true',
5928 swapButton: '[data-swap-button]',
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005929 transitionSpeed: 500
5930 };
5931
5932 /**
5933 * Returns container's visible height.
5934 * @param container
5935 * @returns {number}
5936 */
5937 SwapContent.prototype.currentHeight = function(container) {
5938 return container.children('.' + this.options.activeClass).outerHeight();
5939 };
5940
5941 /**
5942 * Reset to show initial content
5943 */
5944 SwapContent.prototype.reset = function() {
5945 if (!this.initiallyActive.hasClass(this.initiallyActive)) {
5946 this.containers.children().toggleClass(this.options.activeClass);
5947 }
5948 };
5949
5950 /**
5951 * Complete the swap.
5952 */
5953 SwapContent.prototype.complete = function() {
5954 this.containers.height('auto');
5955 this.containers.trigger('swap-complete');
5956 };
5957
5958 /**
5959 * Perform the swap of content.
5960 */
5961 SwapContent.prototype.swap = function() {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005962 this.containers.each(function(index, container) {
5963 container = $(container);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005964
5965 if (!this.options.dynamic) {
5966 container.children().toggleClass(this.options.activeClass);
5967 this.complete.bind(this);
5968 return;
5969 }
5970
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005971 container.height(this.currentHeight(container)).children().toggleClass(this.options.activeClass);
5972 container.animate({height: this.currentHeight(container)}, this.options.transitionSpeed,
5973 this.complete.bind(this));
5974 }.bind(this));
5975 };
5976
5977 /**
5978 * jQuery plugin
5979 * @param {object} options - Override default options.
5980 */
5981 $.fn.dacSwapContent = function(options) {
5982 return this.each(function() {
5983 new SwapContent(this, options);
5984 });
5985 };
5986
5987 /**
5988 * Data Attribute API
5989 */
5990 $(document).on('ready.aranja', function() {
5991 $('[data-swap]').each(function() {
5992 $(this).dacSwapContent($(this).data());
5993 });
5994 });
5995})(jQuery);
5996
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005997/* Tabs */
5998(function($) {
5999 'use strict';
6000
6001 /**
6002 * @param {HTMLElement} el - The DOM element.
6003 * @param {Object} options
6004 * @constructor
6005 */
6006 function Tabs(el, options) {
6007 this.el = $(el);
6008 this.options = $.extend({}, Tabs.DEFAULTS_, options);
6009 this.init();
6010 }
6011
6012 Tabs.DEFAULTS_ = {
6013 activeClass: 'dac-active',
6014 viewDataAttr: 'tab-view',
6015 itemDataAttr: 'tab-item'
6016 };
6017
6018 Tabs.prototype.init = function() {
6019 var itemDataAttribute = '[data-' + this.options.itemDataAttr + ']';
6020 this.tabEl_ = this.el.find(itemDataAttribute);
6021 this.tabViewEl_ = this.el.find('[data-' + this.options.viewDataAttr + ']');
6022 this.el.on('click.dac-tabs', itemDataAttribute, this.changeTabs.bind(this));
6023 };
6024
6025 Tabs.prototype.changeTabs = function(event) {
6026 var current = $(event.currentTarget);
6027 var index = current.index();
6028
6029 if (current.hasClass(this.options.activeClass)) {
6030 current.add(this.tabViewEl_.eq(index)).removeClass(this.options.activeClass);
6031 } else {
6032 this.tabEl_.add(this.tabViewEl_).removeClass(this.options.activeClass);
6033 current.add(this.tabViewEl_.eq(index)).addClass(this.options.activeClass);
6034 }
6035 };
6036
6037 /**
6038 * jQuery plugin
6039 */
6040 $.fn.dacTabs = function() {
6041 return this.each(function() {
6042 var el = $(this);
6043 new Tabs(el, el.data());
6044 });
6045 };
6046
6047 /**
6048 * Data Attribute API
6049 */
6050 $(function() {
6051 $('[data-tabs]').dacTabs();
6052 });
6053})(jQuery);
6054
6055/* Toast Component */
6056(function($) {
6057 'use strict';
6058 /**
6059 * @constant
6060 * @type {String}
6061 */
6062 var LOCAL_STORAGE_KEY = 'toast-closed-index';
6063
6064 /**
6065 * Dictionary from local storage.
6066 */
6067 var toastDictionary = localStorage.getItem(LOCAL_STORAGE_KEY);
6068 toastDictionary = toastDictionary ? JSON.parse(toastDictionary) : {};
6069
6070 /**
6071 * Variable used for caching the body.
6072 */
6073 var bodyCached;
6074
6075 /**
6076 * @param {HTMLElement} el - The DOM element.
6077 * @param {Object} options
6078 * @constructor
6079 */
6080 function Toast(el, options) {
6081 this.el = $(el);
6082 this.options = $.extend({}, Toast.DEFAULTS_, options);
6083 this.init();
6084 }
6085
6086 Toast.DEFAULTS_ = {
6087 closeBtnClass: 'dac-toast-close-btn',
6088 closeDuration: 200,
6089 visibleClass: 'dac-visible',
6090 wrapClass: 'dac-toast-wrap'
6091 };
6092
6093 /**
6094 * Generate a close button.
6095 * @returns {*|HTMLElement}
6096 */
6097 Toast.prototype.closeBtn = function() {
6098 this.closeBtnEl = this.closeBtnEl || $('<button class="' + this.options.closeBtnClass + '">' +
6099 '<i class="dac-sprite dac-close-black"></i>' +
6100 '</button>');
6101 return this.closeBtnEl;
6102 };
6103
6104 /**
6105 * Initialize a new toast element
6106 */
6107 Toast.prototype.init = function() {
6108 this.hash = this.el.text().replace(/[\s\n\t]/g, '').split('').slice(0, 128).join('');
6109
6110 if (toastDictionary[this.hash]) {
6111 return;
6112 }
6113
6114 this.closeBtn().on('click', this.onClickHandler.bind(this));
6115 this.el.find('.' + this.options.wrapClass).append(this.closeBtn());
6116 this.el.addClass(this.options.visibleClass);
6117 this.dynamicPadding(this.el.outerHeight());
6118 };
6119
6120 /**
6121 * Add padding to make sure all page is visible.
6122 */
6123 Toast.prototype.dynamicPadding = function(val) {
6124 var currentPadding = parseInt(bodyCached.css('padding-bottom') || 0);
6125 bodyCached.css('padding-bottom', val + currentPadding);
6126 };
6127
6128 /**
6129 * Remove a toast from the DOM
6130 */
6131 Toast.prototype.remove = function() {
6132 this.dynamicPadding(-this.el.outerHeight());
6133 this.el.remove();
6134 };
6135
6136 /**
6137 * Handle removal of the toast.
6138 */
6139 Toast.prototype.onClickHandler = function() {
6140 // Only fadeout toasts from top of stack. Others are removed immediately.
6141 var duration = this.el.index() === 0 ? this.options.closeDuration : 0;
6142 this.el.fadeOut(duration, this.remove.bind(this));
6143
6144 // Save closed state.
6145 toastDictionary[this.hash] = 1;
6146 localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(toastDictionary));
6147 };
6148
6149 /**
6150 * jQuery plugin
6151 * @param {object} options - Override default options.
6152 */
6153 $.fn.dacToast = function() {
6154 return this.each(function() {
6155 var el = $(this);
6156 new Toast(el, el.data());
6157 });
6158 };
6159
6160 /**
6161 * Data Attribute API
6162 */
6163 $(function() {
6164 bodyCached = $('#body-content');
6165 $('[data-toast]').dacToast();
6166 });
6167})(jQuery);
6168
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006169(function($) {
6170 function Toggle(el) {
6171 $(el).on('click.dac.togglesection', this.toggle);
6172 }
6173
6174 Toggle.prototype.toggle = function() {
6175 var $this = $(this);
6176
6177 var $parent = getParent($this);
6178 var isExpanded = $parent.hasClass('is-expanded');
6179
6180 transitionMaxHeight($parent.find('.dac-toggle-content'), !isExpanded);
6181 $parent.toggleClass('is-expanded');
6182
6183 return false;
6184 };
6185
6186 function getParent($this) {
6187 var selector = $this.attr('data-target');
6188
6189 if (!selector) {
6190 selector = $this.attr('href');
6191 selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '');
6192 }
6193
6194 var $parent = selector && $(selector);
6195
6196 $parent = $parent && $parent.length ? $parent : $this.closest('.dac-toggle');
6197
6198 return $parent.length ? $parent : $this.parent();
6199 }
6200
6201 /**
6202 * Runs a transition of max-height along with responsive styles which hide or expand the element.
6203 * @param $el
6204 * @param visible
6205 */
6206 function transitionMaxHeight($el, visible) {
6207 var contentHeight = $el.prop('scrollHeight');
6208 var targetHeight = visible ? contentHeight : 0;
6209 var duration = $el.transitionDuration();
6210
6211 // If we're hiding, first set the maxHeight we're transitioning from.
6212 if (!visible) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08006213 $el.css({
6214 transitionDuration: '0s',
6215 maxHeight: contentHeight + 'px'
6216 })
6217 .resolveStyles()
6218 .css('transitionDuration', '');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006219 }
6220
6221 // Transition to new state
6222 $el.css('maxHeight', targetHeight);
6223
6224 // Reset maxHeight to css value after transition.
6225 setTimeout(function() {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08006226 $el.css({
6227 transitionDuration: '0s',
6228 maxHeight: ''
6229 })
6230 .resolveStyles()
6231 .css('transitionDuration', '');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006232 }, duration);
6233 }
6234
6235 // Utility to get the transition duration for the element.
6236 $.fn.transitionDuration = function() {
6237 var d = $(this).css('transitionDuration') || '0s';
6238
6239 return +(parseFloat(d) * (/ms/.test(d) ? 1 : 1000)).toFixed(0);
6240 };
6241
6242 // jQuery plugin
6243 $.fn.toggleSection = function(option) {
6244 return this.each(function() {
6245 var $this = $(this);
6246 var data = $this.data('dac.togglesection');
6247 if (!data) {$this.data('dac.togglesection', (data = new Toggle(this)));}
6248 if (typeof option === 'string') {data[option].call($this);}
6249 });
6250 };
6251
6252 // Data api
6253 $(document)
6254 .on('click.toggle', '[data-toggle="section"]', Toggle.prototype.toggle);
6255})(jQuery);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08006256
6257(function(window) {
6258 /**
6259 * Media query breakpoints. Should match CSS.
6260 */
6261 var BREAKPOINTS = {
6262 mobile: [0, 719],
6263 tablet: [720, 959],
6264 desktop: [960, 9999]
6265 };
6266
6267 /**
6268 * Fisher-Yates Shuffle (Knuth shuffle).
6269 * @param {Array} input
6270 * @returns {Array} shuffled array.
6271 */
6272 function shuffle(input) {
6273 for (var i = input.length; i >= 0; i--) {
6274 var randomIndex = Math.floor(Math.random() * (i + 1));
6275 var randomItem = input[randomIndex];
6276 input[randomIndex] = input[i];
6277 input[i] = randomItem;
6278 }
6279
6280 return input;
6281 }
6282
6283 /**
6284 * Matches media breakpoints like in CSS.
6285 * @param {string} form of either mobile, tablet or desktop.
6286 */
6287 function matchesMedia(form) {
6288 var breakpoint = BREAKPOINTS[form];
6289 return window.innerWidth >= breakpoint[0] && window.innerWidth <= breakpoint[1];
6290 }
6291
6292 window.util = {
6293 shuffle: shuffle,
6294 matchesMedia: matchesMedia
6295 };
6296})(window);
6297
6298(function($, window) {
6299 'use strict';
6300
6301 var YouTubePlayer = (function() {
6302 var player;
6303
6304 function VideoPlayer() {
6305 this.mPlayerPaused = false;
6306 this.doneSetup = false;
6307 }
6308
6309 VideoPlayer.prototype.setup = function() {
6310 // loads the IFrame Player API code asynchronously.
6311 $.getScript('https://www.youtube.com/iframe_api');
6312
6313 // Add the shadowbox HTML to the body
6314 $('body').prepend(
6315'<div id="video-player" class="Video">' +
6316 '<div id="video-overlay" class="Video-overlay" />' +
6317 '<div class="Video-container">' +
6318 '<div class="Video-frame">' +
6319 '<span class="Video-loading">Loading&hellip;</span>' +
6320 '<div id="youTubePlayer"></div>' +
6321 '</div>' +
6322 '<div class="Video-controls">' +
6323 '<button id="picture-in-picture" class="Video-button Video-button--picture-in-picture">' +
6324 '<button id="close-video" class="Video-button Video-button--close" />' +
6325 '</div>' +
6326 '</div>' +
6327'</div>');
6328
6329 this.videoPlayer = $('#video-player');
6330
6331 var pictureInPictureButton = this.videoPlayer.find('#picture-in-picture');
6332 pictureInPictureButton.on('click.aranja', this.toggleMinimizeVideo.bind(this));
6333
6334 var videoOverlay = this.videoPlayer.find('#video-overlay');
6335 var closeButton = this.videoPlayer.find('#close-video');
6336 var closeVideo = this.closeVideo.bind(this);
6337 videoOverlay.on('click.aranja', closeVideo);
6338 closeButton.on('click.aranja', closeVideo);
6339
6340 this.doneSetup = true;
6341 };
6342
6343 VideoPlayer.prototype.startYouTubePlayer = function(videoId) {
6344 this.videoPlayer.show();
6345
6346 if (!this.isLoaded) {
6347 this.queueVideo = videoId;
6348 return;
6349 }
6350
6351 this.mPlayerPaused = false;
6352 // check if we've already created this player
6353 if (!this.youTubePlayer) {
6354 // check if there's a start time specified
6355 var idAndHash = videoId.split('#');
6356 var startTime = 0;
6357 if (idAndHash.length > 1) {
6358 startTime = idAndHash[1].split('t=')[1] !== undefined ? idAndHash[1].split('t=')[1] : 0;
6359 }
6360 // enable localized player
6361 var lang = getLangPref();
6362 var captionsOn = lang === 'en' ? 0 : 1;
6363
6364 this.youTubePlayer = new YT.Player('youTubePlayer', {
6365 height: 720,
6366 width: 1280,
6367 videoId: idAndHash[0],
6368 // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
6369 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
6370 // jscs:enable
6371 events: {
6372 'onReady': this.onPlayerReady.bind(this),
6373 'onStateChange': this.onPlayerStateChange.bind(this)
6374 }
6375 });
6376 } else {
6377 // if a video different from the one already playing was requested, cue it up
6378 if (videoId !== this.getVideoId()) {
6379 this.youTubePlayer.cueVideoById(videoId);
6380 }
6381 this.youTubePlayer.playVideo();
6382 }
6383 };
6384
6385 VideoPlayer.prototype.onPlayerReady = function(event) {
6386 if (!isMobile) {
6387 event.target.playVideo();
6388 this.mPlayerPaused = false;
6389 }
6390 };
6391
6392 VideoPlayer.prototype.toggleMinimizeVideo = function(event) {
6393 event.stopPropagation();
6394 this.videoPlayer.toggleClass('Video--picture-in-picture');
6395 };
6396
6397 VideoPlayer.prototype.closeVideo = function() {
6398 try {
6399 this.youTubePlayer.pauseVideo();
6400 } catch (e) {
6401 }
6402 this.videoPlayer.fadeOut(200, function() {
6403 this.videoPlayer.removeClass('Video--picture-in-picture');
6404 }.bind(this));
6405 };
6406
6407 VideoPlayer.prototype.getVideoId = function() {
6408 // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
6409 return this.youTubePlayer && this.youTubePlayer.getVideoData().video_id;
6410 // jscs:enable
6411 };
6412
6413 /* Track youtube playback for analytics */
6414 VideoPlayer.prototype.onPlayerStateChange = function(event) {
6415 var videoId = this.getVideoId();
6416 var currentTime = this.youTubePlayer && this.youTubePlayer.getCurrentTime();
6417
6418 // Video starts, send the video ID
6419 if (event.data === YT.PlayerState.PLAYING) {
6420 if (this.mPlayerPaused) {
6421 ga('send', 'event', 'Videos', 'Resume', videoId);
6422 } else {
6423 // track the start playing event so we know from which page the video was selected
6424 ga('send', 'event', 'Videos', 'Start: ' + videoId, 'on: ' + document.location.href);
6425 }
6426 this.mPlayerPaused = false;
6427 }
6428
6429 // Video paused, send video ID and video elapsed time
6430 if (event.data === YT.PlayerState.PAUSED) {
6431 ga('send', 'event', 'Videos', 'Paused', videoId, currentTime);
6432 this.mPlayerPaused = true;
6433 }
6434
6435 // Video finished, send video ID and video elapsed time
6436 if (event.data === YT.PlayerState.ENDED) {
6437 ga('send', 'event', 'Videos', 'Finished', videoId, currentTime);
6438 this.mPlayerPaused = true;
6439 }
6440 };
6441
6442 return {
6443 getPlayer: function() {
6444 if (!player) {
6445 player = new VideoPlayer();
6446 }
6447
6448 return player;
6449 }
6450 };
6451 })();
6452
6453 var videoPlayer = YouTubePlayer.getPlayer();
6454
6455 window.onYouTubeIframeAPIReady = function() {
6456 videoPlayer.isLoaded = true;
6457
6458 if (videoPlayer.queueVideo) {
6459 videoPlayer.startYouTubePlayer(videoPlayer.queueVideo);
6460 }
6461 };
6462
6463 function wrapLinkInPlayer(e) {
6464 e.preventDefault();
6465
6466 if (!videoPlayer.doneSetup) {
6467 videoPlayer.setup();
6468 }
6469
6470 var videoIdMatches = $(e.currentTarget).attr('href').match(/(?:youtu.be\/|v=)([^&]*)/);
6471 var videoId = videoIdMatches && videoIdMatches[1];
6472
6473 if (videoId) {
6474 videoPlayer.startYouTubePlayer(videoId);
6475 }
6476 }
6477
6478 $(document).on('click.video', 'a[href*="youtube.com/watch"], a[href*="youtu.be"]', wrapLinkInPlayer);
6479})(jQuery, window);
6480
6481/**
6482 * Wide table
6483 *
6484 * Wraps tables in a scrollable area so you can read them on mobile.
6485 */
6486(function($) {
6487 function initWideTable() {
6488 $('table.jd-sumtable').each(function(i, table) {
6489 $(table).wrap('<div class="dac-expand wide-table">');
6490 });
6491 }
6492
6493 $(function() {
6494 initWideTable();
6495 });
6496})(jQuery);