blob: 21dec54758faed3683ba3b5537e598a559480efe [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.
4169 var langAttr = window.getLangPref();
4170 console.log("langAttr is " + langAttr);
4171 views.slice(0, 2).addClass('dac-active');
4172 var selectedNav = views.eq(2).find('.selected').after(forwardLink);
4173 //select the localized text if available else the selectedNav value
4174 if (langAttr!='en') {
4175 $('.dac-nav-back-title').text(selectedNav.attr(langAttr + '-lang'));
4176 } else {
4177 $('.dac-nav-back-title').text(selectedNav.text());
4178 }
4179 }
4180
4181 // Navigation should animate.
4182 setTimeout(function() {
4183 views.removeClass('dac-no-anim');
4184 }, 10);
4185 }
4186
4187 function swap_(event) {
4188 event.preventDefault();
4189 $(event.currentTarget).trigger('swap-content');
4190 }
4191
4192 /**
4193 * jQuery plugin
4194 */
4195 $.fn.dacNav = function() {
4196 return this.each(function() {
4197 new Nav($(this));
4198 });
4199 };
4200})(jQuery);
4201
4202/* global NAVTREE_DATA */
4203(function($) {
4204 /**
4205 * Build the reference navigation with namespace dropdowns.
4206 * @param {jQuery} el - The DOM element.
4207 */
4208 function buildReferenceNav(el) {
4209 var namespaceList = el.find('[data-reference-namespaces]');
4210 var resources = el.find('[data-reference-resources]');
4211 var selected = namespaceList.find('.selected');
4212
4213 // Links should be toggleable.
4214 namespaceList.find('a').addClass('dac-reference-nav-toggle dac-closed');
4215
4216 // Load in all resources
4217 $.getScript('/navtree_data.js', function(data, textStatus, xhr) {
4218 if (xhr.status === 200) {
4219 namespaceList.on('click', 'a.dac-reference-nav-toggle', toggleResourcesHandler);
4220 }
4221 });
4222
4223 // No setup required if no resources are present
4224 if (!resources.length) {
4225 return;
4226 }
4227
4228 // The resources should be a part of selected namespace.
4229 var overview = addResourcesToView(resources, selected);
4230
4231 // Currently viewing Overview
4232 if (location.pathname === overview.attr('href')) {
4233 overview.parent().addClass('selected');
4234 }
4235
4236 // Open currently selected resource
4237 var listsToOpen = selected.children().eq(1);
4238 listsToOpen = listsToOpen.add(listsToOpen.find('.selected').parent()).show();
4239
4240 // Mark dropdowns as open
4241 listsToOpen.prev().removeClass('dac-closed');
4242
4243 // Scroll into view
4244 namespaceList.scrollIntoView(selected);
4245 }
4246
4247 /**
4248 * Handles the toggling of resources.
4249 * @param {Event} event
4250 */
4251 function toggleResourcesHandler(event) {
4252 event.preventDefault();
4253 var el = $(this);
4254
4255 // If resources for given namespace is not present, fetch correct data.
4256 if (this.tagName === 'A' && !this.hasResources) {
4257 addResourcesToView(buildResourcesViewForData(getDataForNamespace(el.text())), el.parent());
4258 }
4259
4260 el.toggleClass('dac-closed').next().slideToggle(200);
4261 }
4262
4263 /**
4264 * @param {String} namespace
4265 * @returns {Array} namespace data
4266 */
4267 function getDataForNamespace(namespace) {
4268 var namespaceData = NAVTREE_DATA.filter(function(data) {
4269 return data[0] === namespace;
4270 });
4271
4272 return namespaceData.length ? namespaceData[0][2] : [];
4273 }
4274
4275 /**
4276 * Build a list item for a resource
4277 * @param {Array} resource
4278 * @returns {String}
4279 */
4280 function buildResourceItem(resource) {
4281 return '<li class="api apilevel-' + resource[3] + '"><a href="/' + resource[1] + '">' + resource[0] + '</a></li>';
4282 }
4283
4284 /**
4285 * Build resources list items.
4286 * @param {Array} resources
4287 * @returns {String}
4288 */
4289 function buildResourceList(resources) {
4290 return '<li><h2>' + resources[0] + '</h2><ul>' + resources[2].map(buildResourceItem).join('') + '</ul>';
4291 }
4292
4293 /**
4294 * Build a resources view
4295 * @param {Array} data
4296 * @returns {jQuery} resources in an unordered list.
4297 */
4298 function buildResourcesViewForData(data) {
4299 return $('<ul>' + data.map(buildResourceList).join('') + '</ul>');
4300 }
4301
4302 /**
4303 * Add resources to a containing view.
4304 * @param {jQuery} resources
4305 * @param {jQuery} view
4306 * @returns {jQuery} the overview link.
4307 */
4308 function addResourcesToView(resources, view) {
4309 var namespace = view.children().eq(0);
4310 var overview = $('<a href="' + namespace.attr('href') + '">Overview</a>');
4311
4312 // Mark namespace with content;
4313 namespace[0].hasResources = true;
4314
4315 // Add correct classes / event listeners to resources.
4316 resources.prepend($('<li>').html(overview))
4317 .find('a')
4318 .addClass('dac-reference-nav-resource')
4319 .end()
4320 .find('h2')
4321 .addClass('dac-reference-nav-toggle dac-closed')
4322 .on('click', toggleResourcesHandler)
4323 .end()
4324 .add(resources.find('ul'))
4325 .addClass('dac-reference-nav-resources')
4326 .end()
4327 .appendTo(view);
4328
4329 return overview;
4330 }
4331
4332 /**
4333 * jQuery plugin
4334 */
4335 $.fn.dacReferenceNav = function() {
4336 return this.each(function() {
4337 buildReferenceNav($(this));
4338 });
4339 };
4340})(jQuery);
4341
4342/** Scroll a container to make a target element visible
4343 This is called when the page finished loading. */
4344$.fn.scrollIntoView = function(target) {
4345 if ('string' === typeof target) {
4346 target = this.find(target);
4347 }
4348 if (this.is(':visible')) {
4349 if (target.length == 0) {
4350 // If no selected item found, exit
4351 return;
4352 }
4353
4354 // get the target element's offset from its container nav by measuring the element's offset
4355 // relative to the document then subtract the container nav's offset relative to the document
4356 var targetOffset = target.offset().top - this.offset().top;
4357 var containerHeight = this.height();
4358 if (targetOffset > containerHeight * .8) { // multiply nav height by .8 so we move up the item
4359 // if it's more than 80% down the nav
4360 // scroll the item up by an amount equal to 80% the container height
4361 this.scrollTop(targetOffset - (containerHeight * .8));
4362 }
4363 }
4364};
4365
4366(function($) {
4367 $.fn.dacCurrentPage = function() {
4368 // Highlight the header tabs...
4369 // highlight Design tab
4370 var baseurl = getBaseUri(window.location.pathname);
4371 var urlSegments = baseurl.split('/');
4372 var navEl = this;
4373 var body = $('body');
4374 var subNavEl = navEl.find('.dac-nav-secondary');
4375 var parentNavEl;
4376 var selected;
4377 // In NDK docs, highlight appropriate sub-nav
4378 if (body.hasClass('ndk')) {
4379 if (body.hasClass('guide')) {
4380 selected = navEl.find('> li.guides > a').addClass('selected');
4381 } else if (body.hasClass('reference')) {
4382 selected = navEl.find('> li.reference > a').addClass('selected');
4383 } else if (body.hasClass('samples')) {
4384 selected = navEl.find('> li.samples > a').addClass('selected');
4385 } else if (body.hasClass('downloads')) {
4386 selected = navEl.find('> li.downloads > a').addClass('selected');
4387 }
4388 } else if (body.hasClass('design')) {
4389 selected = navEl.find('> li.design > a').addClass('selected');
4390 // highlight Home nav
4391 } else if (body.hasClass('about')) {
4392 parentNavEl = navEl.find('> li.home > a');
4393 parentNavEl.addClass('has-subnav');
4394 // In Home docs, also highlight appropriate sub-nav
4395 if (urlSegments[1] === 'wear' || urlSegments[1] === 'tv' ||
4396 urlSegments[1] === 'auto') {
4397 selected = subNavEl.find('li.' + urlSegments[1] + ' > a').addClass('selected');
4398 } else if (urlSegments[1] === 'about') {
4399 selected = subNavEl.find('li.versions > a').addClass('selected');
4400 } else {
4401 selected = parentNavEl.removeClass('has-subnav').addClass('selected');
4402 }
4403 // highlight Develop nav
4404 } else if (body.hasClass('develop') || body.hasClass('google')) {
4405 parentNavEl = navEl.find('> li.develop > a');
4406 parentNavEl.addClass('has-subnav');
4407 // In Develop docs, also highlight appropriate sub-nav
4408 if (urlSegments[1] === 'training') {
4409 selected = subNavEl.find('li.training > a').addClass('selected');
4410 } else if (urlSegments[1] === 'guide') {
4411 selected = subNavEl.find('li.guide > a').addClass('selected');
4412 } else if (urlSegments[1] === 'reference') {
4413 // If the root is reference, but page is also part of Google Services, select Google
4414 if (body.hasClass('google')) {
4415 selected = subNavEl.find('li.google > a').addClass('selected');
4416 } else {
4417 selected = subNavEl.find('li.reference > a').addClass('selected');
4418 }
4419 } else if ((urlSegments[1] === 'tools') || (urlSegments[1] === 'sdk')) {
4420 selected = subNavEl.find('li.tools > a').addClass('selected');
4421 } else if (body.hasClass('google')) {
4422 selected = subNavEl.find('li.google > a').addClass('selected');
4423 } else if (body.hasClass('samples')) {
4424 selected = subNavEl.find('li.samples > a').addClass('selected');
4425 } else {
4426 selected = parentNavEl.removeClass('has-subnav').addClass('selected');
4427 }
4428 // highlight Distribute nav
4429 } else if (body.hasClass('distribute')) {
4430 parentNavEl = navEl.find('> li.distribute > a');
4431 parentNavEl.addClass('has-subnav');
4432 // In Distribute docs, also highlight appropriate sub-nav
4433 if (urlSegments[2] === 'users') {
4434 selected = subNavEl.find('li.users > a').addClass('selected');
4435 } else if (urlSegments[2] === 'engage') {
4436 selected = subNavEl.find('li.engage > a').addClass('selected');
4437 } else if (urlSegments[2] === 'monetize') {
4438 selected = subNavEl.find('li.monetize > a').addClass('selected');
4439 } else if (urlSegments[2] === 'analyze') {
4440 selected = subNavEl.find('li.analyze > a').addClass('selected');
4441 } else if (urlSegments[2] === 'tools') {
4442 selected = subNavEl.find('li.disttools > a').addClass('selected');
4443 } else if (urlSegments[2] === 'stories') {
4444 selected = subNavEl.find('li.stories > a').addClass('selected');
4445 } else if (urlSegments[2] === 'essentials') {
4446 selected = subNavEl.find('li.essentials > a').addClass('selected');
4447 } else if (urlSegments[2] === 'googleplay') {
4448 selected = subNavEl.find('li.googleplay > a').addClass('selected');
4449 } else {
4450 selected = parentNavEl.removeClass('has-subnav').addClass('selected');
4451 }
4452 }
4453 return $(selected);
4454 };
4455})(jQuery);
4456
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004457(function($) {
4458 'use strict';
4459
4460 /**
4461 * Toggle the visabilty of the mobile navigation.
4462 * @param {HTMLElement} el - The DOM element.
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004463 * @param {Object} options
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004464 * @constructor
4465 */
4466 function ToggleNav(el, options) {
4467 this.el = $(el);
4468 this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004469 this.body = $(document.body);
4470 this.navigation_ = this.body.find(this.options.navigation);
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004471 this.el.on('click', this.clickHandler_.bind(this));
4472 }
4473
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004474 ToggleNav.BREAKPOINT_ = 980;
4475
4476 /**
4477 * Open on correct sizes
4478 */
4479 function toggleSidebarVisibility(body) {
4480 var wasClosed = ('' + localStorage.getItem('navigation-open')) === 'false';
4481
4482 if (wasClosed) {
4483 body.removeClass(ToggleNav.DEFAULTS_.activeClass);
4484 } else if (window.innerWidth >= ToggleNav.BREAKPOINT_) {
4485 body.addClass(ToggleNav.DEFAULTS_.activeClass);
4486 } else {
4487 body.removeClass(ToggleNav.DEFAULTS_.activeClass);
4488 }
4489 }
4490
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004491 /**
4492 * ToggleNav Default Settings
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004493 * @type {{body: boolean, dimmer: string, navigation: string, activeClass: string}}
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004494 * @private
4495 */
4496 ToggleNav.DEFAULTS_ = {
4497 body: true,
4498 dimmer: '.dac-nav-dimmer',
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004499 animatingClass: 'dac-nav-animating',
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004500 navigation: '[data-dac-nav]',
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004501 activeClass: 'dac-nav-open'
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004502 };
4503
4504 /**
4505 * The actual toggle logic.
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004506 * @param {Event} event
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004507 * @private
4508 */
4509 ToggleNav.prototype.clickHandler_ = function(event) {
4510 event.preventDefault();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004511 var animatingClass = this.options.animatingClass;
4512 var body = this.body;
4513
4514 body.addClass(animatingClass);
4515 body.toggleClass(this.options.activeClass);
4516
4517 setTimeout(function() {
4518 body.removeClass(animatingClass);
4519 }, this.navigation_.transitionDuration());
4520
4521 if (window.innerWidth >= ToggleNav.BREAKPOINT_) {
4522 localStorage.setItem('navigation-open', body.hasClass(this.options.activeClass));
4523 }
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004524 };
4525
4526 /**
4527 * jQuery plugin
4528 * @param {object} options - Override default options.
4529 */
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004530 $.fn.dacToggleMobileNav = function() {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004531 return this.each(function() {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004532 var el = $(this);
4533 new ToggleNav(el, el.data());
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004534 });
4535 };
4536
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004537 $.fn.dacSidebarToggle = function(body) {
4538 toggleSidebarVisibility(body);
4539 $(window).on('resize', toggleSidebarVisibility.bind(null, body));
4540 };
4541
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004542 /**
4543 * Data Attribute API
4544 */
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004545 $(function() {
4546 $('[data-dac-toggle-nav]').dacToggleMobileNav();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004547 });
4548})(jQuery);
4549
4550(function($) {
4551 'use strict';
4552
4553 /**
4554 * Submit the newsletter form to a Google Form.
4555 * @param {HTMLElement} el - The Form DOM element.
4556 * @constructor
4557 */
4558 function NewsletterForm(el) {
4559 this.el = $(el);
4560 this.form = this.el.find('form');
4561 $('<iframe/>').hide()
4562 .attr('name', 'dac-newsletter-iframe')
4563 .attr('src', '')
4564 .insertBefore(this.form);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004565 this.el.find('[data-newsletter-language]').val(window.polyglot.t('newsletter.languageVal'));
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004566 this.form.on('submit', this.submitHandler_.bind(this));
4567 }
4568
4569 /**
4570 * Milliseconds until modal has vanished after modal-close is triggered.
4571 * @type {number}
4572 * @private
4573 */
4574 NewsletterForm.CLOSE_DELAY_ = 300;
4575
4576 /**
4577 * Switch view to display form after close.
4578 * @private
4579 */
4580 NewsletterForm.prototype.closeHandler_ = function() {
4581 setTimeout(function() {
4582 this.el.trigger('swap-reset');
4583 }.bind(this), NewsletterForm.CLOSE_DELAY_);
4584 };
4585
4586 /**
4587 * Reset the modal to initial state.
4588 * @private
4589 */
4590 NewsletterForm.prototype.reset_ = function() {
4591 this.form.trigger('reset');
4592 this.el.one('modal-close', this.closeHandler_.bind(this));
4593 };
4594
4595 /**
4596 * Display a success view on submit.
4597 * @private
4598 */
4599 NewsletterForm.prototype.submitHandler_ = function() {
4600 this.el.one('swap-complete', this.reset_.bind(this));
4601 this.el.trigger('swap-content');
4602 };
4603
4604 /**
4605 * jQuery plugin
4606 * @param {object} options - Override default options.
4607 */
4608 $.fn.dacNewsletterForm = function(options) {
4609 return this.each(function() {
4610 new NewsletterForm(this, options);
4611 });
4612 };
4613
4614 /**
4615 * Data Attribute API
4616 */
4617 $(document).on('ready.aranja', function() {
4618 $('[data-newsletter]').each(function() {
4619 $(this).dacNewsletterForm();
4620 });
4621 });
4622})(jQuery);
4623
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004624/* globals METADATA, YOUTUBE_RESOURCES, BLOGGER_RESOURCES */
4625window.metadata = {};
4626
4627/**
4628 * Prepare metadata and indices for querying.
4629 */
4630window.metadata.prepare = (function() {
4631 // Helper functions.
4632 function mergeArrays() {
4633 return Array.prototype.concat.apply([], arguments);
4634 }
4635
4636 /**
4637 * Creates lookup maps for a resource index.
4638 * I.e. where MAP['some tag'][resource.id] === true when that resource has 'some tag'.
4639 * @param resourceDict
4640 * @returns {{}}
4641 */
4642 function buildResourceLookupMap(resourceDict) {
4643 var map = {};
4644 for (var key in resourceDict) {
4645 var dictForKey = {};
4646 var srcArr = resourceDict[key];
4647 for (var i = 0; i < srcArr.length; i++) {
4648 dictForKey[srcArr[i].index] = true;
4649 }
4650 map[key] = dictForKey;
4651 }
4652 return map;
4653 }
4654
4655 /**
4656 * Merges metadata maps for english and the current language into the global store.
4657 */
4658 function mergeMetadataMap(name, locale) {
4659 if (locale && locale !== 'en' && METADATA[locale]) {
4660 METADATA[name] = $.extend(METADATA.en[name], METADATA[locale][name]);
4661 } else {
4662 METADATA[name] = METADATA.en[name];
4663 }
4664 }
4665
4666 /**
4667 * Index all resources by type, url, tag and category.
4668 * @param resources
4669 */
4670 function createIndices(resources) {
4671 // URL, type, tag and category lookups
4672 var byType = METADATA.byType = {};
4673 var byUrl = METADATA.byUrl = {};
4674 var byTag = METADATA.byTag = {};
4675 var byCategory = METADATA.byCategory = {};
4676
4677 for (var i = 0; i < resources.length; i++) {
4678 var res = resources[i];
4679
4680 // Store index.
4681 res.index = i;
4682
4683 // Index by type.
4684 var type = res.type;
4685 if (type) {
4686 byType[type] = byType[type] || [];
4687 byType[type].push(res);
4688 }
4689
4690 // Index by tag.
4691 var tags = res.tags || [];
4692 for (var j = 0; j < tags.length; j++) {
4693 var tag = tags[j];
4694 if (tag) {
4695 byTag[tag] = byTag[tag] || [];
4696 byTag[tag].push(res);
4697 }
4698 }
4699
4700 // Index by category.
4701 var category = res.category;
4702 if (category) {
4703 byCategory[category] = byCategory[category] || [];
4704 byCategory[category].push(res);
4705 }
4706
4707 // Index by url.
4708 var url = res.url;
4709 if (url) {
4710 res.baseUrl = url.replace(/^intl\/\w+[\/]/, '');
4711 byUrl[res.baseUrl] = res;
4712 }
4713 }
4714 METADATA.hasType = buildResourceLookupMap(byType);
4715 METADATA.hasTag = buildResourceLookupMap(byTag);
4716 METADATA.hasCategory = buildResourceLookupMap(byCategory);
4717 }
4718
4719 return function() {
4720 // Only once.
4721 if (METADATA.all) { return; }
4722
4723 // Get current language.
4724 var locale = getLangPref();
4725
4726 // Merge english resources.
4727 METADATA.all = mergeArrays(
4728 METADATA.en.about,
4729 METADATA.en.design,
4730 METADATA.en.distribute,
4731 METADATA.en.develop,
4732 YOUTUBE_RESOURCES,
4733 BLOGGER_RESOURCES,
4734 METADATA.en.extras
4735 );
4736
4737 // Merge local language resources.
4738 if (locale !== 'en' && METADATA[locale]) {
4739 METADATA.all = mergeArrays(
4740 METADATA.all,
4741 METADATA[locale].about,
4742 METADATA[locale].design,
4743 METADATA[locale].distribute,
4744 METADATA[locale].develop,
4745 METADATA[locale].extras
4746 );
4747 }
4748
4749 mergeMetadataMap('collections', locale);
4750 mergeMetadataMap('searchHeroCollections', locale);
4751 mergeMetadataMap('carousel', locale);
4752
4753 // Create query indicies for resources.
4754 createIndices(METADATA.all, locale);
4755
4756 // Reference metadata.
4757 METADATA.androidReference = window.DATA;
4758 METADATA.googleReference = mergeArrays(window.GMS_DATA, window.GCM_DATA);
4759 };
4760})();
4761
4762/* global METADATA, util */
4763window.metadata.query = (function($) {
4764 var pageMap = {};
4765
4766 function buildResourceList(opts) {
4767 window.metadata.prepare();
4768 var expressions = parseResourceQuery(opts.query || '');
4769 var instanceMap = {};
4770 var results = [];
4771
4772 for (var i = 0; i < expressions.length; i++) {
4773 var clauses = expressions[i];
4774
4775 // Get all resources for first clause
4776 var resources = getResourcesForClause(clauses.shift());
4777
4778 // Concat to final results list
4779 results = results.concat(resources.map(filterResources(clauses, i > 0, instanceMap)).filter(filterEmpty));
4780 }
4781
4782 // Set correct order
4783 if (opts.sortOrder && results.length) {
4784 results = opts.sortOrder === 'random' ? util.shuffle(results) : results.sort(sortResultsByKey(opts.sortOrder));
4785 }
4786
4787 // Slice max results.
4788 if (opts.maxResults !== Infinity) {
4789 results = results.slice(0, opts.maxResults);
4790 }
4791
4792 // Remove page level duplicates
4793 if (opts.allowDuplicates === undefined || opts.allowDuplicates === 'false') {
4794 results = results.filter(removePageLevelDuplicates);
4795
4796 for (var index = 0; index < results.length; ++index) {
4797 pageMap[results[index].index] = 1;
4798 }
4799 }
4800
4801 return results;
4802 }
4803
4804 function filterResources(clauses, removeDuplicates, map) {
4805 return function(resource) {
4806 var resourceIsAllowed = true;
4807
4808 // References must be defined.
4809 if (resource === undefined) {
4810 return;
4811 }
4812
4813 // Get canonical (localized) version of resource if possible.
4814 resource = METADATA.byUrl[resource.baseUrl] || METADATA.byUrl[resource.url] || resource;
4815
4816 // Filter out resources already used
4817 if (removeDuplicates) {
4818 resourceIsAllowed = !map[resource.index];
4819 }
4820
4821 // Must fulfill all criteria
4822 if (clauses.length > 0) {
4823 resourceIsAllowed = resourceIsAllowed && doesResourceMatchClauses(resource, clauses);
4824 }
4825
4826 // Mark resource as used.
4827 if (resourceIsAllowed) {
4828 map[resource.index] = 1;
4829 }
4830
4831 return resourceIsAllowed && resource;
4832 };
4833 }
4834
4835 function filterEmpty(resource) {
4836 return resource;
4837 }
4838
4839 function sortResultsByKey(key) {
4840 var desc = key.charAt(0) === '-';
4841
4842 if (desc) {
4843 key = key.substring(1);
4844 }
4845
4846 return function(x, y) {
4847 return (desc ? -1 : 1) * (parseInt(x[key], 10) - parseInt(y[key], 10));
4848 };
4849 }
4850
4851 function getResourcesForClause(clause) {
4852 switch (clause.attr) {
4853 case 'type':
4854 return METADATA.byType[clause.value];
4855 case 'tag':
4856 return METADATA.byTag[clause.value];
4857 case 'collection':
4858 var resources = METADATA.collections[clause.value] || {};
4859 return getResourcesByUrlCollection(resources.resources);
4860 case 'history':
4861 return getResourcesByUrlCollection($.dacGetVisitedUrls(clause.value));
4862 case 'section':
4863 return getResourcesByUrlCollection([clause.value].sections);
4864 default:
4865 return [];
4866 }
4867 }
4868
4869 function getResourcesByUrlCollection(resources) {
4870 return (resources || []).map(function(url) {
4871 return METADATA.byUrl[url];
4872 });
4873 }
4874
4875 function removePageLevelDuplicates(resource) {
4876 return resource && !pageMap[resource.index];
4877 }
4878
4879 function doesResourceMatchClauses(resource, clauses) {
4880 for (var i = 0; i < clauses.length; i++) {
4881 var map;
4882 switch (clauses[i].attr) {
4883 case 'type':
4884 map = METADATA.hasType[clauses[i].value];
4885 break;
4886 case 'tag':
4887 map = METADATA.hasTag[clauses[i].value];
4888 break;
4889 }
4890
4891 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
4892 return clauses[i].negative;
4893 }
4894 }
4895
4896 return true;
4897 }
4898
4899 function parseResourceQuery(query) {
4900 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
4901 var expressions = [];
4902 var expressionStrs = query.split(',') || [];
4903 for (var i = 0; i < expressionStrs.length; i++) {
4904 var expr = expressionStrs[i] || '';
4905
4906 // Break expression into clauses (clause e.g. 'tag:foo')
4907 var clauses = [];
4908 var clauseStrs = expr.split(/(?=[\+\-])/);
4909 for (var j = 0; j < clauseStrs.length; j++) {
4910 var clauseStr = clauseStrs[j] || '';
4911
4912 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
4913 var parts = clauseStr.split(':');
4914 var clause = {};
4915
4916 clause.attr = parts[0].replace(/^\s+|\s+$/g, '');
4917 if (clause.attr) {
4918 if (clause.attr.charAt(0) === '+') {
4919 clause.attr = clause.attr.substring(1);
4920 } else if (clause.attr.charAt(0) === '-') {
4921 clause.negative = true;
4922 clause.attr = clause.attr.substring(1);
4923 }
4924 }
4925
4926 if (parts.length > 1) {
4927 clause.value = parts[1].replace(/^\s+|\s+$/g, '');
4928 }
4929
4930 clauses.push(clause);
4931 }
4932
4933 if (!clauses.length) {
4934 continue;
4935 }
4936
4937 expressions.push(clauses);
4938 }
4939
4940 return expressions;
4941 }
4942
4943 return buildResourceList;
4944})(jQuery);
4945
4946/* global METADATA, getLangPref */
4947
4948window.metadata.search = (function() {
4949 'use strict';
4950
4951 var currentLang = getLangPref();
4952
4953 function search(query) {
4954 window.metadata.prepare();
4955 return {
4956 android: findDocsMatches(query, METADATA.androidReference),
4957 docs: findDocsMatches(query, METADATA.googleReference),
4958 resources: findResourceMatches(query)
4959 };
4960 }
4961
4962 function findDocsMatches(query, data) {
4963 var results = [];
4964
4965 for (var i = 0; i < data.length; i++) {
4966 var s = data[i];
4967 if (query.length !== 0 && s.label.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
4968 results.push(s);
4969 }
4970 }
4971
4972 rankAutocompleteApiResults(query, results);
4973
4974 return results;
4975 }
4976
4977 function findResourceMatches(query) {
4978 var results = [];
4979
4980 // Search for matching JD docs
4981 if (query.length >= 2) {
4982 /* In some langs, spaces may be optional between certain non-Ascii word-glyphs. For
4983 * those langs, only match query at word boundaries if query includes Ascii chars only.
4984 */
4985 var NO_BOUNDARY_LANGUAGES = ['ja','ko','vi','zh-cn','zh-tw'];
4986 var isAsciiOnly = /^[\u0000-\u007f]*$/.test(query);
4987 var noBoundaries = (NO_BOUNDARY_LANGUAGES.indexOf(window.getLangPref()) !== -1);
4988 var exprBoundary = (!isAsciiOnly && noBoundaries) ? '' : '(?:^|\\s)';
4989 var queryRegex = new RegExp(exprBoundary + query.toLowerCase(), 'g');
4990
4991 var all = METADATA.all;
4992 for (var i = 0; i < all.length; i++) {
4993 // current search comparison, with counters for tag and title,
4994 // used later to improve ranking
4995 var s = all[i];
4996 s.matched_tag = 0;
4997 s.matched_title = 0;
4998 var matched = false;
4999
5000 // Check if query matches any tags; work backwards toward 1 to assist ranking
5001 if (s.keywords) {
5002 for (var j = s.keywords.length - 1; j >= 0; j--) {
5003 // it matches a tag
5004 if (s.keywords[j].toLowerCase().match(queryRegex)) {
5005 matched = true;
5006 s.matched_tag = j + 1; // add 1 to index position
5007 }
5008 }
5009 }
5010
5011 // Check if query matches doc title
5012 if (s.title.toLowerCase().match(queryRegex)) {
5013 matched = true;
5014 s.matched_title = 1;
5015 }
5016
5017 // Remember the doc if it matches either
5018 if (matched) {
5019 results.push(s);
5020 }
5021 }
5022
5023 // Improve the current results
5024 results = lookupBetterResult(results);
5025
5026 // Rank/sort all the matched pages
5027 rankAutocompleteDocResults(results);
5028
5029 return results;
5030 }
5031 }
5032
5033 // Replaces a match with another resource by url, if it exists.
5034 function lookupReplacementByUrl(match, url) {
5035 var replacement = METADATA.byUrl[url];
5036
5037 // Replacement resource does not exists.
5038 if (!replacement) { return; }
5039
5040 replacement.matched_title = Math.max(replacement.matched_title, match.matched_title);
5041 replacement.matched_tag = Math.max(replacement.matched_tag, match.matched_tag);
5042
5043 return replacement;
5044 }
5045
5046 // Find the localized version of a page if it exists.
5047 function lookupLocalizedVersion(match) {
5048 return METADATA.byUrl[match.baseUrl] || METADATA.byUrl[match.url];
5049 }
5050
5051 // Find the main page for a tutorial when matching a subpage.
5052 function lookupTutorialIndex(match) {
5053 // Guard for non index tutorial pages.
5054 if (match.type !== 'training' || match.url.indexOf('index.html') >= 0) { return; }
5055
5056 var indexUrl = match.url.replace(/[^\/]+$/, 'index.html');
5057 return lookupReplacementByUrl(match, indexUrl);
5058 }
5059
5060 // Find related results which are a better match for the user.
5061 function lookupBetterResult(matches) {
5062 var newMatches = [];
5063
5064 matches = matches.filter(function(match) {
5065 var newMatch = match;
5066 newMatch = lookupTutorialIndex(newMatch) || newMatch;
5067 newMatch = lookupLocalizedVersion(newMatch) || newMatch;
5068
5069 if (newMatch !== match) {
5070 newMatches.push(newMatch);
5071 }
5072
5073 return newMatch === match;
5074 });
5075
5076 return toUnique(newMatches.concat(matches));
5077 }
5078
5079 /* Order the jd doc result list based on match quality */
5080 function rankAutocompleteDocResults(matches) {
5081 if (!matches || !matches.length) {
5082 return;
5083 }
5084
5085 var _resultScoreFn = function(match) {
5086 var score = 1.0;
5087
5088 // if the query matched a tag
5089 if (match.matched_tag > 0) {
5090 // multiply score by factor relative to position in tags list (max of 3)
5091 score *= 3 / match.matched_tag;
5092
5093 // if it also matched the title
5094 if (match.matched_title > 0) {
5095 score *= 2;
5096 }
5097 } else if (match.matched_title > 0) {
5098 score *= 3;
5099 }
5100
5101 if (match.lang === currentLang) {
5102 score *= 5;
5103 }
5104
5105 return score;
5106 };
5107
5108 for (var i = 0; i < matches.length; i++) {
5109 matches[i].__resultScore = _resultScoreFn(matches[i]);
5110 }
5111
5112 matches.sort(function(a, b) {
5113 var n = b.__resultScore - a.__resultScore;
5114
5115 if (n === 0) {
5116 // lexicographical sort if scores are the same
5117 n = (a.title < b.title) ? -1 : 1;
5118 }
5119
5120 return n;
5121 });
5122 }
5123
5124 /* Order the result list based on match quality */
5125 function rankAutocompleteApiResults(query, matches) {
5126 query = query || '';
5127 if (!matches || !matches.length) {
5128 return;
5129 }
5130
5131 // helper function that gets the last occurence index of the given regex
5132 // in the given string, or -1 if not found
5133 var _lastSearch = function(s, re) {
5134 if (s === '') {
5135 return -1;
5136 }
5137 var l = -1;
5138 var tmp;
5139 while ((tmp = s.search(re)) >= 0) {
5140 if (l < 0) {
5141 l = 0;
5142 }
5143 l += tmp;
5144 s = s.substr(tmp + 1);
5145 }
5146 return l;
5147 };
5148
5149 // helper function that counts the occurrences of a given character in
5150 // a given string
5151 var _countChar = function(s, c) {
5152 var n = 0;
5153 for (var i = 0; i < s.length; i++) {
5154 if (s.charAt(i) === c) {
5155 ++n;
5156 }
5157 }
5158 return n;
5159 };
5160
5161 var queryLower = query.toLowerCase();
5162 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
5163 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
5164 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
5165
5166 var _resultScoreFn = function(result) {
5167 // scores are calculated based on exact and prefix matches,
5168 // and then number of path separators (dots) from the last
5169 // match (i.e. favoring classes and deep package names)
5170 var score = 1.0;
5171 var labelLower = result.label.toLowerCase();
5172 var t;
5173 var partsAfter;
5174 t = _lastSearch(labelLower, partExactAlnumRE);
5175 if (t >= 0) {
5176 // exact part match
5177 partsAfter = _countChar(labelLower.substr(t + 1), '.');
5178 score *= 200 / (partsAfter + 1);
5179 } else {
5180 t = _lastSearch(labelLower, partPrefixAlnumRE);
5181 if (t >= 0) {
5182 // part prefix match
5183 partsAfter = _countChar(labelLower.substr(t + 1), '.');
5184 score *= 20 / (partsAfter + 1);
5185 }
5186 }
5187
5188 return score;
5189 };
5190
5191 for (var i = 0; i < matches.length; i++) {
5192 // if the API is deprecated, default score is 0; otherwise, perform scoring
5193 if (matches[i].deprecated === 'true') {
5194 matches[i].__resultScore = 0;
5195 } else {
5196 matches[i].__resultScore = _resultScoreFn(matches[i]);
5197 }
5198 }
5199
5200 matches.sort(function(a, b) {
5201 var n = b.__resultScore - a.__resultScore;
5202
5203 if (n === 0) {
5204 // lexicographical sort if scores are the same
5205 n = (a.label < b.label) ? -1 : 1;
5206 }
5207
5208 return n;
5209 });
5210 }
5211
5212 // Destructive but fast toUnique.
5213 // http://stackoverflow.com/a/25082874
5214 function toUnique(array) {
5215 var c;
5216 var b = array.length || 1;
5217
5218 while (c = --b) {
5219 while (c--) {
5220 if (array[b] === array[c]) {
5221 array.splice(c, 1);
5222 }
5223 }
5224 }
5225 return array;
5226 }
5227
5228 return search;
5229})();
5230
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005231(function($) {
5232 'use strict';
5233
5234 /**
5235 * Smoothly scroll to location on current page.
5236 * @param el
5237 * @param options
5238 * @constructor
5239 */
5240 function ScrollButton(el, options) {
5241 this.el = $(el);
5242 this.target = $(this.el.attr('href'));
5243 this.options = $.extend({}, ScrollButton.DEFAULTS_, options);
5244
5245 if (typeof this.options.offset === 'string') {
5246 this.options.offset = $(this.options.offset).height();
5247 }
5248
5249 this.el.on('click', this.clickHandler_.bind(this));
5250 }
5251
5252 /**
5253 * Default options
5254 * @type {{duration: number, easing: string, offset: number, scrollContainer: string}}
5255 * @private
5256 */
5257 ScrollButton.DEFAULTS_ = {
5258 duration: 300,
5259 easing: 'swing',
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005260 offset: '.dac-header',
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005261 scrollContainer: 'html, body'
5262 };
5263
5264 /**
5265 * Scroll logic
5266 * @param event
5267 * @private
5268 */
5269 ScrollButton.prototype.clickHandler_ = function(event) {
5270 if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
5271 return;
5272 }
5273
5274 event.preventDefault();
5275
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005276 var position = this.getTargetPosition();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005277 $(this.options.scrollContainer).animate({
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005278 scrollTop: position - this.options.offset
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005279 }, this.options);
5280 };
5281
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005282 ScrollButton.prototype.getTargetPosition = function() {
5283 if (this.options.scrollContainer === ScrollButton.DEFAULTS_.scrollContainer) {
5284 return this.target.offset().top;
5285 }
5286 var scrollContainer = $(this.options.scrollContainer)[0];
5287 var currentEl = this.target[0];
5288 var pos = 0;
5289 while (currentEl !== scrollContainer && currentEl !== null) {
5290 pos += currentEl.offsetTop;
5291 currentEl = currentEl.offsetParent;
5292 }
5293 return pos;
5294 };
5295
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005296 /**
5297 * jQuery plugin
5298 * @param {object} options - Override default options.
5299 */
5300 $.fn.dacScrollButton = function(options) {
5301 return this.each(function() {
5302 new ScrollButton(this, options);
5303 });
5304 };
5305
5306 /**
5307 * Data Attribute API
5308 */
5309 $(document).on('ready.aranja', function() {
5310 $('[data-scroll-button]').each(function() {
5311 $(this).dacScrollButton($(this).data());
5312 });
5313 });
5314})(jQuery);
5315
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005316/* global getLangPref */
5317(function($) {
5318 var LANG;
5319
5320 function getSearchLang() {
5321 if (!LANG) {
5322 LANG = getLangPref();
5323
5324 // Fix zh-cn to be zh-CN.
5325 LANG = LANG.replace(/-\w+/, function(m) { return m.toUpperCase(); });
5326 }
5327 return LANG;
5328 }
5329
5330 function customSearch(query, start) {
5331 var searchParams = {
5332 // current cse instance:
5333 //cx: '001482626316274216503:zu90b7s047u',
5334 // new cse instance:
5335 cx: '000521750095050289010:zpcpi1ea4s8',
5336 key: 'AIzaSyCFhbGnjW06dYwvRCU8h_zjdpS4PYYbEe8',
5337 q: query,
5338 start: start || 1,
5339 num: 6,
5340 hl: getSearchLang(),
5341 fields: 'queries,items(pagemap,link,title,htmlSnippet,formattedUrl)'
5342 };
5343
5344 return $.get('https://content.googleapis.com/customsearch/v1?' + $.param(searchParams));
5345 }
5346
5347 function renderResults(el, results) {
5348 if (!results.items) {
5349 el.append($('<div>').text('No results'));
5350 return;
5351 }
5352
5353 for (var i = 0; i < results.items.length; i++) {
5354 var item = results.items[i];
5355 var hasImage = item.pagemap && item.pagemap.cse_thumbnail;
5356 var sectionMatch = item.link.match(/developer\.android\.com\/(\w*)/);
5357 var section = (sectionMatch && sectionMatch[1]) || 'blog';
5358
5359 var entry = $('<div>').addClass('dac-custom-search-entry cols');
5360
5361 if (hasImage) {
5362 var image = item.pagemap.cse_thumbnail[0];
5363 entry.append($('<div>').addClass('col-1of6')
5364 .append($('<div>').addClass('dac-custom-search-image').css('background-image', 'url(' + image.src + ')')));
5365 }
5366
5367 entry.append($('<div>').addClass(hasImage ? 'col-5of6' : 'col-6of6')
5368 .append($('<p>').addClass('dac-custom-search-section').text(section))
5369 .append(
5370 $('<a>').text(item.title).attr('href', item.link).wrap('<h2>').parent().addClass('dac-custom-search-title')
5371 )
5372 .append($('<p>').addClass('dac-custom-search-snippet').html(item.htmlSnippet.replace(/<br>/g, '')))
5373 .append($('<a>').addClass('dac-custom-search-link').text(item.formattedUrl).attr('href', item.link)));
5374
5375 el.append(entry);
5376 }
5377
5378 if (results.queries.nextPage) {
5379 var loadMoreButton = $('<button id="dac-custom-search-load-more">')
5380 .addClass('dac-custom-search-load-more')
5381 .text('Load more')
5382 .click(function() {
5383 loadMoreResults(el, results);
5384 });
5385
5386 el.append(loadMoreButton);
5387 }
5388 }
5389
5390 function loadMoreResults(el, results) {
5391 var query = results.queries.request.searchTerms;
5392 var start = results.queries.nextPage.startIndex;
5393 var loadMoreButton = el.find('#dac-custom-search-load-more');
5394
5395 loadMoreButton.text('Loading more...');
5396
5397 customSearch(query, start).then(function(results) {
5398 loadMoreButton.remove();
5399 renderResults(el, results);
5400 });
5401 }
5402
5403 $.fn.customSearch = function(query) {
5404 var el = $(this);
5405
5406 customSearch(query).then(function(results) {
5407 el.empty();
5408 renderResults(el, results);
5409 });
5410 };
5411})(jQuery);
5412
5413/* global METADATA */
5414
5415(function($) {
5416 $.fn.dacSearchRenderHero = function(resources, query) {
5417 var el = $(this);
5418 el.empty();
5419
5420 var resource = METADATA.searchHeroCollections[query];
5421
5422 if (resource) {
5423 el.dacHero(resource, true);
5424 el.show();
5425
5426 return true;
5427 } else {
5428 el.hide();
5429 }
5430 };
5431})(jQuery);
5432
5433(function($) {
5434 $.fn.dacSearchRenderReferences = function(results, query) {
5435 var referenceCard = $('.suggest-card.reference');
5436 referenceCard.data('searchreferences.dac', {results: results, query: query});
5437 renderResults(referenceCard, results, query, false);
5438 };
5439
5440 var ROW_COUNT_COLLAPSED = 7;
5441 var ROW_COUNT_EXPANDED = 33;
5442 var ROW_COUNT_GOOGLE_COLLAPSED = 1;
5443 var ROW_COUNT_GOOGLE_EXPANDED = 8;
5444
5445 function onSuggestionClick(e) {
5446 var normalClick = e.which === 1 && !e.ctrlKey && !e.shiftKey && !e.metaKey;
5447 if (normalClick) {
5448 e.preventDefault();
5449 }
5450
5451 // When user clicks a suggested document, track it
5452 var url = $(e.currentTarget).attr('href');
5453 ga('send', 'event', 'Suggestion Click', 'clicked: ' + url,
5454 'query: ' + $('#search_autocomplete').val().toLowerCase(),
5455 {hitCallback: function() {
5456 if (normalClick) {
5457 document.location = url;
5458 }
5459 }});
5460 }
5461
5462 function buildLink(match) {
5463 var link = $('<a>').attr('href', window.toRoot + match.link);
5464
5465 var label = match.label;
5466 var classNameStart = label.match(/[A-Z]/) ? label.search(/[A-Z]/) : label.lastIndexOf('.') + 1;
5467
5468 var newLink = '<span class="namespace">' +
5469 label.substr(0, classNameStart) +
5470 '</span><br />' +
5471 label.substr(classNameStart, label.length);
5472
5473 link.html(newLink);
5474 return link;
5475 }
5476
5477 function buildSuggestion(match, query) {
5478 var li = $('<li>').addClass('dac-search-results-reference-entry');
5479
5480 var link = buildLink(match);
5481 link.highlightMatches(query);
5482 li.append(link);
5483 return li[0];
5484 }
5485
5486 function buildResults(results, query) {
5487 return results.map(function(match) {
5488 return buildSuggestion(match, query);
5489 });
5490 }
5491
5492 function renderAndroidResults(list, gMatches, query) {
5493 list.empty();
5494
5495 var header = $('<li class="dac-search-results-reference-header">Reference</li>');
5496 list.append(header);
5497
5498 if (gMatches.length > 0) {
5499 list.removeClass('no-results');
5500
5501 var resources = buildResults(gMatches, query);
5502 list.append(resources);
5503
5504 return true;
5505 } else {
5506 list.append('<li class="dac-search-results-reference-entry-empty">No results</li>');
5507 }
5508 }
5509
5510 function renderGoogleDocsResults(list, gGoogleMatches, query) {
5511 list = $('.suggest-card.reference ul');
5512
5513 if (gGoogleMatches.length > 0) {
5514 list.append('<li class="dac-search-results-reference-header">in Google Services</li>');
5515
5516 var resources = buildResults(gGoogleMatches, query);
5517 list.append(resources);
5518
5519 return true;
5520 }
5521 }
5522
5523 function renderResults(referenceCard, results, query, expanded) {
5524 var list = referenceCard.find('ul');
5525 list.toggleClass('is-expanded', !!expanded);
5526
5527 // Figure out how many results we can show in our fixed size box.
5528 var total = expanded ? ROW_COUNT_EXPANDED : ROW_COUNT_COLLAPSED;
5529 var googleCount = expanded ? ROW_COUNT_GOOGLE_EXPANDED : ROW_COUNT_GOOGLE_COLLAPSED;
5530 googleCount = Math.max(googleCount, total - results.android.length);
5531 googleCount = Math.min(googleCount, results.docs.length);
5532
5533 if (googleCount > 0) {
5534 // If there are google results, reserve space for its header.
5535 googleCount++;
5536 }
5537
5538 var androidCount = Math.max(0, total - googleCount);
5539 if (androidCount === 0) {
5540 // Reserve space for "No reference results"
5541 googleCount--;
5542 }
5543
5544 renderAndroidResults(list, results.android.slice(0, androidCount), query);
5545 renderGoogleDocsResults(list, results.docs.slice(0, googleCount - 1), query);
5546
5547 var totalResults = results.android.length + results.docs.length;
5548 if (totalResults === 0) {
5549 list.addClass('no-results');
5550 }
5551
5552 // Tweak see more logic to account for references.
5553 var hasMore = totalResults > ROW_COUNT_COLLAPSED && !util.matchesMedia('mobile');
5554 var searchEl = $('#search-resources');
5555 searchEl.toggleClass('dac-has-more', searchEl.hasClass('dac-has-more') || (hasMore && !expanded));
5556 searchEl.toggleClass('dac-has-less', searchEl.hasClass('dac-has-less') || (hasMore && expanded));
5557 }
5558
5559 function onToggleMore(e) {
5560 var link = $(e.currentTarget);
5561 var referenceCard = $('.suggest-card.reference');
5562 var data = referenceCard.data('searchreferences.dac');
5563
5564 if (util.matchesMedia('mobile')) { return; }
5565
5566 renderResults(referenceCard, data.results, data.query, link.data('toggle') === 'show-more');
5567 }
5568
5569 $(document).on('click', '.dac-search-results-resources [data-toggle="show-more"]', onToggleMore);
5570 $(document).on('click', '.dac-search-results-resources [data-toggle="show-less"]', onToggleMore);
5571 $(document).on('click', '.suggest-card.reference a', onSuggestionClick);
5572})(jQuery);
5573
5574(function($) {
5575 function highlightPage(query, page) {
5576 page.find('.title').highlightMatches(query);
5577 }
5578
5579 $.fn.dacSearchRenderResources = function(gDocsMatches, query) {
5580 this.resourceWidget(gDocsMatches, {
5581 itemsPerPage: 18,
5582 initialResults: 6,
5583 cardSizes: ['6x2'],
5584 onRenderPage: highlightPage.bind(null, query)
5585 });
5586
5587 return this;
5588 };
5589})(jQuery);
5590
5591/*global metadata */
5592
5593(function($, metadata) {
5594 'use strict';
5595
5596 function Search() {
5597 this.body = $('body');
5598 this.lastQuery = null;
5599 this.searchResults = $('#search-results');
5600 this.searchClose = $('[data-search-close]');
5601 this.searchClear = $('[data-search-clear]');
5602 this.searchInput = $('#search_autocomplete');
5603 this.searchResultsContent = $('#dac-search-results-content');
5604 this.searchResultsFor = $('#search-results-for');
5605 this.searchResultsHistory = $('#dac-search-results-history');
5606 this.searchResultsResources = $('#search-resources');
5607 this.searchResultsHero = $('#dac-search-results-hero');
5608 this.searchResultsReference = $('#dac-search-results-reference');
5609 this.searchHeader = $('[data-search]').data('search-input.dac');
5610 }
5611
5612 Search.prototype.init = function() {
5613 if (this.checkRedirectToIndex()) { return; }
5614
5615 this.searchHistory = window.dacStore('search-history');
5616
5617 this.searchInput.focus(this.onSearchChanged.bind(this));
5618 this.searchInput.keydown(this.handleKeyboardShortcut.bind(this));
5619 this.searchInput.on('input', this.onSearchChanged.bind(this));
5620 this.searchClear.click(this.clear.bind(this));
5621 this.searchClose.click(this.close.bind(this));
5622
5623 this.customSearch = $.fn.debounce(function(query) {
5624 $('#dac-custom-search-results').customSearch(query);
5625 }, 1000);
5626
5627 // Start search shortcut (/)
5628 $('body').keyup(function(event) {
5629 if (event.which === 191 && $(event.target).is(':not(:input)')) {
5630 this.searchInput.focus();
5631 }
5632 }.bind(this));
5633
5634 $(window).on('popstate', this.onPopState.bind(this));
5635 $(window).hashchange(this.onHashChange.bind(this));
5636 this.onHashChange();
5637 };
5638
5639 Search.prototype.checkRedirectToIndex = function() {
5640 var query = this.getUrlQuery();
5641 var target = window.getLangTarget();
5642 var prefix = (target !== 'en') ? '/intl/' + target : '';
5643 var pathname = location.pathname.slice(prefix.length);
5644 if (query != null && pathname !== '/index.html') {
5645 location.href = prefix + '/index.html' + location.hash;
5646 return true;
5647 }
5648 };
5649
5650 Search.prototype.handleKeyboardShortcut = function(event) {
5651 // Close (esc)
5652 if (event.which === 27) {
5653 this.searchClose.trigger('click');
5654 event.preventDefault();
5655 }
5656
5657 // Previous result (up arrow)
5658 if (event.which === 38) {
5659 this.previousResult();
5660 event.preventDefault();
5661 }
5662
5663 // Next result (down arrow)
5664 if (event.which === 40) {
5665 this.nextResult();
5666 event.preventDefault();
5667 }
5668
5669 // Navigate to result (enter)
5670 if (event.which === 13) {
5671 this.navigateToResult();
5672 event.preventDefault();
5673 }
5674 };
5675
5676 Search.prototype.goToResult = function(relativeIndex) {
5677 var links = this.searchResults.find('a').filter(':visible');
5678 var selectedLink = this.searchResults.find('.dac-selected');
5679
5680 if (selectedLink.length) {
5681 var found = $.inArray(selectedLink[0], links);
5682
5683 selectedLink.removeClass('dac-selected');
5684 links.eq(found + relativeIndex).addClass('dac-selected');
5685 return true;
5686 } else {
5687 if (relativeIndex > 0) {
5688 links.first().addClass('dac-selected');
5689 }
5690 }
5691 };
5692
5693 Search.prototype.previousResult = function() {
5694 this.goToResult(-1);
5695 };
5696
5697 Search.prototype.nextResult = function() {
5698 this.goToResult(1);
5699 };
5700
5701 Search.prototype.navigateToResult = function() {
5702 var query = this.getQuery();
5703 var selectedLink = this.searchResults.find('.dac-selected');
5704
5705 if (selectedLink.length) {
5706 selectedLink[0].click();
5707 } else {
5708 this.searchHistory.push(query);
5709 this.addQueryToUrl(query);
5710
5711 var isMobileOrTablet = typeof window.orientation !== 'undefined';
5712
5713 if (isMobileOrTablet) {
5714 this.searchInput.blur();
5715 }
5716 }
5717 };
5718
5719 Search.prototype.onHashChange = function() {
5720 var query = this.getUrlQuery();
5721 if (query != null && query !== this.getQuery()) {
5722 this.searchInput.val(query);
5723 this.onSearchChanged();
5724 }
5725 };
5726
5727 Search.prototype.clear = function() {
5728 this.searchInput.val('');
5729 window.location.hash = '';
5730 this.onSearchChanged();
5731 this.searchInput.focus();
5732 };
5733
5734 Search.prototype.close = function() {
5735 this.removeQueryFromUrl();
5736 this.searchInput.blur();
5737 this.hideOverlay();
5738 };
5739
5740 Search.prototype.getUrlQuery = function() {
5741 var queryMatch = location.hash.match(/q=(.*)&?/);
5742 return queryMatch && queryMatch[1] && decodeURI(queryMatch[1]);
5743 };
5744
5745 Search.prototype.getQuery = function() {
5746 return this.searchInput.val().replace(/(^ +)|( +$)/g, '');
5747 };
5748
5749 Search.prototype.onSearchChanged = function() {
5750 var query = this.getQuery();
5751
5752 this.showOverlay();
5753 this.render(query);
5754 };
5755
5756 Search.prototype.render = function(query) {
5757 if (this.lastQuery === query) { return; }
5758
5759 if (query.length < 2) {
5760 query = '';
5761 }
5762
5763 this.lastQuery = query;
5764 this.searchResultsFor.text(query);
5765 this.customSearch(query);
5766 var metadataResults = metadata.search(query);
5767 this.searchResultsResources.dacSearchRenderResources(metadataResults.resources, query);
5768 this.searchResultsReference.dacSearchRenderReferences(metadataResults, query);
5769 var hasHero = this.searchResultsHero.dacSearchRenderHero(metadataResults.resources, query);
5770 var hasQuery = !!query;
5771
5772 this.searchResultsReference.toggle(!hasHero);
5773 this.searchResultsContent.toggle(hasQuery);
5774 this.searchResultsHistory.toggle(!hasQuery);
5775 this.addQueryToUrl(query);
5776 this.pushState();
5777 };
5778
5779 Search.prototype.addQueryToUrl = function(query) {
5780 var hash = 'q=' + encodeURI(query);
5781
5782 if (query) {
5783 if (window.history.replaceState) {
5784 window.history.replaceState(null, '', '#' + hash);
5785 } else {
5786 window.location.hash = hash;
5787 }
5788 }
5789 };
5790
5791 Search.prototype.onPopState = function() {
5792 if (!this.getUrlQuery()) {
5793 this.hideOverlay();
5794 this.searchHeader.unsetActiveState();
5795 }
5796 };
5797
5798 Search.prototype.removeQueryFromUrl = function() {
5799 window.location.hash = '';
5800 };
5801
5802 Search.prototype.pushState = function() {
5803 if (window.history.pushState && !this.lastQuery.length) {
5804 window.history.pushState(null, '');
5805 }
5806 };
5807
5808 Search.prototype.showOverlay = function() {
5809 this.body.addClass('dac-modal-open dac-search-open');
5810 };
5811
5812 Search.prototype.hideOverlay = function() {
5813 this.body.removeClass('dac-modal-open dac-search-open');
5814 };
5815
5816 $(document).on('ready.aranja', function() {
5817 var search = new Search();
5818 search.init();
5819 });
5820})(jQuery, metadata);
5821
5822window.dacStore = (function(window) {
5823 /**
5824 * Creates a new persistent store.
5825 * If localStorage is unavailable, the items are stored in memory.
5826 *
5827 * @constructor
5828 * @param {string} name The name of the store
5829 * @param {number} maxSize The maximum number of items the store can hold.
5830 */
5831 var Store = function(name, maxSize) {
5832 var content = [];
5833
5834 var hasLocalStorage = !!window.localStorage;
5835
5836 if (hasLocalStorage) {
5837 try {
5838 content = JSON.parse(window.localStorage.getItem(name) || []);
5839 } catch (e) {
5840 // Store contains invalid data
5841 window.localStorage.removeItem(name);
5842 }
5843 }
5844
5845 function push(item) {
5846 if (content[0] === item) {
5847 return;
5848 }
5849
5850 content.unshift(item);
5851
5852 if (maxSize) {
5853 content.splice(maxSize, content.length);
5854 }
5855
5856 if (hasLocalStorage) {
5857 window.localStorage.setItem(name, JSON.stringify(content));
5858 }
5859 }
5860
5861 function all() {
5862 // Return a copy
5863 return content.slice();
5864 }
5865
5866 return {
5867 push: push,
5868 all: all
5869 };
5870 };
5871
5872 var stores = {
5873 'search-history': new Store('search-history', 3)
5874 };
5875
5876 /**
5877 * Get a named persistent store.
5878 * @param {string} name
5879 * @return {Store}
5880 */
5881 return function getStore(name) {
5882 return stores[name];
5883 };
5884})(window);
5885
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005886(function($) {
5887 'use strict';
5888
5889 /**
5890 * A component that swaps two dynamic height views with an animation.
5891 * Listens for the following events:
5892 * * swap-content: triggers SwapContent.swap_()
5893 * * swap-reset: triggers SwapContent.reset()
5894 * @param el
5895 * @param options
5896 * @constructor
5897 */
5898 function SwapContent(el, options) {
5899 this.el = $(el);
5900 this.options = $.extend({}, SwapContent.DEFAULTS_, options);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005901 this.options.dynamic = this.options.dynamic === 'true';
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005902 this.containers = this.el.find(this.options.container);
5903 this.initiallyActive = this.containers.children('.' + this.options.activeClass).eq(0);
5904 this.el.on('swap-content', this.swap.bind(this));
5905 this.el.on('swap-reset', this.reset.bind(this));
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005906 this.el.find(this.options.swapButton).on('click', this.swap.bind(this));
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005907 }
5908
5909 /**
5910 * SwapContent's default settings.
5911 * @type {{activeClass: string, container: string, transitionSpeed: number}}
5912 * @private
5913 */
5914 SwapContent.DEFAULTS_ = {
5915 activeClass: 'dac-active',
5916 container: '[data-swap-container]',
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005917 dynamic: 'true',
5918 swapButton: '[data-swap-button]',
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005919 transitionSpeed: 500
5920 };
5921
5922 /**
5923 * Returns container's visible height.
5924 * @param container
5925 * @returns {number}
5926 */
5927 SwapContent.prototype.currentHeight = function(container) {
5928 return container.children('.' + this.options.activeClass).outerHeight();
5929 };
5930
5931 /**
5932 * Reset to show initial content
5933 */
5934 SwapContent.prototype.reset = function() {
5935 if (!this.initiallyActive.hasClass(this.initiallyActive)) {
5936 this.containers.children().toggleClass(this.options.activeClass);
5937 }
5938 };
5939
5940 /**
5941 * Complete the swap.
5942 */
5943 SwapContent.prototype.complete = function() {
5944 this.containers.height('auto');
5945 this.containers.trigger('swap-complete');
5946 };
5947
5948 /**
5949 * Perform the swap of content.
5950 */
5951 SwapContent.prototype.swap = function() {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005952 this.containers.each(function(index, container) {
5953 container = $(container);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005954
5955 if (!this.options.dynamic) {
5956 container.children().toggleClass(this.options.activeClass);
5957 this.complete.bind(this);
5958 return;
5959 }
5960
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005961 container.height(this.currentHeight(container)).children().toggleClass(this.options.activeClass);
5962 container.animate({height: this.currentHeight(container)}, this.options.transitionSpeed,
5963 this.complete.bind(this));
5964 }.bind(this));
5965 };
5966
5967 /**
5968 * jQuery plugin
5969 * @param {object} options - Override default options.
5970 */
5971 $.fn.dacSwapContent = function(options) {
5972 return this.each(function() {
5973 new SwapContent(this, options);
5974 });
5975 };
5976
5977 /**
5978 * Data Attribute API
5979 */
5980 $(document).on('ready.aranja', function() {
5981 $('[data-swap]').each(function() {
5982 $(this).dacSwapContent($(this).data());
5983 });
5984 });
5985})(jQuery);
5986
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005987/* Tabs */
5988(function($) {
5989 'use strict';
5990
5991 /**
5992 * @param {HTMLElement} el - The DOM element.
5993 * @param {Object} options
5994 * @constructor
5995 */
5996 function Tabs(el, options) {
5997 this.el = $(el);
5998 this.options = $.extend({}, Tabs.DEFAULTS_, options);
5999 this.init();
6000 }
6001
6002 Tabs.DEFAULTS_ = {
6003 activeClass: 'dac-active',
6004 viewDataAttr: 'tab-view',
6005 itemDataAttr: 'tab-item'
6006 };
6007
6008 Tabs.prototype.init = function() {
6009 var itemDataAttribute = '[data-' + this.options.itemDataAttr + ']';
6010 this.tabEl_ = this.el.find(itemDataAttribute);
6011 this.tabViewEl_ = this.el.find('[data-' + this.options.viewDataAttr + ']');
6012 this.el.on('click.dac-tabs', itemDataAttribute, this.changeTabs.bind(this));
6013 };
6014
6015 Tabs.prototype.changeTabs = function(event) {
6016 var current = $(event.currentTarget);
6017 var index = current.index();
6018
6019 if (current.hasClass(this.options.activeClass)) {
6020 current.add(this.tabViewEl_.eq(index)).removeClass(this.options.activeClass);
6021 } else {
6022 this.tabEl_.add(this.tabViewEl_).removeClass(this.options.activeClass);
6023 current.add(this.tabViewEl_.eq(index)).addClass(this.options.activeClass);
6024 }
6025 };
6026
6027 /**
6028 * jQuery plugin
6029 */
6030 $.fn.dacTabs = function() {
6031 return this.each(function() {
6032 var el = $(this);
6033 new Tabs(el, el.data());
6034 });
6035 };
6036
6037 /**
6038 * Data Attribute API
6039 */
6040 $(function() {
6041 $('[data-tabs]').dacTabs();
6042 });
6043})(jQuery);
6044
6045/* Toast Component */
6046(function($) {
6047 'use strict';
6048 /**
6049 * @constant
6050 * @type {String}
6051 */
6052 var LOCAL_STORAGE_KEY = 'toast-closed-index';
6053
6054 /**
6055 * Dictionary from local storage.
6056 */
6057 var toastDictionary = localStorage.getItem(LOCAL_STORAGE_KEY);
6058 toastDictionary = toastDictionary ? JSON.parse(toastDictionary) : {};
6059
6060 /**
6061 * Variable used for caching the body.
6062 */
6063 var bodyCached;
6064
6065 /**
6066 * @param {HTMLElement} el - The DOM element.
6067 * @param {Object} options
6068 * @constructor
6069 */
6070 function Toast(el, options) {
6071 this.el = $(el);
6072 this.options = $.extend({}, Toast.DEFAULTS_, options);
6073 this.init();
6074 }
6075
6076 Toast.DEFAULTS_ = {
6077 closeBtnClass: 'dac-toast-close-btn',
6078 closeDuration: 200,
6079 visibleClass: 'dac-visible',
6080 wrapClass: 'dac-toast-wrap'
6081 };
6082
6083 /**
6084 * Generate a close button.
6085 * @returns {*|HTMLElement}
6086 */
6087 Toast.prototype.closeBtn = function() {
6088 this.closeBtnEl = this.closeBtnEl || $('<button class="' + this.options.closeBtnClass + '">' +
6089 '<i class="dac-sprite dac-close-black"></i>' +
6090 '</button>');
6091 return this.closeBtnEl;
6092 };
6093
6094 /**
6095 * Initialize a new toast element
6096 */
6097 Toast.prototype.init = function() {
6098 this.hash = this.el.text().replace(/[\s\n\t]/g, '').split('').slice(0, 128).join('');
6099
6100 if (toastDictionary[this.hash]) {
6101 return;
6102 }
6103
6104 this.closeBtn().on('click', this.onClickHandler.bind(this));
6105 this.el.find('.' + this.options.wrapClass).append(this.closeBtn());
6106 this.el.addClass(this.options.visibleClass);
6107 this.dynamicPadding(this.el.outerHeight());
6108 };
6109
6110 /**
6111 * Add padding to make sure all page is visible.
6112 */
6113 Toast.prototype.dynamicPadding = function(val) {
6114 var currentPadding = parseInt(bodyCached.css('padding-bottom') || 0);
6115 bodyCached.css('padding-bottom', val + currentPadding);
6116 };
6117
6118 /**
6119 * Remove a toast from the DOM
6120 */
6121 Toast.prototype.remove = function() {
6122 this.dynamicPadding(-this.el.outerHeight());
6123 this.el.remove();
6124 };
6125
6126 /**
6127 * Handle removal of the toast.
6128 */
6129 Toast.prototype.onClickHandler = function() {
6130 // Only fadeout toasts from top of stack. Others are removed immediately.
6131 var duration = this.el.index() === 0 ? this.options.closeDuration : 0;
6132 this.el.fadeOut(duration, this.remove.bind(this));
6133
6134 // Save closed state.
6135 toastDictionary[this.hash] = 1;
6136 localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(toastDictionary));
6137 };
6138
6139 /**
6140 * jQuery plugin
6141 * @param {object} options - Override default options.
6142 */
6143 $.fn.dacToast = function() {
6144 return this.each(function() {
6145 var el = $(this);
6146 new Toast(el, el.data());
6147 });
6148 };
6149
6150 /**
6151 * Data Attribute API
6152 */
6153 $(function() {
6154 bodyCached = $('#body-content');
6155 $('[data-toast]').dacToast();
6156 });
6157})(jQuery);
6158
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006159(function($) {
6160 function Toggle(el) {
6161 $(el).on('click.dac.togglesection', this.toggle);
6162 }
6163
6164 Toggle.prototype.toggle = function() {
6165 var $this = $(this);
6166
6167 var $parent = getParent($this);
6168 var isExpanded = $parent.hasClass('is-expanded');
6169
6170 transitionMaxHeight($parent.find('.dac-toggle-content'), !isExpanded);
6171 $parent.toggleClass('is-expanded');
6172
6173 return false;
6174 };
6175
6176 function getParent($this) {
6177 var selector = $this.attr('data-target');
6178
6179 if (!selector) {
6180 selector = $this.attr('href');
6181 selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '');
6182 }
6183
6184 var $parent = selector && $(selector);
6185
6186 $parent = $parent && $parent.length ? $parent : $this.closest('.dac-toggle');
6187
6188 return $parent.length ? $parent : $this.parent();
6189 }
6190
6191 /**
6192 * Runs a transition of max-height along with responsive styles which hide or expand the element.
6193 * @param $el
6194 * @param visible
6195 */
6196 function transitionMaxHeight($el, visible) {
6197 var contentHeight = $el.prop('scrollHeight');
6198 var targetHeight = visible ? contentHeight : 0;
6199 var duration = $el.transitionDuration();
6200
6201 // If we're hiding, first set the maxHeight we're transitioning from.
6202 if (!visible) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08006203 $el.css({
6204 transitionDuration: '0s',
6205 maxHeight: contentHeight + 'px'
6206 })
6207 .resolveStyles()
6208 .css('transitionDuration', '');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006209 }
6210
6211 // Transition to new state
6212 $el.css('maxHeight', targetHeight);
6213
6214 // Reset maxHeight to css value after transition.
6215 setTimeout(function() {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08006216 $el.css({
6217 transitionDuration: '0s',
6218 maxHeight: ''
6219 })
6220 .resolveStyles()
6221 .css('transitionDuration', '');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006222 }, duration);
6223 }
6224
6225 // Utility to get the transition duration for the element.
6226 $.fn.transitionDuration = function() {
6227 var d = $(this).css('transitionDuration') || '0s';
6228
6229 return +(parseFloat(d) * (/ms/.test(d) ? 1 : 1000)).toFixed(0);
6230 };
6231
6232 // jQuery plugin
6233 $.fn.toggleSection = function(option) {
6234 return this.each(function() {
6235 var $this = $(this);
6236 var data = $this.data('dac.togglesection');
6237 if (!data) {$this.data('dac.togglesection', (data = new Toggle(this)));}
6238 if (typeof option === 'string') {data[option].call($this);}
6239 });
6240 };
6241
6242 // Data api
6243 $(document)
6244 .on('click.toggle', '[data-toggle="section"]', Toggle.prototype.toggle);
6245})(jQuery);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08006246
6247(function(window) {
6248 /**
6249 * Media query breakpoints. Should match CSS.
6250 */
6251 var BREAKPOINTS = {
6252 mobile: [0, 719],
6253 tablet: [720, 959],
6254 desktop: [960, 9999]
6255 };
6256
6257 /**
6258 * Fisher-Yates Shuffle (Knuth shuffle).
6259 * @param {Array} input
6260 * @returns {Array} shuffled array.
6261 */
6262 function shuffle(input) {
6263 for (var i = input.length; i >= 0; i--) {
6264 var randomIndex = Math.floor(Math.random() * (i + 1));
6265 var randomItem = input[randomIndex];
6266 input[randomIndex] = input[i];
6267 input[i] = randomItem;
6268 }
6269
6270 return input;
6271 }
6272
6273 /**
6274 * Matches media breakpoints like in CSS.
6275 * @param {string} form of either mobile, tablet or desktop.
6276 */
6277 function matchesMedia(form) {
6278 var breakpoint = BREAKPOINTS[form];
6279 return window.innerWidth >= breakpoint[0] && window.innerWidth <= breakpoint[1];
6280 }
6281
6282 window.util = {
6283 shuffle: shuffle,
6284 matchesMedia: matchesMedia
6285 };
6286})(window);
6287
6288(function($, window) {
6289 'use strict';
6290
6291 var YouTubePlayer = (function() {
6292 var player;
6293
6294 function VideoPlayer() {
6295 this.mPlayerPaused = false;
6296 this.doneSetup = false;
6297 }
6298
6299 VideoPlayer.prototype.setup = function() {
6300 // loads the IFrame Player API code asynchronously.
6301 $.getScript('https://www.youtube.com/iframe_api');
6302
6303 // Add the shadowbox HTML to the body
6304 $('body').prepend(
6305'<div id="video-player" class="Video">' +
6306 '<div id="video-overlay" class="Video-overlay" />' +
6307 '<div class="Video-container">' +
6308 '<div class="Video-frame">' +
6309 '<span class="Video-loading">Loading&hellip;</span>' +
6310 '<div id="youTubePlayer"></div>' +
6311 '</div>' +
6312 '<div class="Video-controls">' +
6313 '<button id="picture-in-picture" class="Video-button Video-button--picture-in-picture">' +
6314 '<button id="close-video" class="Video-button Video-button--close" />' +
6315 '</div>' +
6316 '</div>' +
6317'</div>');
6318
6319 this.videoPlayer = $('#video-player');
6320
6321 var pictureInPictureButton = this.videoPlayer.find('#picture-in-picture');
6322 pictureInPictureButton.on('click.aranja', this.toggleMinimizeVideo.bind(this));
6323
6324 var videoOverlay = this.videoPlayer.find('#video-overlay');
6325 var closeButton = this.videoPlayer.find('#close-video');
6326 var closeVideo = this.closeVideo.bind(this);
6327 videoOverlay.on('click.aranja', closeVideo);
6328 closeButton.on('click.aranja', closeVideo);
6329
6330 this.doneSetup = true;
6331 };
6332
6333 VideoPlayer.prototype.startYouTubePlayer = function(videoId) {
6334 this.videoPlayer.show();
6335
6336 if (!this.isLoaded) {
6337 this.queueVideo = videoId;
6338 return;
6339 }
6340
6341 this.mPlayerPaused = false;
6342 // check if we've already created this player
6343 if (!this.youTubePlayer) {
6344 // check if there's a start time specified
6345 var idAndHash = videoId.split('#');
6346 var startTime = 0;
6347 if (idAndHash.length > 1) {
6348 startTime = idAndHash[1].split('t=')[1] !== undefined ? idAndHash[1].split('t=')[1] : 0;
6349 }
6350 // enable localized player
6351 var lang = getLangPref();
6352 var captionsOn = lang === 'en' ? 0 : 1;
6353
6354 this.youTubePlayer = new YT.Player('youTubePlayer', {
6355 height: 720,
6356 width: 1280,
6357 videoId: idAndHash[0],
6358 // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
6359 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
6360 // jscs:enable
6361 events: {
6362 'onReady': this.onPlayerReady.bind(this),
6363 'onStateChange': this.onPlayerStateChange.bind(this)
6364 }
6365 });
6366 } else {
6367 // if a video different from the one already playing was requested, cue it up
6368 if (videoId !== this.getVideoId()) {
6369 this.youTubePlayer.cueVideoById(videoId);
6370 }
6371 this.youTubePlayer.playVideo();
6372 }
6373 };
6374
6375 VideoPlayer.prototype.onPlayerReady = function(event) {
6376 if (!isMobile) {
6377 event.target.playVideo();
6378 this.mPlayerPaused = false;
6379 }
6380 };
6381
6382 VideoPlayer.prototype.toggleMinimizeVideo = function(event) {
6383 event.stopPropagation();
6384 this.videoPlayer.toggleClass('Video--picture-in-picture');
6385 };
6386
6387 VideoPlayer.prototype.closeVideo = function() {
6388 try {
6389 this.youTubePlayer.pauseVideo();
6390 } catch (e) {
6391 }
6392 this.videoPlayer.fadeOut(200, function() {
6393 this.videoPlayer.removeClass('Video--picture-in-picture');
6394 }.bind(this));
6395 };
6396
6397 VideoPlayer.prototype.getVideoId = function() {
6398 // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
6399 return this.youTubePlayer && this.youTubePlayer.getVideoData().video_id;
6400 // jscs:enable
6401 };
6402
6403 /* Track youtube playback for analytics */
6404 VideoPlayer.prototype.onPlayerStateChange = function(event) {
6405 var videoId = this.getVideoId();
6406 var currentTime = this.youTubePlayer && this.youTubePlayer.getCurrentTime();
6407
6408 // Video starts, send the video ID
6409 if (event.data === YT.PlayerState.PLAYING) {
6410 if (this.mPlayerPaused) {
6411 ga('send', 'event', 'Videos', 'Resume', videoId);
6412 } else {
6413 // track the start playing event so we know from which page the video was selected
6414 ga('send', 'event', 'Videos', 'Start: ' + videoId, 'on: ' + document.location.href);
6415 }
6416 this.mPlayerPaused = false;
6417 }
6418
6419 // Video paused, send video ID and video elapsed time
6420 if (event.data === YT.PlayerState.PAUSED) {
6421 ga('send', 'event', 'Videos', 'Paused', videoId, currentTime);
6422 this.mPlayerPaused = true;
6423 }
6424
6425 // Video finished, send video ID and video elapsed time
6426 if (event.data === YT.PlayerState.ENDED) {
6427 ga('send', 'event', 'Videos', 'Finished', videoId, currentTime);
6428 this.mPlayerPaused = true;
6429 }
6430 };
6431
6432 return {
6433 getPlayer: function() {
6434 if (!player) {
6435 player = new VideoPlayer();
6436 }
6437
6438 return player;
6439 }
6440 };
6441 })();
6442
6443 var videoPlayer = YouTubePlayer.getPlayer();
6444
6445 window.onYouTubeIframeAPIReady = function() {
6446 videoPlayer.isLoaded = true;
6447
6448 if (videoPlayer.queueVideo) {
6449 videoPlayer.startYouTubePlayer(videoPlayer.queueVideo);
6450 }
6451 };
6452
6453 function wrapLinkInPlayer(e) {
6454 e.preventDefault();
6455
6456 if (!videoPlayer.doneSetup) {
6457 videoPlayer.setup();
6458 }
6459
6460 var videoIdMatches = $(e.currentTarget).attr('href').match(/(?:youtu.be\/|v=)([^&]*)/);
6461 var videoId = videoIdMatches && videoIdMatches[1];
6462
6463 if (videoId) {
6464 videoPlayer.startYouTubePlayer(videoId);
6465 }
6466 }
6467
6468 $(document).on('click.video', 'a[href*="youtube.com/watch"], a[href*="youtu.be"]', wrapLinkInPlayer);
6469})(jQuery, window);
6470
6471/**
6472 * Wide table
6473 *
6474 * Wraps tables in a scrollable area so you can read them on mobile.
6475 */
6476(function($) {
6477 function initWideTable() {
6478 $('table.jd-sumtable').each(function(i, table) {
6479 $(table).wrap('<div class="dac-expand wide-table">');
6480 });
6481 }
6482
6483 $(function() {
6484 initWideTable();
6485 });
6486})(jQuery);