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