blob: 9afed711ef17480e6c744ee03ee0a0bcfdd638b4 [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
Amanda Kassayebcf3972016-03-31 10:03:57 -040018 var pagePath = devsite ?
19 location.href.replace(location.hash, '') : document.location.pathname;
Dirk Dougherty541b4942014-02-14 18:31:53 -080020 // account for intl docs by removing the intl/*/ path
21 if (pagePath.indexOf("/intl/") == 0) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -080022 pagePath = pagePath.substr(pagePath.indexOf("/", 6)); // start after intl/ to get last /
Dirk Dougherty541b4942014-02-14 18:31:53 -080023 }
24
25 if (pagePath.indexOf(SITE_ROOT) == 0) {
26 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
27 pagePath += 'index.html';
28 }
29 }
30
31 // Need a copy of the pagePath before it gets changed in the next block;
32 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
33 var pagePathOriginal = pagePath;
34 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
35 // If running locally, SITE_ROOT will be a relative path, so account for that by
36 // finding the relative URL to this page. This will allow us to find links on the page
37 // leading back to this page.
38 var pathParts = pagePath.split('/');
39 var relativePagePathParts = [];
40 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
41 for (var i = 0; i < upDirs; i++) {
42 relativePagePathParts.push('..');
43 }
44 for (var i = 0; i < upDirs; i++) {
45 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
46 }
47 relativePagePathParts.push(pathParts[pathParts.length - 1]);
48 pagePath = relativePagePathParts.join('/');
49 } else {
50 // Otherwise the page path is already an absolute URL
51 }
52
Dirk Dougherty541b4942014-02-14 18:31:53 -080053 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
54 // and highlight the sidenav
55 mPagePath = pagePath;
56 highlightSidenav();
57
58 // set up prev/next links if they exist
59 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
60 var $selListItem;
61 if ($selNavLink.length) {
62 $selListItem = $selNavLink.closest('li');
63
64 // set up prev links
65 var $prevLink = [];
66 var $prevListItem = $selListItem.prev('li');
67
68 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
69false; // navigate across topic boundaries only in design docs
70 if ($prevListItem.length) {
Dirk Doughertyff233cc2015-05-04 14:37:05 -070071 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Dirk Dougherty541b4942014-02-14 18:31:53 -080072 // jump to last topic of previous section
73 $prevLink = $prevListItem.find('a:last');
74 } else if (!$selListItem.hasClass('nav-section')) {
75 // jump to previous topic in this section
76 $prevLink = $prevListItem.find('a:eq(0)');
77 }
78 } else {
79 // jump to this section's index page (if it exists)
80 var $parentListItem = $selListItem.parents('li');
81 $prevLink = $selListItem.parents('li').find('a');
82
83 // except if cross boundaries aren't allowed, and we're at the top of a section already
84 // (and there's another parent)
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -080085 if (!crossBoundaries && $parentListItem.hasClass('nav-section') &&
86 $selListItem.hasClass('nav-section')) {
Dirk Dougherty541b4942014-02-14 18:31:53 -080087 $prevLink = [];
88 }
89 }
90
91 // set up next links
92 var $nextLink = [];
93 var startClass = false;
Dirk Dougherty541b4942014-02-14 18:31:53 -080094 var isCrossingBoundary = false;
95
96 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
97 // we're on an index page, jump to the first topic
98 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
99
100 // if there aren't any children, go to the next section (required for About pages)
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800101 if ($nextLink.length == 0) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800102 $nextLink = $selListItem.next('li').find('a');
103 } else if ($('.topic-start-link').length) {
104 // as long as there's a child link and there is a "topic start link" (we're on a landing)
105 // then set the landing page "start link" text to be the first doc title
106 $('.topic-start-link').text($nextLink.text().toUpperCase());
107 }
108
109 // If the selected page has a description, then it's a class or article homepage
110 if ($selListItem.find('a[description]').length) {
111 // this means we're on a class landing page
112 startClass = true;
113 }
114 } else {
115 // jump to the next topic in this section (if it exists)
116 $nextLink = $selListItem.next('li').find('a:eq(0)');
117 if ($nextLink.length == 0) {
118 isCrossingBoundary = true;
119 // no more topics in this section, jump to the first topic in the next section
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700120 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800121 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
122 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
123 if ($nextLink.length == 0) {
124 // if that doesn't work, we're at the end of the list, so disable NEXT link
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800125 $('.next-page-link').attr('href', '').addClass("disabled")
Dirk Dougherty541b4942014-02-14 18:31:53 -0800126 .click(function() { return false; });
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700127 // and completely hide the one in the footer
128 $('.content-footer .next-page-link').hide();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800129 }
130 }
131 }
132 }
133
134 if (startClass) {
135 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
136
137 // if there's no training bar (below the start button),
138 // then we need to add a bottom border to button
139 if (!$("#tb").length) {
140 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
141 }
142 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
143 $('.content-footer.next-class').show();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800144 $('.next-page-link').attr('href', '')
Dirk Dougherty541b4942014-02-14 18:31:53 -0800145 .removeClass("hide").addClass("disabled")
146 .click(function() { return false; });
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700147 // and completely hide the one in the footer
148 $('.content-footer .next-page-link').hide();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800149 $('.content-footer .prev-page-link').hide();
150
Dirk Dougherty541b4942014-02-14 18:31:53 -0800151 if ($nextLink.length) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800152 $('.next-class-link').attr('href', $nextLink.attr('href'))
153 .removeClass("hide");
154
155 $('.content-footer .next-class-link').append($nextLink.html());
156
Dirk Dougherty541b4942014-02-14 18:31:53 -0800157 $('.next-class-link').find('.new').empty();
158 }
159 } else {
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700160 $('.next-page-link').attr('href', $nextLink.attr('href'))
161 .removeClass("hide");
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800162 // for the footer link, also add the previous and next page titles
163 $('.content-footer .prev-page-link').append($prevLink.html());
164 $('.content-footer .next-page-link').append($nextLink.html());
Dirk Dougherty541b4942014-02-14 18:31:53 -0800165 }
166
167 if (!startClass && $prevLink.length) {
168 var prevHref = $prevLink.attr('href');
169 if (prevHref == SITE_ROOT + 'index.html') {
170 // Don't show Previous when it leads to the homepage
171 } else {
172 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
173 }
174 }
175
Dirk Dougherty541b4942014-02-14 18:31:53 -0800176 }
177
Dirk Dougherty541b4942014-02-14 18:31:53 -0800178 // Set up the course landing pages for Training with class names and descriptions
179 if ($('body.trainingcourse').length) {
180 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700181
182 // create an array for all the class descriptions
183 var $classDescriptions = new Array($classLinks.length);
184 var lang = getLangPref();
185 $classLinks.each(function(index) {
186 var langDescr = $(this).attr(lang + "-description");
187 if (typeof langDescr !== 'undefined' && langDescr !== false) {
188 // if there's a class description in the selected language, use that
189 $classDescriptions[index] = langDescr;
190 } else {
191 // otherwise, use the default english description
192 $classDescriptions[index] = $(this).attr("description");
193 }
194 });
Dirk Dougherty541b4942014-02-14 18:31:53 -0800195
196 var $olClasses = $('<ol class="class-list"></ol>');
197 var $liClass;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800198 var $h2Title;
199 var $pSummary;
200 var $olLessons;
201 var $liLesson;
202 $classLinks.each(function(index) {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -0800203 $liClass = $('<li class="clearfix"></li>');
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800204 $h2Title = $('<a class="title" href="' + $(this).attr('href') + '"><h2 class="norule">' + $(this).html() + '</h2><span></span></a>');
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700205 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800206
207 $olLessons = $('<ol class="lesson-list"></ol>');
208
209 $lessons = $(this).closest('li').find('ul li a');
210
211 if ($lessons.length) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800212 $lessons.each(function(index) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800213 $olLessons.append('<li><a href="' + $(this).attr('href') + '">' + $(this).html() + '</a></li>');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800214 });
215 } else {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800216 $pSummary.addClass('article');
217 }
218
Dirk Dougherty0dc81b92015-12-08 14:49:52 -0800219 $liClass.append($h2Title).append($pSummary).append($olLessons);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800220 $olClasses.append($liClass);
221 });
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800222 $('#classes').append($olClasses);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800223 }
224
225 // Set up expand/collapse behavior
226 initExpandableNavItems("#nav");
227
Dirk Dougherty541b4942014-02-14 18:31:53 -0800228 // Set up play-on-hover <video> tags.
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800229 $('video.play-on-hover').bind('click', function() {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800230 $(this).get(0).load(); // in case the video isn't seekable
231 $(this).get(0).play();
232 });
233
234 // Set up tooltips
235 var TOOLTIP_MARGIN = 10;
236 $('acronym,.tooltip-link').each(function() {
237 var $target = $(this);
238 var $tooltip = $('<div>')
239 .addClass('tooltip-box')
240 .append($target.attr('title'))
241 .hide()
242 .appendTo('body');
243 $target.removeAttr('title');
244
245 $target.hover(function() {
246 // in
247 var targetRect = $target.offset();
248 targetRect.width = $target.width();
249 targetRect.height = $target.height();
250
251 $tooltip.css({
252 left: targetRect.left,
253 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
254 });
255 $tooltip.addClass('below');
256 $tooltip.show();
257 }, function() {
258 // out
259 $tooltip.hide();
260 });
261 });
262
263 // Set up <h2> deeplinks
264 $('h2').click(function() {
265 var id = $(this).attr('id');
266 if (id) {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -0800267 if (history && history.replaceState) {
268 // Change url without scrolling.
269 history.replaceState({}, '', '#' + id);
270 } else {
271 document.location.hash = id;
272 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800273 }
274 });
275
276 //Loads the +1 button
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800277 //var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
278 //po.src = 'https://apis.google.com/js/plusone.js';
279 //var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800280});
281// END of the onload event
282
Dirk Dougherty541b4942014-02-14 18:31:53 -0800283function initExpandableNavItems(rootTag) {
284 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
285 var section = $(this).closest('li.nav-section');
286 if (section.hasClass('expanded')) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800287 /* hide me and descendants */
Dirk Dougherty541b4942014-02-14 18:31:53 -0800288 section.find('ul').slideUp(250, function() {
289 // remove 'expanded' class from my section and any children
290 section.closest('li').removeClass('expanded');
291 $('li.nav-section', section).removeClass('expanded');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800292 });
293 } else {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800294 /* show me */
Dirk Dougherty541b4942014-02-14 18:31:53 -0800295 // first hide all other siblings
296 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
297 $others.removeClass('expanded').children('ul').slideUp(250);
298
299 // now expand me
300 section.closest('li').addClass('expanded');
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800301 section.children('ul').slideDown(250);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800302 }
303 });
304
305 // Stop expand/collapse behavior when clicking on nav section links
306 // (since we're navigating away from the page)
307 // This selector captures the first instance of <a>, but not those with "#" as the href.
308 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
309 window.location.href = $(this).attr('href');
310 return false;
311 });
312}
313
314/** Highlight the current page in sidenav, expanding children as appropriate */
315function highlightSidenav() {
316 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
317 if ($("ul#nav li.selected").length) {
318 unHighlightSidenav();
319 }
320 // look for URL in sidenav, including the hash
321 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
322
323 // If the selNavLink is still empty, look for it without the hash
324 if ($selNavLink.length == 0) {
325 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
326 }
327
328 var $selListItem;
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800329 var breadcrumb = [];
330
Dirk Dougherty541b4942014-02-14 18:31:53 -0800331 if ($selNavLink.length) {
332 // Find this page's <li> in sidenav and set selected
333 $selListItem = $selNavLink.closest('li');
334 $selListItem.addClass('selected');
335
336 // Traverse up the tree and expand all parent nav-sections
337 $selNavLink.parents('li.nav-section').each(function() {
338 $(this).addClass('expanded');
339 $(this).children('ul').show();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800340
341 var link = $(this).find('a').first();
342
343 if (!$(this).is($selListItem)) {
344 breadcrumb.unshift(link)
345 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800346 });
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800347
348 $('#nav').scrollIntoView($selNavLink);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800349 }
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800350
351 breadcrumb.forEach(function(link) {
352 link.dacCrumbs();
353 });
Dirk Dougherty541b4942014-02-14 18:31:53 -0800354}
355
356function unHighlightSidenav() {
357 $("ul#nav li.selected").removeClass("selected");
358 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
359}
360
Dirk Dougherty541b4942014-02-14 18:31:53 -0800361var agent = navigator['userAgent'].toLowerCase();
362// If a mobile phone, set flag and do mobile setup
363if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
364 (agent.indexOf("blackberry") != -1) ||
365 (agent.indexOf("webos") != -1) ||
366 (agent.indexOf("mini") != -1)) { // opera mini browsers
367 isMobile = true;
368}
369
Dirk Dougherty541b4942014-02-14 18:31:53 -0800370$(document).ready(function() {
371 $("pre:not(.no-pretty-print)").addClass("prettyprint");
372 prettyPrint();
373});
374
Dirk Dougherty541b4942014-02-14 18:31:53 -0800375/* Show popup dialogs */
376function showDialog(id) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800377 $dialog = $("#" + id);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800378 $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>');
379 $dialog.wrapInner('<div/>');
380 $dialog.removeClass("hide");
381}
382
Dirk Dougherty541b4942014-02-14 18:31:53 -0800383/* ######### COOKIES! ########## */
384
385function readCookie(cookie) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800386 var myCookie = cookie_namespace + "_" + cookie + "=";
Dirk Dougherty541b4942014-02-14 18:31:53 -0800387 if (document.cookie) {
388 var index = document.cookie.indexOf(myCookie);
389 if (index != -1) {
390 var valStart = index + myCookie.length;
391 var valEnd = document.cookie.indexOf(";", valStart);
392 if (valEnd == -1) {
393 valEnd = document.cookie.length;
394 }
395 var val = document.cookie.substring(valStart, valEnd);
396 return val;
397 }
398 }
399 return 0;
400}
401
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700402function writeCookie(cookie, val, section) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800403 if (val == undefined) return;
404 section = section == null ? "_" : "_" + section + "_";
405 var age = 2 * 365 * 24 * 60 * 60; // set max-age to 2 years
406 var cookieValue = cookie_namespace + section + cookie + "=" + val +
407 "; max-age=" + age + "; path=/";
Dirk Dougherty541b4942014-02-14 18:31:53 -0800408 document.cookie = cookieValue;
409}
410
411/* ######### END COOKIES! ########## */
412
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700413/*
414 * Manages secion card states and nav resize to conclude loading
Dirk Dougherty08032402014-02-15 10:14:35 -0800415 */
Dirk Dougherty08032402014-02-15 10:14:35 -0800416(function() {
417 $(document).ready(function() {
418
Dirk Dougherty08032402014-02-15 10:14:35 -0800419 // Stack hover states
420 $('.section-card-menu').each(function(index, el) {
421 var height = $(el).height();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800422 $(el).css({height:height + 'px', position:'relative'});
Dirk Dougherty08032402014-02-15 10:14:35 -0800423 var $cardInfo = $(el).find('.card-info');
424
425 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
426 });
427
Dirk Dougherty08032402014-02-15 10:14:35 -0800428 });
429
430})();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800431
Dirk Dougherty541b4942014-02-14 18:31:53 -0800432/* MISC LIBRARY FUNCTIONS */
433
Dirk Dougherty541b4942014-02-14 18:31:53 -0800434function toggle(obj, slide) {
435 var ul = $("ul:first", obj);
436 var li = ul.parent();
437 if (li.hasClass("closed")) {
438 if (slide) {
439 ul.slideDown("fast");
440 } else {
441 ul.show();
442 }
443 li.removeClass("closed");
444 li.addClass("open");
445 $(".toggle-img", li).attr("title", "hide pages");
446 } else {
447 ul.slideUp("fast");
448 li.removeClass("open");
449 li.addClass("closed");
450 $(".toggle-img", li).attr("title", "show pages");
451 }
452}
453
Dirk Dougherty541b4942014-02-14 18:31:53 -0800454function buildToggleLists() {
455 $(".toggle-list").each(
456 function(i) {
457 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
458 $(this).addClass("closed");
459 });
460}
461
Dirk Dougherty541b4942014-02-14 18:31:53 -0800462function hideNestedItems(list, toggle) {
463 $list = $(list);
464 // hide nested lists
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800465 if ($list.hasClass('showing')) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800466 $("li ol", $list).hide('fast');
467 $list.removeClass('showing');
468 // show nested lists
469 } else {
470 $("li ol", $list).show('fast');
471 $list.addClass('showing');
472 }
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800473 $(".more,.less", $(toggle)).toggle();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800474}
475
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700476/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
477function setupIdeDocToggle() {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800478 $("select.ide").change(function() {
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700479 var selected = $(this).find("option:selected").attr("value");
480 $(".select-ide").hide();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800481 $(".select-ide." + selected).show();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800482
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700483 $("select.ide").val(selected);
484 });
485}
Dirk Dougherty541b4942014-02-14 18:31:53 -0800486
Dirk Dougherty541b4942014-02-14 18:31:53 -0800487/* Used to hide and reveal supplemental content, such as long code samples.
488 See the companion CSS in android-developer-docs.css */
489function toggleContent(obj) {
490 var div = $(obj).closest(".toggle-content");
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800491 var toggleMe = $(".toggle-content-toggleme:eq(0)", div);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800492 if (div.hasClass("closed")) { // if it's closed, open it
493 toggleMe.slideDown();
494 $(".toggle-content-text:eq(0)", obj).toggle();
495 div.removeClass("closed").addClass("open");
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800496 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot +
497 "assets/images/triangle-opened.png");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800498 } else { // if it's open, close it
499 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
500 $(".toggle-content-text:eq(0)", obj).toggle();
501 div.removeClass("open").addClass("closed");
502 div.find(".toggle-content").removeClass("open").addClass("closed")
503 .find(".toggle-content-toggleme").hide();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800504 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot +
505 "assets/images/triangle-closed.png");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800506 });
507 }
508 return false;
509}
510
Dirk Dougherty541b4942014-02-14 18:31:53 -0800511/* New version of expandable content */
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800512function toggleExpandable(link, id) {
513 if ($(id).is(':visible')) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800514 $(id).slideUp();
515 $(link).removeClass('expanded');
516 } else {
517 $(id).slideDown();
518 $(link).addClass('expanded');
519 }
520}
521
522function hideExpandable(ids) {
523 $(ids).slideUp();
524 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
525}
526
Dirk Dougherty541b4942014-02-14 18:31:53 -0800527/*
528 * Slideshow 1.0
529 * Used on /index.html and /develop/index.html for carousel
530 *
531 * Sample usage:
532 * HTML -
533 * <div class="slideshow-container">
534 * <a href="" class="slideshow-prev">Prev</a>
535 * <a href="" class="slideshow-next">Next</a>
536 * <ul>
537 * <li class="item"><img src="images/marquee1.jpg"></li>
538 * <li class="item"><img src="images/marquee2.jpg"></li>
539 * <li class="item"><img src="images/marquee3.jpg"></li>
540 * <li class="item"><img src="images/marquee4.jpg"></li>
541 * </ul>
542 * </div>
543 *
544 * <script type="text/javascript">
545 * $('.slideshow-container').dacSlideshow({
546 * auto: true,
547 * btnPrev: '.slideshow-prev',
548 * btnNext: '.slideshow-next'
549 * });
550 * </script>
551 *
552 * Options:
553 * btnPrev: optional identifier for previous button
554 * btnNext: optional identifier for next button
555 * btnPause: optional identifier for pause button
556 * auto: whether or not to auto-proceed
557 * speed: animation speed
558 * autoTime: time between auto-rotation
559 * easing: easing function for transition
560 * start: item to select by default
561 * scroll: direction to scroll in
562 * pagination: whether or not to include dotted pagination
563 *
564 */
565
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800566(function($) {
567 $.fn.dacSlideshow = function(o) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800568
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800569 //Options - see above
570 o = $.extend({
571 btnPrev: null,
572 btnNext: null,
573 btnPause: null,
574 auto: true,
575 speed: 500,
576 autoTime: 12000,
577 easing: null,
578 start: 0,
579 scroll: 1,
580 pagination: true
Dirk Dougherty541b4942014-02-14 18:31:53 -0800581
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800582 }, o || {});
Dirk Dougherty541b4942014-02-14 18:31:53 -0800583
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800584 //Set up a carousel for each
585 return this.each(function() {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800586
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800587 var running = false;
588 var animCss = o.vertical ? "top" : "left";
589 var sizeCss = o.vertical ? "height" : "width";
590 var div = $(this);
591 var ul = $("ul", div);
592 var tLi = $("li", ul);
593 var tl = tLi.size();
594 var timer = null;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800595
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800596 var li = $("li", ul);
597 var itemLength = li.size();
598 var curr = o.start;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800599
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800600 li.css({float: o.vertical ? "none" : "left"});
601 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
602 div.css({position: "relative", "z-index": "2", left: "0px"});
Dirk Dougherty541b4942014-02-14 18:31:53 -0800603
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800604 var liSize = o.vertical ? height(li) : width(li);
605 var ulSize = liSize * itemLength;
606 var divSize = liSize;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800607
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800608 li.css({width: li.width(), height: li.height()});
609 ul.css(sizeCss, ulSize + "px").css(animCss, -(curr * liSize));
Dirk Dougherty541b4942014-02-14 18:31:53 -0800610
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800611 div.css(sizeCss, divSize + "px");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800612
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800613 //Pagination
614 if (o.pagination) {
615 var pagination = $("<div class='pagination'></div>");
616 var pag_ul = $("<ul></ul>");
617 if (tl > 1) {
618 for (var i = 0; i < tl; i++) {
619 var li = $("<li>" + i + "</li>");
620 pag_ul.append(li);
621 if (i == o.start) li.addClass('active');
622 li.click(function() {
623 go(parseInt($(this).text()));
624 })
625 }
626 pagination.append(pag_ul);
627 div.append(pagination);
628 }
629 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800630
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800631 //Previous button
632 if (o.btnPrev)
Dirk Dougherty541b4942014-02-14 18:31:53 -0800633 $(o.btnPrev).click(function(e) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800634 e.preventDefault();
635 return go(curr - o.scroll);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800636 });
637
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800638 //Next button
639 if (o.btnNext)
Dirk Dougherty541b4942014-02-14 18:31:53 -0800640 $(o.btnNext).click(function(e) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800641 e.preventDefault();
642 return go(curr + o.scroll);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800643 });
644
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800645 //Pause button
646 if (o.btnPause)
Dirk Dougherty541b4942014-02-14 18:31:53 -0800647 $(o.btnPause).click(function(e) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800648 e.preventDefault();
649 if ($(this).hasClass('paused')) {
650 startRotateTimer();
651 } else {
652 pauseRotateTimer();
653 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800654 });
655
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800656 //Auto rotation
657 if (o.auto) startRotateTimer();
Dirk Dougherty541b4942014-02-14 18:31:53 -0800658
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800659 function startRotateTimer() {
660 clearInterval(timer);
661 timer = setInterval(function() {
662 if (curr == tl - 1) {
663 go(0);
664 } else {
665 go(curr + o.scroll);
666 }
667 }, o.autoTime);
668 $(o.btnPause).removeClass('paused');
669 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800670
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800671 function pauseRotateTimer() {
672 clearInterval(timer);
673 $(o.btnPause).addClass('paused');
674 }
Dirk Dougherty541b4942014-02-14 18:31:53 -0800675
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800676 //Go to an item
677 function go(to) {
678 if (!running) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800679
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800680 if (to < 0) {
681 to = itemLength - 1;
682 } else if (to > itemLength - 1) {
683 to = 0;
684 }
685 curr = to;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800686
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800687 running = true;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800688
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800689 ul.animate(
690 animCss == "left" ? {left: -(curr * liSize)} : {top: -(curr * liSize)} , o.speed, o.easing,
Dirk Dougherty541b4942014-02-14 18:31:53 -0800691 function() {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800692 running = false;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800693 }
694 );
695
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800696 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
697 $((curr - o.scroll < 0 && o.btnPrev) ||
698 (curr + o.scroll > itemLength && o.btnNext) ||
699 []
700 ).addClass("disabled");
Dirk Dougherty541b4942014-02-14 18:31:53 -0800701
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800702 var nav_items = $('li', pagination);
703 nav_items.removeClass('active');
704 nav_items.eq(to).addClass('active');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800705
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800706 }
707 if (o.auto) startRotateTimer();
708 return false;
709 };
710 });
711 };
Dirk Dougherty541b4942014-02-14 18:31:53 -0800712
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800713 function css(el, prop) {
714 return parseInt($.css(el[0], prop)) || 0;
715 };
716 function width(el) {
717 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
718 };
719 function height(el) {
720 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
721 };
Dirk Dougherty541b4942014-02-14 18:31:53 -0800722
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800723})(jQuery);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800724
725/*
726 * dacSlideshow 1.0
727 * Used on develop/index.html for side-sliding tabs
728 *
729 * Sample usage:
730 * HTML -
731 * <div class="slideshow-container">
732 * <a href="" class="slideshow-prev">Prev</a>
733 * <a href="" class="slideshow-next">Next</a>
734 * <ul>
735 * <li class="item"><img src="images/marquee1.jpg"></li>
736 * <li class="item"><img src="images/marquee2.jpg"></li>
737 * <li class="item"><img src="images/marquee3.jpg"></li>
738 * <li class="item"><img src="images/marquee4.jpg"></li>
739 * </ul>
740 * </div>
741 *
742 * <script type="text/javascript">
743 * $('.slideshow-container').dacSlideshow({
744 * auto: true,
745 * btnPrev: '.slideshow-prev',
746 * btnNext: '.slideshow-next'
747 * });
748 * </script>
749 *
750 * Options:
751 * btnPrev: optional identifier for previous button
752 * btnNext: optional identifier for next button
753 * auto: whether or not to auto-proceed
754 * speed: animation speed
755 * autoTime: time between auto-rotation
756 * easing: easing function for transition
757 * start: item to select by default
758 * scroll: direction to scroll in
759 * pagination: whether or not to include dotted pagination
760 *
761 */
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800762(function($) {
763 $.fn.dacTabbedList = function(o) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800764
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800765 //Options - see above
766 o = $.extend({
767 speed : 250,
768 easing: null,
769 nav_id: null,
770 frame_id: null
771 }, o || {});
Dirk Dougherty541b4942014-02-14 18:31:53 -0800772
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800773 //Set up a carousel for each
774 return this.each(function() {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800775
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800776 var curr = 0;
777 var running = false;
778 var animCss = "margin-left";
779 var sizeCss = "width";
780 var div = $(this);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800781
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800782 var nav = $(o.nav_id, div);
783 var nav_li = $("li", nav);
784 var nav_size = nav_li.size();
785 var frame = div.find(o.frame_id);
786 var content_width = $(frame).find('ul').width();
787 //Buttons
788 $(nav_li).click(function(e) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800789 go($(nav_li).index($(this)));
790 })
791
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800792 //Go to an item
793 function go(to) {
794 if (!running) {
795 curr = to;
796 running = true;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800797
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800798 frame.animate({'margin-left' : -(curr * content_width)}, o.speed, o.easing,
Dirk Dougherty541b4942014-02-14 18:31:53 -0800799 function() {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800800 running = false;
Dirk Dougherty541b4942014-02-14 18:31:53 -0800801 }
802 );
803
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800804 nav_li.removeClass('active');
805 nav_li.eq(to).addClass('active');
Dirk Dougherty541b4942014-02-14 18:31:53 -0800806
Dirk Dougherty541b4942014-02-14 18:31:53 -0800807 }
808 return false;
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800809 };
Dirk Dougherty541b4942014-02-14 18:31:53 -0800810 });
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800811 };
Dirk Dougherty541b4942014-02-14 18:31:53 -0800812
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800813 function css(el, prop) {
814 return parseInt($.css(el[0], prop)) || 0;
815 };
816 function width(el) {
817 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
818 };
819 function height(el) {
820 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
821 };
Dirk Dougherty541b4942014-02-14 18:31:53 -0800822
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800823})(jQuery);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800824
825/* ######################################################## */
826/* ################# JAVADOC REFERENCE ################### */
827/* ######################################################## */
828
829/* Initialize some droiddoc stuff, but only if we're in the reference */
830if (location.pathname.indexOf("/reference") == 0) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800831 if (!(location.pathname.indexOf("/reference-gms/packages.html") == 0) &&
832 !(location.pathname.indexOf("/reference-gcm/packages.html") == 0) &&
833 !(location.pathname.indexOf("/reference/com/google") == 0)) {
Dirk Dougherty541b4942014-02-14 18:31:53 -0800834 $(document).ready(function() {
835 // init available apis based on user pref
836 changeApiLevel();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800837 });
Dirk Dougherty541b4942014-02-14 18:31:53 -0800838 }
839}
840
841var API_LEVEL_COOKIE = "api_level";
842var minLevel = 1;
843var maxLevel = 1;
844
Dirk Dougherty541b4942014-02-14 18:31:53 -0800845function buildApiLevelSelector() {
846 maxLevel = SINCE_DATA.length;
847 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
848 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
849
850 minLevel = parseInt($("#doc-api-level").attr("class"));
851 // Handle provisional api levels; the provisional level will always be the highest possible level
852 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
853 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
854 if (isNaN(minLevel) && minLevel.length) {
855 minLevel = maxLevel;
856 }
857 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800858 for (var i = maxLevel - 1; i >= 0; i--) {
859 var option = $("<option />").attr("value", "" + SINCE_DATA[i]).append("" + SINCE_DATA[i]);
860 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
Dirk Dougherty541b4942014-02-14 18:31:53 -0800861 select.append(option);
862 }
863
864 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800865 var selectedLevelItem = $("#apiLevelSelector option[value='" + userApiLevel + "']").get(0);
866 selectedLevelItem.setAttribute('selected', true);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800867}
868
869function changeApiLevel() {
870 maxLevel = SINCE_DATA.length;
871 var selectedLevel = maxLevel;
872
873 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
874 toggleVisisbleApis(selectedLevel, "body");
875
Dirk Doughertyff233cc2015-05-04 14:37:05 -0700876 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Dirk Dougherty541b4942014-02-14 18:31:53 -0800877
878 if (selectedLevel < minLevel) {
smain@google.coma0a6d322016-02-05 17:34:22 -0800879 $("#naMessage").show().html("<div><p><strong>This API" +
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800880 " 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";
smain@google.coma0a6d322016-02-05 17:34:22 -08001107 trigger.src = toRoot + "assets/images/styles/disclosure_up.png";
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001108 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";
smain@google.coma0a6d322016-02-05 17:34:22 -08001113 trigger.src = toRoot + "assets/images/styles/disclosure_down.png";
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08001114 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() {
smain@google.com1c8d4a42016-02-26 16:30:50 -08003374 var lang = window.readCookie('pref_lang');
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003375 if (lang === 0 || LANGUAGES.indexOf(lang) === -1) {
3376 lang = 'en';
3377 }
3378 return lang;
3379 })();
3380 var localeTarget = (function() {
3381 var localeTarget = locale;
Amanda Kassayebcf3972016-03-31 10:03:57 -04003382 if (window.devsite) {
3383 if (getQueryVariable('hl')) {
3384 var target = getQueryVariable('hl');
3385 if (!(target === 0) || (LANGUAGES.indexOf(target) === -1)) {
3386 localeTarget = target;
3387 }
3388 }
3389 } else {
3390 if (location.pathname.substring(0,6) == "/intl/") {
3391 var target = location.pathname.split('/')[2];
3392 if (!(target === 0) || (LANGUAGES.indexOf(target) === -1)) {
3393 localeTarget = target;
3394 }
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003395 }
3396 }
Amanda Kassayebcf3972016-03-31 10:03:57 -04003397
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003398 return localeTarget;
3399 })();
3400
3401 /**
3402 * Global function shims for backwards compatibility
3403 */
3404 window.changeNavLang = function() {
3405 // Already done.
3406 };
3407
3408 window.loadLangPref = function() {
3409 // Languages pref already loaded.
3410 };
3411
3412 window.getLangPref = function() {
3413 return locale;
3414 };
3415
3416 window.getLangTarget = function() {
3417 return localeTarget;
3418 };
3419
3420 // Expose polyglot instance for advanced localization.
3421 var polyglot = window.polyglot = new window.Polyglot({
3422 locale: locale,
3423 phrases: PHRASES
3424 });
3425
3426 // When DOM is ready.
3427 $(function() {
3428 // Mark current locale in language picker.
3429 $('#language').find('option[value="' + locale + '"]').attr('selected', true);
3430
3431 $('html').dacTranslate().on('dac:domchange', function(e) {
3432 $(e.target).dacTranslate();
3433 });
3434 });
3435
3436 $.fn.dacTranslate = function() {
3437 // Translate strings in template markup:
3438
3439 // OLD
3440 // Having all translations in HTML does not scale well and bloats every page.
3441 // Need to migrate this to data-l JS translations below.
3442 if (locale !== 'en') {
3443 var $links = this.find('a[' + locale + '-lang]');
3444 $links.each(function() { // for each link with a translation
3445 var $link = $(this);
3446 // put the desired language from the attribute as the text
3447 $link.text($link.attr(locale + '-lang'));
3448 });
3449 }
3450
3451 // NEW
3452 // A simple declarative api for JS translations. Feel free to extend as appropriate.
3453
3454 // Miscellaneous string compilations
3455 // Build full strings from localized substrings:
3456 var myLocaleTarget = window.getLangTarget();
3457 var myTargetLang = window.polyglot.t("newsletter.languageValTarget." + myLocaleTarget);
3458 var myLang = window.polyglot.t("newsletter.languageVal");
3459 var myTargetLangTitleString = window.polyglot.t("newsletter.resetLangTitle", {targetLang: myTargetLang});
3460 var myResetLangTextIntro = window.polyglot.t("newsletter.resetLangTextIntro", {targetLang: myTargetLang, lang: myLang});
3461 var myResetLangTextCta = window.polyglot.t("newsletter.resetLangTextCta", {targetLang: myTargetLang});
3462 //var myResetLangButtonYes = window.polyglot.t("newsletter.resetLangButtonYes", {targetLang: myTargetLang});
3463
3464 // Inject strings as text values in dialog components:
3465 $("#langform .dac-modal-header-title").text(myTargetLangTitleString);
3466 $("#langform #resetLangText").text(myResetLangTextIntro);
3467 $("#langform #resetLangCta").text(myResetLangTextCta);
3468 //$("#resetLangButtonYes").attr("data-t", window.polyglot.t(myResetLangButtonYes));
3469
3470 // Text: <div data-t="nav.home"></div>
3471 // HTML: <div data-t="privacy" data-t-html></html>
3472 this.find('[data-t]').each(function() {
3473 var el = $(this);
3474 var data = el.data();
3475 if (data.t) {
3476 el[data.tHtml === '' ? 'html' : 'text'](polyglot.t(data.t));
3477 }
3478 });
3479
3480 return this;
3481 };
3482})();
3483/* ########## END LOCALIZATION ############ */
3484
3485// Translations. These should eventually be moved into language-specific files and loaded on demand.
3486// jshint nonbsp:false
3487switch (window.getLangPref()) {
3488 case 'ar':
3489 window.polyglot.extend({
3490 'newsletter': {
3491 'title': 'Google Play. يمكنك الحصول على آخر الأخبار والنصائح من مطوّري تطبيقات Android، مما يساعدك ' +
3492 'على تحقيق النجاح على',
3493 'requiredHint': '* حقول مطلوبة',
3494 'name': '. الاسم بالكامل ',
3495 'email': '. عنوان البريد الإلكتروني ',
3496 'company': '. اسم الشركة / اسم مطوّر البرامج',
3497 'appUrl': '. أحد عناوين URL لتطبيقاتك في متجر Play',
3498 'business': {
3499 'label': '. ما العنصر الذي يوضح طبيعة نشاطك التجاري بدقة؟ ',
3500 'apps': 'التطبيقات',
3501 'games': 'الألعاب',
3502 'both': 'التطبيقات والألعاب'
3503 },
3504 'confirmMailingList': 'إضافتي إلى القائمة البريدية للنشرة الإخبارية الشهرية والرسائل الإلكترونية التي يتم' +
3505 ' إرسالها من حين لآخر بشأن التطوير وفرص Google Play.',
3506 'privacyPolicy': 'أقر بأن المعلومات المقدَّمة في هذا النموذج تخضع لسياسة خصوصية ' +
3507 '<a href="https://www.google.com/intl/ar/policies/privacy/" target="_blank">Google</a>.',
3508 'languageVal': 'Arabic (العربيّة)',
3509 'successTitle': 'رائع!',
3510 'successDetails': 'لقد اشتركت بنجاح للحصول على آخر الأخبار والنصائح من مطوّري برامج Android.'
3511 }
3512 });
3513 break;
3514 case 'zh-cn':
3515 window.polyglot.extend({
3516 'newsletter': {
3517 'title': '获取最新的 Android 开发者资讯和提示,助您在 Google Play 上取得成功。',
3518 'requiredHint': '* 必填字段',
3519 'name': '全名',
3520 'email': '电子邮件地址',
3521 'company': '公司/开发者名称',
3522 'appUrl': '您的某个 Play 商店应用网址',
3523 'business': {
3524 'label': '哪一项能够最准确地描述您的业务?',
3525 'apps': '应用',
3526 'games': '游戏',
3527 'both': '应用和游戏'
3528 },
3529 'confirmMailingList': '将我添加到邮寄名单,以便接收每月简报以及不定期发送的关于开发和 Google Play 商机的电子邮件。',
3530 'privacyPolicy': '我确认自己了解在此表单中提供的信息受 <a href="https://www.google.com/intl/zh-CN/' +
3531 'policies/privacy/" target="_blank">Google</a> 隐私权政策的约束。',
3532 'languageVal': 'Simplified Chinese (简体中文)',
3533 'successTitle': '太棒了!',
3534 'successDetails': '您已成功订阅最新的 Android 开发者资讯和提示。'
3535 }
3536 });
3537 break;
3538 case 'zh-tw':
3539 window.polyglot.extend({
3540 'newsletter': {
3541 'title': '獲得 Android 開發人員的最新消息和各項秘訣,讓您在 Google Play 上輕鬆邁向成功之路。',
3542 'requiredHint': '* 必要欄位',
3543 'name': '全名',
3544 'email': '電子郵件地址',
3545 'company': '公司/開發人員名稱',
3546 'appUrl': '您其中一個 Play 商店應用程式的網址',
3547 'business': {
3548 'label': '為您的商家選取最合適的產品類別。',
3549 'apps': '應用程式',
3550 'games': '遊戲',
3551 'both': '應用程式和遊戲'
3552 },
3553 'confirmMailingList': '我想加入 Google Play 的郵寄清單,以便接收每月電子報和 Google Play 不定期寄送的電子郵件,' +
3554 '瞭解關於開發和 Google Play 商機的資訊。',
3555 'privacyPolicy': '我瞭解,我在這張表單中提供的資訊將受到 <a href="' +
3556 'https://www.google.com/intl/zh-TW/policies/privacy/" target="_blank">Google</a> 隱私權政策.',
3557 'languageVal': 'Traditional Chinese (繁體中文)',
3558 'successTitle': '太棒了!',
3559 'successDetails': '您已經成功訂閱 Android 開發人員的最新消息和各項秘訣。'
3560 }
3561 });
3562 break;
3563 case 'fr':
3564 window.polyglot.extend({
3565 'newsletter': {
3566 'title': 'Recevez les dernières actualités destinées aux développeurs Android, ainsi que des conseils qui ' +
3567 'vous mèneront vers le succès sur Google Play.',
3568 'requiredHint': '* Champs obligatoires',
3569 'name': 'Nom complet',
3570 'email': 'Adresse e-mail',
3571 'company': 'Nom de la société ou du développeur',
3572 'appUrl': 'Une de vos URL Play Store',
3573 'business': {
3574 'label': 'Quelle option décrit le mieux votre activité ?',
3575 'apps': 'Applications',
3576 'games': 'Jeux',
3577 'both': 'Applications et jeux'
3578 },
3579 'confirmMailingList': 'Ajoutez-moi à la liste de diffusion de la newsletter mensuelle et tenez-moi informé ' +
3580 'par des e-mails occasionnels de l\'évolution et des opportunités de Google Play.',
3581 'privacyPolicy': 'Je comprends que les renseignements fournis dans ce formulaire seront soumis aux <a href="' +
3582 'https://www.google.com/intl/fr/policies/privacy/" target="_blank">règles de confidentialité</a> de Google.',
3583 'languageVal': 'French (français)',
3584 'successTitle': 'Super !',
3585 'successDetails': 'Vous êtes bien inscrit pour recevoir les actualités et les conseils destinés aux ' +
3586 'développeurs Android.'
3587 }
3588 });
3589 break;
3590 case 'de':
3591 window.polyglot.extend({
3592 'newsletter': {
3593 'title': 'Abonniere aktuelle Informationen und Tipps für Android-Entwickler und werde noch erfolgreicher ' +
3594 'bei Google Play.',
3595 'requiredHint': '* Pflichtfelder',
3596 'name': 'Vollständiger Name',
3597 'email': 'E-Mail-Adresse',
3598 'company': 'Unternehmens-/Entwicklername',
3599 'appUrl': 'Eine der URLs deiner Play Store App',
3600 'business': {
3601 'label': 'Welche der folgenden Kategorien beschreibt dein Unternehmen am besten?',
3602 'apps': 'Apps',
3603 'games': 'Spiele',
3604 'both': 'Apps und Spiele'
3605 },
3606 'confirmMailingList': 'Meine E-Mail-Adresse soll zur Mailingliste hinzugefügt werden, damit ich den ' +
3607 'monatlichen Newsletter sowie gelegentlich E-Mails zu Entwicklungen und Optionen bei Google Play erhalte.',
3608 'privacyPolicy': 'Ich bestätige, dass die in diesem Formular bereitgestellten Informationen gemäß der ' +
3609 '<a href="https://www.google.com/intl/de/policies/privacy/" target="_blank">Datenschutzerklärung</a> von ' +
3610 'Google verwendet werden dürfen.',
3611 'languageVal': 'German (Deutsch)',
3612 'successTitle': 'Super!',
3613 'successDetails': 'Du hast dich erfolgreich angemeldet und erhältst jetzt aktuelle Informationen und Tipps ' +
3614 'für Android-Entwickler.'
3615 }
3616 });
3617 break;
3618 case 'in':
3619 window.polyglot.extend({
3620 'newsletter': {
3621 'title': 'Receba as dicas e as notícias mais recentes para os desenvolvedores Android e seja bem-sucedido ' +
3622 'no Google Play.',
3623 'requiredHint': '* Bidang Wajib Diisi',
3624 'name': 'Nama lengkap',
3625 'email': 'Alamat email',
3626 'company': 'Nama pengembang / perusahaan',
3627 'appUrl': 'Salah satu URL aplikasi Play Store Anda',
3628 'business': {
3629 'label': 'Dari berikut ini, mana yang paling cocok dengan bisnis Anda?',
3630 'apps': 'Aplikasi',
3631 'games': 'Game',
3632 'both': 'Aplikasi dan Game'
3633 },
3634 'confirmMailingList': 'Tambahkan saya ke milis untuk mendapatkan buletin bulanan dan email sesekali mengenai ' +
3635 'perkembangan dan kesempatan yang ada di Google Play.',
3636 'privacyPolicy': 'Saya memahami bahwa informasi yang diberikan dalam formulir ini tunduk pada <a href="' +
3637 'https://www.google.com/intl/in/policies/privacy/" target="_blank">kebijakan privasi</a> Google.',
3638 'languageVal': 'Indonesian (Bahasa)',
3639 'successTitle': 'Hore!',
3640 'successDetails': 'Anda berhasil mendaftar untuk kiat dan berita pengembang Android terbaru.'
3641 }
3642 });
3643 break;
3644 case 'it':
3645 //window.polyglot.extend({
3646 // 'newsletter': {
3647 // 'title': 'Receba as dicas e as notícias mais recentes para os desenvolvedores Android e seja bem-sucedido ' +
3648 // 'no Google Play.',
3649 // 'requiredHint': '* Campos obrigatórios',
3650 // 'name': 'Nome completo',
3651 // 'email': 'Endereço de Email',
3652 // 'company': 'Nome da empresa / do desenvolvedor',
3653 // 'appUrl': 'URL de um dos seus apps da Play Store',
3654 // 'business': {
3655 // 'label': 'Qual das seguintes opções melhor descreve sua empresa?',
3656 // 'apps': 'Apps',
3657 // 'games': 'Jogos',
3658 // 'both': 'Apps e Jogos'
3659 // },
3660 // 'confirmMailingList': 'Inscreva-me na lista de e-mails para que eu receba o boletim informativo mensal, ' +
3661 // 'bem como e-mails ocasionais sobre o desenvolvimento e as oportunidades do Google Play.',
3662 // 'privacyPolicy': 'Reconheço que as informações fornecidas neste formulário estão sujeitas à <a href="' +
3663 // 'https://www.google.com.br/policies/privacy/" target="_blank">Política de Privacidade</a> do Google.',
3664 // 'languageVal': 'Italian (italiano)',
3665 // 'successTitle': 'Uhu!',
3666 // 'successDetails': 'Você se inscreveu para receber as notícias e as dicas mais recentes para os ' +
3667 // 'desenvolvedores Android.',
3668 // }
3669 //});
3670 break;
3671 case 'ja':
3672 window.polyglot.extend({
3673 'newsletter': {
3674 'title': 'Google Play での成功に役立つ Android デベロッパー向けの最新ニュースやおすすめの情報をお届けします。',
3675 'requiredHint': '* 必須',
3676 'name': '氏名',
3677 'email': 'メールアドレス',
3678 'company': '会社名 / デベロッパー名',
3679 'appUrl': 'Play ストア アプリの URL(いずれか 1 つ)',
3680 'business': {
3681 'label': 'お客様のビジネスに最もよく当てはまるものをお選びください。',
3682 'apps': 'アプリ',
3683 'games': 'ゲーム',
3684 'both': 'アプリとゲーム'
3685 },
3686 'confirmMailingList': '開発や Google Play の最新情報に関する毎月発行のニュースレターや不定期発行のメールを受け取る',
3687 'privacyPolicy': 'このフォームに入力した情報に <a href="https://www.google.com/intl/ja/policies/privacy/" ' +
3688 'target="_blank">Google</a> のプライバシー ポリシーが適用',
3689 'languageVal': 'Japanese (日本語)',
3690 'successTitle': '完了です!',
3691 'successDetails': 'Android デベロッパー向けの最新ニュースやおすすめの情報の配信登録が完了しました。'
3692 }
3693 });
3694 break;
3695 case 'ko':
3696 window.polyglot.extend({
3697 'newsletter': {
3698 'title': 'Google Play에서 성공을 거두는 데 도움이 되는 최신 Android 개발자 소식 및 도움말을 받아 보세요.',
3699 'requiredHint': '* 필수 입력란',
3700 'name': '이름',
3701 'email': '이메일 주소',
3702 'company': '회사/개발자 이름',
3703 'appUrl': 'Play 스토어 앱 URL 중 1개',
3704 'business': {
3705 'label': '다음 중 내 비즈니스를 가장 잘 설명하는 단어는 무엇인가요?',
3706 'apps': '앱',
3707 'games': '게임',
3708 'both': '앱 및 게임'
3709 },
3710 'confirmMailingList': '개발 및 Google Play 관련 소식에 관한 월별 뉴스레터 및 비정기 이메일을 받아보겠습니다.',
3711 'privacyPolicy': '이 양식에 제공한 정보는 <a href="https://www.google.com/intl/ko/policies/privacy/" ' +
3712 'target="_blank">Google의</a> 개인정보취급방침에 따라 사용됨을',
3713 'languageVal':'Korean (한국어)',
3714 'successTitle': '축하합니다!',
3715 'successDetails': '최신 Android 개발자 뉴스 및 도움말을 받아볼 수 있도록 가입을 완료했습니다.'
3716 }
3717 });
3718 break;
3719 case 'pt-br':
3720 window.polyglot.extend({
3721 'newsletter': {
3722 'title': 'Receba as dicas e as notícias mais recentes para os desenvolvedores Android e seja bem-sucedido ' +
3723 'no Google Play.',
3724 'requiredHint': '* Campos obrigatórios',
3725 'name': 'Nome completo',
3726 'email': 'Endereço de Email',
3727 'company': 'Nome da empresa / do desenvolvedor',
3728 'appUrl': 'URL de um dos seus apps da Play Store',
3729 'business': {
3730 'label': 'Qual das seguintes opções melhor descreve sua empresa?',
3731 'apps': 'Apps',
3732 'games': 'Jogos',
3733 'both': 'Apps e Jogos'
3734 },
3735 'confirmMailingList': 'Inscreva-me na lista de e-mails para que eu receba o boletim informativo mensal, ' +
3736 'bem como e-mails ocasionais sobre o desenvolvimento e as oportunidades do Google Play.',
3737 'privacyPolicy': 'Reconheço que as informações fornecidas neste formulário estão sujeitas à <a href="' +
3738 'https://www.google.com.br/policies/privacy/" target="_blank">Política de Privacidade</a> do Google.',
3739 'languageVal': 'Brazilian Portuguese (Português Brasileiro)',
3740 'successTitle': 'Uhu!',
3741 'successDetails': 'Você se inscreveu para receber as notícias e as dicas mais recentes para os ' +
3742 'desenvolvedores Android.'
3743 }
3744 });
3745 break;
3746 case 'ru':
3747 window.polyglot.extend({
3748 'newsletter': {
3749 'title': 'Хотите получать последние новости и советы для разработчиков Google Play? Заполните эту форму.',
3750 'requiredHint': '* Обязательные поля',
3751 'name': 'Полное имя',
3752 'email': 'Адрес электронной почты',
3753 'company': 'Название компании или имя разработчика',
3754 'appUrl': 'Ссылка на любое ваше приложение в Google Play',
3755 'business': {
3756 'label': 'Что вы создаете?',
3757 'apps': 'Приложения',
3758 'games': 'Игры',
3759 'both': 'Игры и приложения'
3760 },
3761 'confirmMailingList': 'Я хочу получать ежемесячную рассылку для разработчиков и другие полезные новости ' +
3762 'Google Play.',
3763 'privacyPolicy': 'Я предоставляю эти данные в соответствии с <a href="' +
3764 'https://www.google.com/intl/ru/policies/privacy/" target="_blank">Политикой конфиденциальности</a> Google.',
3765 'languageVal': 'Russian (Русский)',
3766 'successTitle': 'Поздравляем!',
3767 'successDetails': 'Теперь вы подписаны на последние новости и советы для разработчиков Android.'
3768 }
3769 });
3770 break;
3771 case 'es':
3772 window.polyglot.extend({
3773 'newsletter': {
3774 'title': 'Recibe las últimas noticias y sugerencias para programadores de Android y logra tener éxito en ' +
3775 'Google Play.',
3776 'requiredHint': '* Campos obligatorios',
3777 'name': 'Dirección de correo electrónico',
3778 'email': 'Endereço de Email',
3779 'company': 'Nombre de la empresa o del programador',
3780 'appUrl': 'URL de una de tus aplicaciones de Play Store',
3781 'business': {
3782 'label': '¿Qué describe mejor a tu empresa?',
3783 'apps': 'Aplicaciones',
3784 'games': 'Juegos',
3785 'both': 'Juegos y aplicaciones'
3786 },
3787 'confirmMailingList': 'Deseo unirme a la lista de distribución para recibir el boletín informativo mensual ' +
3788 'y correos electrónicos ocasionales sobre desarrollo y oportunidades de Google Play.',
3789 'privacyPolicy': 'Acepto que la información que proporcioné en este formulario cumple con la <a href="' +
3790 'https://www.google.com/intl/es/policies/privacy/" target="_blank">política de privacidad</a> de Google.',
3791 'languageVal': 'Spanish (español)',
3792 'successTitle': '¡Felicitaciones!',
3793 'successDetails': 'El registro para recibir las últimas noticias y sugerencias para programadores de Android ' +
3794 'se realizó correctamente.'
3795 }
3796 });
3797 break;
3798 case 'th':
3799 window.polyglot.extend({
3800 'newsletter': {
3801 'title': 'รับข่าวสารล่าสุดสำหรับนักพัฒนาซอฟต์แวร์ Android ตลอดจนเคล็ดลับที่จะช่วยให้คุณประสบความสำเร็จบน ' +
3802 'Google Play',
3803 'requiredHint': '* ช่องที่ต้องกรอก',
3804 'name': 'ชื่อและนามสกุล',
3805 'email': 'ที่อยู่อีเมล',
3806 'company': 'ชื่อบริษัท/นักพัฒนาซอฟต์แวร์',
3807 'appUrl': 'URL แอปใดแอปหนึ่งของคุณใน Play สโตร์',
3808 'business': {
3809 'label': 'ข้อใดตรงกับธุรกิจของคุณมากที่สุด',
3810 'apps': 'แอป',
3811 'games': 'เกม',
3812 'both': 'แอปและเกม'
3813 },
3814 'confirmMailingList': 'เพิ่มฉันลงในรายชื่ออีเมลเพื่อรับจดหมายข่าวรายเดือนและอีเมลเป็นครั้งคราวเกี่ยวกับก' +
3815 'ารพัฒนาซอฟต์แวร์และโอกาสใน Google Play',
3816 'privacyPolicy': 'ฉันรับทราบว่าข้อมูลที่ให้ไว้ในแบบฟอร์มนี้จะเป็นไปตามนโยบายส่วนบุคคลของ ' +
3817 '<a href="https://www.google.com/intl/th/policies/privacy/" target="_blank">Google</a>',
3818 'languageVal': 'Thai (ภาษาไทย)',
3819 'successTitle': 'ไชโย!',
3820 'successDetails': 'คุณลงชื่อสมัครรับข่าวสารและเคล็ดลับล่าสุดสำหรับนักพัฒนาซอฟต์แวร์ Android เสร็จเรียบร้อยแล้ว'
3821 }
3822 });
3823 break;
3824 case 'tr':
3825 window.polyglot.extend({
3826 'newsletter': {
3827 'title': 'Google Play\'de başarılı olmanıza yardımcı olacak en son Android geliştirici haberleri ve ipuçları.',
3828 'requiredHint': '* Zorunlu Alanlar',
3829 'name': 'Tam ad',
3830 'email': 'E-posta adresi',
3831 'company': 'Şirket / geliştirici adı',
3832 'appUrl': 'Play Store uygulama URL\'lerinizden biri',
3833 'business': {
3834 'label': 'İşletmenizi en iyi hangisi tanımlar?',
3835 'apps': 'Uygulamalar',
3836 'games': 'Oyunlar',
3837 'both': 'Uygulamalar ve Oyunlar'
3838 },
3839 'confirmMailingList': 'Beni, geliştirme ve Google Play fırsatlarıyla ilgili ara sıra gönderilen e-posta ' +
3840 'iletilerine ilişkin posta listesine ve aylık haber bültenine ekle.',
3841 'privacyPolicy': 'Bu formda sağlanan bilgilerin Google\'ın ' +
3842 '<a href="https://www.google.com/intl/tr/policies/privacy/" target="_blank">Gizlilik Politikası\'na</a> ' +
3843 'tabi olacağını kabul ediyorum.',
3844 'languageVal': 'Turkish (Türkçe)',
3845 'successTitle': 'Yaşasın!',
3846 'successDetails': 'En son Android geliştirici haberleri ve ipuçlarına başarıyla kaydoldunuz.'
3847 }
3848 });
3849 break;
3850 case 'vi':
3851 window.polyglot.extend({
3852 'newsletter': {
3853 '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 ' +
3854 'Google Play.',
3855 'requiredHint': '* Các trường bắt buộc',
3856 'name': 'Tên đầy đủ',
3857 'email': 'Địa chỉ email',
3858 'company': 'Tên công ty/nhà phát triển',
3859 'appUrl': 'Một trong số các URL ứng dụng trên cửa hàng Play của bạn',
3860 'business': {
3861 'label': 'Lựa chọn nào sau đây mô tả chính xác nhất doanh nghiệp của bạn?',
3862 'apps': 'Ứng dụng',
3863 'games': 'Trò chơi',
3864 'both': 'Ứng dụng và trò chơi'
3865 },
3866 '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 ' +
3867 'triển và cơ hội của Google Play.',
3868 '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 ' +
3869 'của <a href="https://www.google.com/intl/vi/policies/privacy/" target="_blank">Google</a>.',
3870 'languageVal': 'Vietnamese (tiếng Việt)',
3871 'successTitle': 'Thật tuyệt!',
3872 '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.'
3873 }
3874 });
3875 break;
3876}
3877
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003878(function($) {
3879 'use strict';
3880
3881 function Modal(el, options) {
3882 this.el = $(el);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003883 this.options = $.extend({}, options);
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003884 this.isOpen = false;
3885
3886 this.el.on('click', function(event) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003887 if (!$.contains(this.el.find('.dac-modal-window')[0], event.target)) {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003888 return this.el.trigger('modal-close');
3889 }
3890 }.bind(this));
3891
3892 this.el.on('modal-open', this.open_.bind(this));
3893 this.el.on('modal-close', this.close_.bind(this));
3894 this.el.on('modal-toggle', this.toggle_.bind(this));
3895 }
3896
3897 Modal.prototype.toggle_ = function() {
3898 this.el.trigger('modal-' + (this.isOpen ? 'close' : 'open'));
3899 };
3900
3901 Modal.prototype.close_ = function() {
3902 this.el.removeClass('dac-active');
3903 $('body').removeClass('dac-modal-open');
3904 this.isOpen = false;
Dirk Doughertyc607a4d2016-01-28 08:32:47 -08003905 // When closing the modal for Android Studio downloads, reload the page
3906 // because otherwise we might get stuck with post-download dialog state
3907 if ($("[data-modal='studio_tos']").length) {
3908 location.reload();
3909 }
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003910 };
3911
3912 Modal.prototype.open_ = function() {
3913 this.el.addClass('dac-active');
3914 $('body').addClass('dac-modal-open');
3915 this.isOpen = true;
3916 };
3917
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003918 function onClickToggleModal(event) {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003919 event.preventDefault();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003920 var toggle = $(event.currentTarget);
3921 var options = toggle.data();
3922 var modal = options.modalToggle ? $('[data-modal="' + options.modalToggle + '"]') :
3923 toggle.closest('[data-modal]');
3924 modal.trigger('modal-toggle');
3925 }
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003926
3927 /**
3928 * jQuery plugin
3929 * @param {object} options - Override default options.
3930 */
3931 $.fn.dacModal = function(options) {
3932 return this.each(function() {
3933 new Modal(this, options);
3934 });
3935 };
3936
3937 $.fn.dacToggleModal = function(options) {
3938 return this.each(function() {
3939 new ToggleModal(this, options);
3940 });
3941 };
3942
3943 /**
3944 * Data Attribute API
3945 */
3946 $(document).on('ready.aranja', function() {
3947 $('[data-modal]').each(function() {
3948 $(this).dacModal($(this).data());
3949 });
3950
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003951 $('html').on('click.modal', '[data-modal-toggle]', onClickToggleModal);
3952
3953 // Check if url anchor is targetting a toggle to open the modal.
3954 if (location.hash) {
3955 $(location.hash + '[data-modal-toggle]').trigger('click');
3956 }
3957
3958 if (window.getLangTarget() !== window.getLangPref()) {
3959 $('#langform').trigger('modal-open');
3960 $("#langform button.yes").attr("onclick","window.changeLangPref('" + window.getLangTarget() + "', true); return false;");
3961 $("#langform button.no").attr("onclick","window.changeLangPref('" + window.getLangPref() + "', true); return false;");
3962 }
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08003963 });
3964})(jQuery);
3965
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08003966/* Fullscreen - Toggle fullscreen mode for reference pages */
3967(function($) {
3968 'use strict';
3969
3970 /**
3971 * @param {HTMLElement} el - The DOM element.
3972 * @constructor
3973 */
3974 function Fullscreen(el) {
3975 this.el = $(el);
3976 this.html = $('html');
3977 this.icon = this.el.find('.dac-sprite');
3978 this.isFullscreen = window.readCookie(Fullscreen.COOKIE_) === 'true';
3979 this.activate_();
3980 this.el.on('click.dac-fullscreen', this.toggleHandler_.bind(this));
3981 }
3982
3983 /**
3984 * Cookie name for storing the state
3985 * @type {string}
3986 * @private
3987 */
3988 Fullscreen.COOKIE_ = 'fullscreen';
3989
3990 /**
3991 * Classes to modify the DOM
3992 * @type {{mode: string, fullscreen: string, fullscreenExit: string}}
3993 * @private
3994 */
3995 Fullscreen.CLASSES_ = {
3996 mode: 'dac-fullscreen-mode',
3997 fullscreen: 'dac-fullscreen',
3998 fullscreenExit: 'dac-fullscreen-exit'
3999 };
4000
4001 /**
4002 * Event listener for toggling fullscreen mode
4003 * @param {MouseEvent} event
4004 * @private
4005 */
4006 Fullscreen.prototype.toggleHandler_ = function(event) {
4007 event.stopPropagation();
4008 this.toggle(!this.isFullscreen, true);
4009 };
4010
4011 /**
4012 * Change the DOM based on current state.
4013 * @private
4014 */
4015 Fullscreen.prototype.activate_ = function() {
4016 this.icon.toggleClass(Fullscreen.CLASSES_.fullscreen, !this.isFullscreen);
4017 this.icon.toggleClass(Fullscreen.CLASSES_.fullscreenExit, this.isFullscreen);
4018 this.html.toggleClass(Fullscreen.CLASSES_.mode, this.isFullscreen);
4019 };
4020
4021 /**
4022 * Toggle fullscreen mode and store the state in a cookie.
4023 */
4024 Fullscreen.prototype.toggle = function() {
4025 this.isFullscreen = !this.isFullscreen;
4026 window.writeCookie(Fullscreen.COOKIE_, this.isFullscreen, null);
4027 this.activate_();
4028 };
4029
4030 /**
4031 * jQuery plugin
4032 */
4033 $.fn.dacFullscreen = function() {
4034 return this.each(function() {
4035 new Fullscreen($(this));
4036 });
4037 };
4038})(jQuery);
4039
4040(function($) {
4041 'use strict';
4042
4043 /**
4044 * @param {HTMLElement} selected - The link that is selected in the nav.
4045 * @constructor
4046 */
4047 function HeaderTabs(selected) {
4048
4049 // Don't highlight any tabs on the index page
4050 if (location.pathname === '/index.html' || location.pathname === '/') {
4051 //return;
4052 }
4053
4054 this.selected = $(selected);
4055 this.selectedParent = this.selected.closest('.dac-nav-secondary').siblings('a');
4056 this.links = $('.dac-header-tabs a');
4057
4058 this.selectActiveTab();
4059 }
4060
4061 HeaderTabs.prototype.selectActiveTab = function() {
4062 var section = null;
4063
4064 if (this.selectedParent.length) {
4065 section = this.selectedParent.text();
4066 } else {
4067 section = this.selected.text();
4068 }
4069
4070 if (section) {
4071 this.links.removeClass('selected');
4072
4073 this.links.filter(function() {
4074 return $(this).text() === $.trim(section);
4075 }).addClass('selected');
4076 }
4077 };
4078
4079 /**
4080 * jQuery plugin
4081 */
4082 $.fn.dacHeaderTabs = function() {
4083 return this.each(function() {
4084 new HeaderTabs(this);
4085 });
4086 };
4087})(jQuery);
4088
4089(function($) {
4090 'use strict';
4091 var icon = $('<i/>').addClass('dac-sprite dac-nav-forward');
4092 var config = JSON.parse(window.localStorage.getItem('global-navigation') || '{}');
4093 var forwardLink = $('<span/>')
4094 .addClass('dac-nav-link-forward')
4095 .html(icon)
4096 .on('click', swap_);
4097
4098 /**
4099 * @constructor
4100 */
4101 function Nav(navigation) {
4102 $('.dac-nav-list').dacCurrentPage().dacHeaderTabs().dacSidebarToggle($('body'));
4103
4104 navigation.find('[data-reference-tree]').dacReferenceNav();
4105
4106 setupViews_(navigation.children().eq(0).children());
4107
4108 initCollapsedNavs(navigation.find('.dac-nav-sub-slider'));
4109
4110 $('#dac-main-navigation').scrollIntoView('.selected')
4111 }
4112
4113 function updateStore(icon) {
4114 var navClass = getCurrentLandingPage_(icon);
4115 var isExpanded = icon.hasClass('dac-expand-less-black');
4116 var expandedNavs = config.expanded || [];
4117 if (isExpanded) {
4118 expandedNavs.push(navClass);
4119 } else {
4120 expandedNavs = expandedNavs.filter(function(item) {
4121 return item !== navClass;
4122 });
4123 }
4124 config.expanded = expandedNavs;
4125 window.localStorage.setItem('global-navigation', JSON.stringify(config));
4126 }
4127
4128 function toggleSubNav_(icon) {
4129 var isExpanded = icon.hasClass('dac-expand-less-black');
4130 icon.toggleClass('dac-expand-less-black', !isExpanded);
4131 icon.toggleClass('dac-expand-more-black', isExpanded);
4132 icon.data('sub-navigation.dac').slideToggle(200);
4133
4134 updateStore(icon);
4135 }
4136
4137 function handleSubNavToggle_(event) {
4138 event.preventDefault();
4139 var icon = $(event.target);
4140 toggleSubNav_(icon);
4141 }
4142
4143 function getCurrentLandingPage_(icon) {
4144 return icon.closest('li')[0].className.replace('dac-nav-item ', '');
4145 }
4146
4147 // Setup sub navigation collapse/expand
4148 function initCollapsedNavs(toggleIcons) {
4149 toggleIcons.each(setInitiallyActive_($('body')));
4150 toggleIcons.on('click', handleSubNavToggle_);
4151
4152 }
4153
4154 function setInitiallyActive_(body) {
4155 var expandedNavs = config.expanded || [];
4156 return function(i, icon) {
4157 icon = $(icon);
4158 var subNav = icon.next();
4159
4160 if (!subNav.length) {
4161 return;
4162 }
4163
4164 var landingPageClass = getCurrentLandingPage_(icon);
4165 var expanded = expandedNavs.indexOf(landingPageClass) >= 0;
4166 landingPageClass = landingPageClass === 'home' ? 'about' : landingPageClass;
4167
4168 // TODO: Should read from localStorage
4169 var visible = body.hasClass(landingPageClass) || expanded;
4170
4171 icon.data('sub-navigation.dac', subNav);
4172 icon.toggleClass('dac-expand-less-black', visible);
4173 icon.toggleClass('dac-expand-more-black', !visible);
4174 subNav.toggle(visible);
4175 };
4176 }
4177
4178 function setupViews_(views) {
4179 if (views.length === 1) {
4180 // Active tier 1 nav.
4181 views.addClass('dac-active');
4182 } else {
4183 // Activate back button and tier 2 nav.
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004184 views.slice(0, 2).addClass('dac-active');
4185 var selectedNav = views.eq(2).find('.selected').after(forwardLink);
Dirk Dougherty82e929d2016-01-30 14:04:37 -08004186 var langAttr = selectedNav.attr(window.getLangPref() + '-lang');
4187 //form the label from locale attr if possible, else set to selectedNav text value
4188 if ((typeof langAttr !== typeof undefined && langAttr !== false) && (langAttr !== '')) {
4189 $('.dac-nav-back-title').text(langAttr);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004190 } else {
4191 $('.dac-nav-back-title').text(selectedNav.text());
4192 }
4193 }
4194
4195 // Navigation should animate.
4196 setTimeout(function() {
4197 views.removeClass('dac-no-anim');
4198 }, 10);
4199 }
4200
4201 function swap_(event) {
4202 event.preventDefault();
4203 $(event.currentTarget).trigger('swap-content');
4204 }
4205
4206 /**
4207 * jQuery plugin
4208 */
4209 $.fn.dacNav = function() {
4210 return this.each(function() {
4211 new Nav($(this));
4212 });
4213 };
4214})(jQuery);
4215
4216/* global NAVTREE_DATA */
4217(function($) {
4218 /**
4219 * Build the reference navigation with namespace dropdowns.
4220 * @param {jQuery} el - The DOM element.
4221 */
4222 function buildReferenceNav(el) {
4223 var namespaceList = el.find('[data-reference-namespaces]');
4224 var resources = el.find('[data-reference-resources]');
4225 var selected = namespaceList.find('.selected');
4226
4227 // Links should be toggleable.
4228 namespaceList.find('a').addClass('dac-reference-nav-toggle dac-closed');
4229
4230 // Load in all resources
4231 $.getScript('/navtree_data.js', function(data, textStatus, xhr) {
4232 if (xhr.status === 200) {
4233 namespaceList.on('click', 'a.dac-reference-nav-toggle', toggleResourcesHandler);
4234 }
4235 });
4236
4237 // No setup required if no resources are present
4238 if (!resources.length) {
4239 return;
4240 }
4241
4242 // The resources should be a part of selected namespace.
4243 var overview = addResourcesToView(resources, selected);
4244
4245 // Currently viewing Overview
4246 if (location.pathname === overview.attr('href')) {
4247 overview.parent().addClass('selected');
4248 }
4249
4250 // Open currently selected resource
4251 var listsToOpen = selected.children().eq(1);
4252 listsToOpen = listsToOpen.add(listsToOpen.find('.selected').parent()).show();
4253
4254 // Mark dropdowns as open
4255 listsToOpen.prev().removeClass('dac-closed');
4256
4257 // Scroll into view
4258 namespaceList.scrollIntoView(selected);
4259 }
4260
4261 /**
4262 * Handles the toggling of resources.
4263 * @param {Event} event
4264 */
4265 function toggleResourcesHandler(event) {
4266 event.preventDefault();
4267 var el = $(this);
4268
4269 // If resources for given namespace is not present, fetch correct data.
4270 if (this.tagName === 'A' && !this.hasResources) {
4271 addResourcesToView(buildResourcesViewForData(getDataForNamespace(el.text())), el.parent());
4272 }
4273
4274 el.toggleClass('dac-closed').next().slideToggle(200);
4275 }
4276
4277 /**
4278 * @param {String} namespace
4279 * @returns {Array} namespace data
4280 */
4281 function getDataForNamespace(namespace) {
4282 var namespaceData = NAVTREE_DATA.filter(function(data) {
4283 return data[0] === namespace;
4284 });
4285
4286 return namespaceData.length ? namespaceData[0][2] : [];
4287 }
4288
4289 /**
4290 * Build a list item for a resource
4291 * @param {Array} resource
4292 * @returns {String}
4293 */
4294 function buildResourceItem(resource) {
4295 return '<li class="api apilevel-' + resource[3] + '"><a href="/' + resource[1] + '">' + resource[0] + '</a></li>';
4296 }
4297
4298 /**
4299 * Build resources list items.
4300 * @param {Array} resources
4301 * @returns {String}
4302 */
4303 function buildResourceList(resources) {
4304 return '<li><h2>' + resources[0] + '</h2><ul>' + resources[2].map(buildResourceItem).join('') + '</ul>';
4305 }
4306
4307 /**
4308 * Build a resources view
4309 * @param {Array} data
4310 * @returns {jQuery} resources in an unordered list.
4311 */
4312 function buildResourcesViewForData(data) {
4313 return $('<ul>' + data.map(buildResourceList).join('') + '</ul>');
4314 }
4315
4316 /**
4317 * Add resources to a containing view.
4318 * @param {jQuery} resources
4319 * @param {jQuery} view
4320 * @returns {jQuery} the overview link.
4321 */
4322 function addResourcesToView(resources, view) {
4323 var namespace = view.children().eq(0);
4324 var overview = $('<a href="' + namespace.attr('href') + '">Overview</a>');
4325
4326 // Mark namespace with content;
4327 namespace[0].hasResources = true;
4328
4329 // Add correct classes / event listeners to resources.
4330 resources.prepend($('<li>').html(overview))
4331 .find('a')
4332 .addClass('dac-reference-nav-resource')
4333 .end()
4334 .find('h2')
4335 .addClass('dac-reference-nav-toggle dac-closed')
4336 .on('click', toggleResourcesHandler)
4337 .end()
4338 .add(resources.find('ul'))
4339 .addClass('dac-reference-nav-resources')
4340 .end()
4341 .appendTo(view);
4342
4343 return overview;
4344 }
4345
4346 /**
4347 * jQuery plugin
4348 */
4349 $.fn.dacReferenceNav = function() {
4350 return this.each(function() {
4351 buildReferenceNav($(this));
4352 });
4353 };
4354})(jQuery);
4355
4356/** Scroll a container to make a target element visible
4357 This is called when the page finished loading. */
4358$.fn.scrollIntoView = function(target) {
4359 if ('string' === typeof target) {
4360 target = this.find(target);
4361 }
4362 if (this.is(':visible')) {
4363 if (target.length == 0) {
4364 // If no selected item found, exit
4365 return;
4366 }
4367
4368 // get the target element's offset from its container nav by measuring the element's offset
4369 // relative to the document then subtract the container nav's offset relative to the document
4370 var targetOffset = target.offset().top - this.offset().top;
4371 var containerHeight = this.height();
4372 if (targetOffset > containerHeight * .8) { // multiply nav height by .8 so we move up the item
4373 // if it's more than 80% down the nav
4374 // scroll the item up by an amount equal to 80% the container height
4375 this.scrollTop(targetOffset - (containerHeight * .8));
4376 }
4377 }
4378};
4379
4380(function($) {
4381 $.fn.dacCurrentPage = function() {
4382 // Highlight the header tabs...
4383 // highlight Design tab
4384 var baseurl = getBaseUri(window.location.pathname);
4385 var urlSegments = baseurl.split('/');
4386 var navEl = this;
4387 var body = $('body');
4388 var subNavEl = navEl.find('.dac-nav-secondary');
4389 var parentNavEl;
4390 var selected;
4391 // In NDK docs, highlight appropriate sub-nav
4392 if (body.hasClass('ndk')) {
4393 if (body.hasClass('guide')) {
4394 selected = navEl.find('> li.guides > a').addClass('selected');
4395 } else if (body.hasClass('reference')) {
4396 selected = navEl.find('> li.reference > a').addClass('selected');
smain@google.coma0a6d322016-02-05 17:34:22 -08004397 } else if (body.hasClass('samples')) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004398 selected = navEl.find('> li.samples > a').addClass('selected');
smain@google.coma0a6d322016-02-05 17:34:22 -08004399 } else if (body.hasClass('downloads')) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004400 selected = navEl.find('> li.downloads > a').addClass('selected');
4401 }
smain@google.com190e6102016-04-04 16:47:28 -07004402 } else if (body.hasClass('studio')) {
4403 if (body.hasClass('features')) {
4404 selected = navEl.find('> li.features > a').addClass('selected');
4405 } else if (body.hasClass('guide')) {
4406 selected = navEl.find('> li.guide > a').addClass('selected');
4407 } else if (body.hasClass('preview')) {
4408 selected = navEl.find('> li.preview > a').addClass('selected');
4409 }
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004410 } else if (body.hasClass('design')) {
4411 selected = navEl.find('> li.design > a').addClass('selected');
4412 // highlight Home nav
4413 } else if (body.hasClass('about')) {
4414 parentNavEl = navEl.find('> li.home > a');
4415 parentNavEl.addClass('has-subnav');
4416 // In Home docs, also highlight appropriate sub-nav
4417 if (urlSegments[1] === 'wear' || urlSegments[1] === 'tv' ||
4418 urlSegments[1] === 'auto') {
4419 selected = subNavEl.find('li.' + urlSegments[1] + ' > a').addClass('selected');
4420 } else if (urlSegments[1] === 'about') {
4421 selected = subNavEl.find('li.versions > a').addClass('selected');
4422 } else {
4423 selected = parentNavEl.removeClass('has-subnav').addClass('selected');
4424 }
4425 // highlight Develop nav
4426 } else if (body.hasClass('develop') || body.hasClass('google')) {
4427 parentNavEl = navEl.find('> li.develop > a');
4428 parentNavEl.addClass('has-subnav');
4429 // In Develop docs, also highlight appropriate sub-nav
4430 if (urlSegments[1] === 'training') {
4431 selected = subNavEl.find('li.training > a').addClass('selected');
4432 } else if (urlSegments[1] === 'guide') {
4433 selected = subNavEl.find('li.guide > a').addClass('selected');
4434 } else if (urlSegments[1] === 'reference') {
4435 // If the root is reference, but page is also part of Google Services, select Google
4436 if (body.hasClass('google')) {
4437 selected = subNavEl.find('li.google > a').addClass('selected');
4438 } else {
4439 selected = subNavEl.find('li.reference > a').addClass('selected');
4440 }
4441 } else if ((urlSegments[1] === 'tools') || (urlSegments[1] === 'sdk')) {
4442 selected = subNavEl.find('li.tools > a').addClass('selected');
4443 } else if (body.hasClass('google')) {
4444 selected = subNavEl.find('li.google > a').addClass('selected');
4445 } else if (body.hasClass('samples')) {
4446 selected = subNavEl.find('li.samples > a').addClass('selected');
4447 } else {
4448 selected = parentNavEl.removeClass('has-subnav').addClass('selected');
4449 }
4450 // highlight Distribute nav
4451 } else if (body.hasClass('distribute')) {
4452 parentNavEl = navEl.find('> li.distribute > a');
4453 parentNavEl.addClass('has-subnav');
4454 // In Distribute docs, also highlight appropriate sub-nav
4455 if (urlSegments[2] === 'users') {
4456 selected = subNavEl.find('li.users > a').addClass('selected');
4457 } else if (urlSegments[2] === 'engage') {
4458 selected = subNavEl.find('li.engage > a').addClass('selected');
4459 } else if (urlSegments[2] === 'monetize') {
4460 selected = subNavEl.find('li.monetize > a').addClass('selected');
4461 } else if (urlSegments[2] === 'analyze') {
4462 selected = subNavEl.find('li.analyze > a').addClass('selected');
4463 } else if (urlSegments[2] === 'tools') {
4464 selected = subNavEl.find('li.disttools > a').addClass('selected');
4465 } else if (urlSegments[2] === 'stories') {
4466 selected = subNavEl.find('li.stories > a').addClass('selected');
4467 } else if (urlSegments[2] === 'essentials') {
4468 selected = subNavEl.find('li.essentials > a').addClass('selected');
4469 } else if (urlSegments[2] === 'googleplay') {
4470 selected = subNavEl.find('li.googleplay > a').addClass('selected');
4471 } else {
4472 selected = parentNavEl.removeClass('has-subnav').addClass('selected');
4473 }
Dirk Dougherty86120022016-02-09 18:00:05 -08004474 } else if (body.hasClass('preview')) {
4475 selected = navEl.find('> li.preview > a').addClass('selected');
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004476 }
4477 return $(selected);
4478 };
4479})(jQuery);
4480
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004481(function($) {
4482 'use strict';
4483
4484 /**
4485 * Toggle the visabilty of the mobile navigation.
4486 * @param {HTMLElement} el - The DOM element.
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004487 * @param {Object} options
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004488 * @constructor
4489 */
4490 function ToggleNav(el, options) {
4491 this.el = $(el);
4492 this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004493 this.body = $(document.body);
4494 this.navigation_ = this.body.find(this.options.navigation);
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004495 this.el.on('click', this.clickHandler_.bind(this));
4496 }
4497
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004498 ToggleNav.BREAKPOINT_ = 980;
4499
4500 /**
4501 * Open on correct sizes
4502 */
4503 function toggleSidebarVisibility(body) {
4504 var wasClosed = ('' + localStorage.getItem('navigation-open')) === 'false';
4505
4506 if (wasClosed) {
4507 body.removeClass(ToggleNav.DEFAULTS_.activeClass);
4508 } else if (window.innerWidth >= ToggleNav.BREAKPOINT_) {
4509 body.addClass(ToggleNav.DEFAULTS_.activeClass);
4510 } else {
4511 body.removeClass(ToggleNav.DEFAULTS_.activeClass);
4512 }
4513 }
4514
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004515 /**
4516 * ToggleNav Default Settings
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004517 * @type {{body: boolean, dimmer: string, navigation: string, activeClass: string}}
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004518 * @private
4519 */
4520 ToggleNav.DEFAULTS_ = {
4521 body: true,
4522 dimmer: '.dac-nav-dimmer',
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004523 animatingClass: 'dac-nav-animating',
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004524 navigation: '[data-dac-nav]',
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004525 activeClass: 'dac-nav-open'
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004526 };
4527
4528 /**
4529 * The actual toggle logic.
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004530 * @param {Event} event
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004531 * @private
4532 */
4533 ToggleNav.prototype.clickHandler_ = function(event) {
4534 event.preventDefault();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004535 var animatingClass = this.options.animatingClass;
4536 var body = this.body;
4537
4538 body.addClass(animatingClass);
4539 body.toggleClass(this.options.activeClass);
4540
4541 setTimeout(function() {
4542 body.removeClass(animatingClass);
4543 }, this.navigation_.transitionDuration());
4544
4545 if (window.innerWidth >= ToggleNav.BREAKPOINT_) {
4546 localStorage.setItem('navigation-open', body.hasClass(this.options.activeClass));
4547 }
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004548 };
4549
4550 /**
4551 * jQuery plugin
4552 * @param {object} options - Override default options.
4553 */
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004554 $.fn.dacToggleMobileNav = function() {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004555 return this.each(function() {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004556 var el = $(this);
4557 new ToggleNav(el, el.data());
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004558 });
4559 };
4560
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004561 $.fn.dacSidebarToggle = function(body) {
4562 toggleSidebarVisibility(body);
4563 $(window).on('resize', toggleSidebarVisibility.bind(null, body));
4564 };
4565
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004566 /**
4567 * Data Attribute API
4568 */
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004569 $(function() {
4570 $('[data-dac-toggle-nav]').dacToggleMobileNav();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004571 });
4572})(jQuery);
4573
4574(function($) {
4575 'use strict';
4576
4577 /**
4578 * Submit the newsletter form to a Google Form.
4579 * @param {HTMLElement} el - The Form DOM element.
4580 * @constructor
4581 */
4582 function NewsletterForm(el) {
4583 this.el = $(el);
4584 this.form = this.el.find('form');
4585 $('<iframe/>').hide()
4586 .attr('name', 'dac-newsletter-iframe')
4587 .attr('src', '')
4588 .insertBefore(this.form);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004589 this.el.find('[data-newsletter-language]').val(window.polyglot.t('newsletter.languageVal'));
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08004590 this.form.on('submit', this.submitHandler_.bind(this));
4591 }
4592
4593 /**
4594 * Milliseconds until modal has vanished after modal-close is triggered.
4595 * @type {number}
4596 * @private
4597 */
4598 NewsletterForm.CLOSE_DELAY_ = 300;
4599
4600 /**
4601 * Switch view to display form after close.
4602 * @private
4603 */
4604 NewsletterForm.prototype.closeHandler_ = function() {
4605 setTimeout(function() {
4606 this.el.trigger('swap-reset');
4607 }.bind(this), NewsletterForm.CLOSE_DELAY_);
4608 };
4609
4610 /**
4611 * Reset the modal to initial state.
4612 * @private
4613 */
4614 NewsletterForm.prototype.reset_ = function() {
4615 this.form.trigger('reset');
4616 this.el.one('modal-close', this.closeHandler_.bind(this));
4617 };
4618
4619 /**
4620 * Display a success view on submit.
4621 * @private
4622 */
4623 NewsletterForm.prototype.submitHandler_ = function() {
4624 this.el.one('swap-complete', this.reset_.bind(this));
4625 this.el.trigger('swap-content');
4626 };
4627
4628 /**
4629 * jQuery plugin
4630 * @param {object} options - Override default options.
4631 */
4632 $.fn.dacNewsletterForm = function(options) {
4633 return this.each(function() {
4634 new NewsletterForm(this, options);
4635 });
4636 };
4637
4638 /**
4639 * Data Attribute API
4640 */
4641 $(document).on('ready.aranja', function() {
4642 $('[data-newsletter]').each(function() {
4643 $(this).dacNewsletterForm();
4644 });
4645 });
4646})(jQuery);
4647
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08004648/* globals METADATA, YOUTUBE_RESOURCES, BLOGGER_RESOURCES */
4649window.metadata = {};
4650
4651/**
4652 * Prepare metadata and indices for querying.
4653 */
4654window.metadata.prepare = (function() {
4655 // Helper functions.
4656 function mergeArrays() {
4657 return Array.prototype.concat.apply([], arguments);
4658 }
4659
4660 /**
4661 * Creates lookup maps for a resource index.
4662 * I.e. where MAP['some tag'][resource.id] === true when that resource has 'some tag'.
4663 * @param resourceDict
4664 * @returns {{}}
4665 */
4666 function buildResourceLookupMap(resourceDict) {
4667 var map = {};
4668 for (var key in resourceDict) {
4669 var dictForKey = {};
4670 var srcArr = resourceDict[key];
4671 for (var i = 0; i < srcArr.length; i++) {
4672 dictForKey[srcArr[i].index] = true;
4673 }
4674 map[key] = dictForKey;
4675 }
4676 return map;
4677 }
4678
4679 /**
4680 * Merges metadata maps for english and the current language into the global store.
4681 */
4682 function mergeMetadataMap(name, locale) {
4683 if (locale && locale !== 'en' && METADATA[locale]) {
4684 METADATA[name] = $.extend(METADATA.en[name], METADATA[locale][name]);
4685 } else {
4686 METADATA[name] = METADATA.en[name];
4687 }
4688 }
4689
4690 /**
4691 * Index all resources by type, url, tag and category.
4692 * @param resources
4693 */
4694 function createIndices(resources) {
4695 // URL, type, tag and category lookups
4696 var byType = METADATA.byType = {};
4697 var byUrl = METADATA.byUrl = {};
4698 var byTag = METADATA.byTag = {};
4699 var byCategory = METADATA.byCategory = {};
4700
4701 for (var i = 0; i < resources.length; i++) {
4702 var res = resources[i];
4703
4704 // Store index.
4705 res.index = i;
4706
4707 // Index by type.
4708 var type = res.type;
4709 if (type) {
4710 byType[type] = byType[type] || [];
4711 byType[type].push(res);
4712 }
4713
4714 // Index by tag.
4715 var tags = res.tags || [];
4716 for (var j = 0; j < tags.length; j++) {
4717 var tag = tags[j];
4718 if (tag) {
4719 byTag[tag] = byTag[tag] || [];
4720 byTag[tag].push(res);
4721 }
4722 }
4723
4724 // Index by category.
4725 var category = res.category;
4726 if (category) {
4727 byCategory[category] = byCategory[category] || [];
4728 byCategory[category].push(res);
4729 }
4730
4731 // Index by url.
4732 var url = res.url;
4733 if (url) {
4734 res.baseUrl = url.replace(/^intl\/\w+[\/]/, '');
4735 byUrl[res.baseUrl] = res;
4736 }
4737 }
4738 METADATA.hasType = buildResourceLookupMap(byType);
4739 METADATA.hasTag = buildResourceLookupMap(byTag);
4740 METADATA.hasCategory = buildResourceLookupMap(byCategory);
4741 }
4742
4743 return function() {
4744 // Only once.
4745 if (METADATA.all) { return; }
4746
4747 // Get current language.
4748 var locale = getLangPref();
4749
4750 // Merge english resources.
4751 METADATA.all = mergeArrays(
4752 METADATA.en.about,
4753 METADATA.en.design,
4754 METADATA.en.distribute,
4755 METADATA.en.develop,
4756 YOUTUBE_RESOURCES,
4757 BLOGGER_RESOURCES,
4758 METADATA.en.extras
4759 );
4760
4761 // Merge local language resources.
4762 if (locale !== 'en' && METADATA[locale]) {
4763 METADATA.all = mergeArrays(
4764 METADATA.all,
4765 METADATA[locale].about,
4766 METADATA[locale].design,
4767 METADATA[locale].distribute,
4768 METADATA[locale].develop,
4769 METADATA[locale].extras
4770 );
4771 }
4772
4773 mergeMetadataMap('collections', locale);
4774 mergeMetadataMap('searchHeroCollections', locale);
4775 mergeMetadataMap('carousel', locale);
4776
4777 // Create query indicies for resources.
4778 createIndices(METADATA.all, locale);
4779
4780 // Reference metadata.
4781 METADATA.androidReference = window.DATA;
4782 METADATA.googleReference = mergeArrays(window.GMS_DATA, window.GCM_DATA);
4783 };
4784})();
4785
4786/* global METADATA, util */
4787window.metadata.query = (function($) {
4788 var pageMap = {};
4789
4790 function buildResourceList(opts) {
4791 window.metadata.prepare();
4792 var expressions = parseResourceQuery(opts.query || '');
4793 var instanceMap = {};
4794 var results = [];
4795
4796 for (var i = 0; i < expressions.length; i++) {
4797 var clauses = expressions[i];
4798
4799 // Get all resources for first clause
4800 var resources = getResourcesForClause(clauses.shift());
4801
4802 // Concat to final results list
4803 results = results.concat(resources.map(filterResources(clauses, i > 0, instanceMap)).filter(filterEmpty));
4804 }
4805
4806 // Set correct order
4807 if (opts.sortOrder && results.length) {
4808 results = opts.sortOrder === 'random' ? util.shuffle(results) : results.sort(sortResultsByKey(opts.sortOrder));
4809 }
4810
4811 // Slice max results.
4812 if (opts.maxResults !== Infinity) {
4813 results = results.slice(0, opts.maxResults);
4814 }
4815
4816 // Remove page level duplicates
4817 if (opts.allowDuplicates === undefined || opts.allowDuplicates === 'false') {
4818 results = results.filter(removePageLevelDuplicates);
4819
4820 for (var index = 0; index < results.length; ++index) {
4821 pageMap[results[index].index] = 1;
4822 }
4823 }
4824
4825 return results;
4826 }
4827
4828 function filterResources(clauses, removeDuplicates, map) {
4829 return function(resource) {
4830 var resourceIsAllowed = true;
4831
4832 // References must be defined.
4833 if (resource === undefined) {
4834 return;
4835 }
4836
4837 // Get canonical (localized) version of resource if possible.
4838 resource = METADATA.byUrl[resource.baseUrl] || METADATA.byUrl[resource.url] || resource;
4839
4840 // Filter out resources already used
4841 if (removeDuplicates) {
4842 resourceIsAllowed = !map[resource.index];
4843 }
4844
4845 // Must fulfill all criteria
4846 if (clauses.length > 0) {
4847 resourceIsAllowed = resourceIsAllowed && doesResourceMatchClauses(resource, clauses);
4848 }
4849
4850 // Mark resource as used.
4851 if (resourceIsAllowed) {
4852 map[resource.index] = 1;
4853 }
4854
4855 return resourceIsAllowed && resource;
4856 };
4857 }
4858
4859 function filterEmpty(resource) {
4860 return resource;
4861 }
4862
4863 function sortResultsByKey(key) {
4864 var desc = key.charAt(0) === '-';
4865
4866 if (desc) {
4867 key = key.substring(1);
4868 }
4869
4870 return function(x, y) {
4871 return (desc ? -1 : 1) * (parseInt(x[key], 10) - parseInt(y[key], 10));
4872 };
4873 }
4874
4875 function getResourcesForClause(clause) {
4876 switch (clause.attr) {
4877 case 'type':
4878 return METADATA.byType[clause.value];
4879 case 'tag':
4880 return METADATA.byTag[clause.value];
4881 case 'collection':
4882 var resources = METADATA.collections[clause.value] || {};
4883 return getResourcesByUrlCollection(resources.resources);
4884 case 'history':
4885 return getResourcesByUrlCollection($.dacGetVisitedUrls(clause.value));
4886 case 'section':
4887 return getResourcesByUrlCollection([clause.value].sections);
4888 default:
4889 return [];
4890 }
4891 }
4892
4893 function getResourcesByUrlCollection(resources) {
4894 return (resources || []).map(function(url) {
4895 return METADATA.byUrl[url];
4896 });
4897 }
4898
4899 function removePageLevelDuplicates(resource) {
4900 return resource && !pageMap[resource.index];
4901 }
4902
4903 function doesResourceMatchClauses(resource, clauses) {
4904 for (var i = 0; i < clauses.length; i++) {
4905 var map;
4906 switch (clauses[i].attr) {
4907 case 'type':
4908 map = METADATA.hasType[clauses[i].value];
4909 break;
4910 case 'tag':
4911 map = METADATA.hasTag[clauses[i].value];
4912 break;
4913 }
4914
4915 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
4916 return clauses[i].negative;
4917 }
4918 }
4919
4920 return true;
4921 }
4922
4923 function parseResourceQuery(query) {
4924 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
4925 var expressions = [];
4926 var expressionStrs = query.split(',') || [];
4927 for (var i = 0; i < expressionStrs.length; i++) {
4928 var expr = expressionStrs[i] || '';
4929
4930 // Break expression into clauses (clause e.g. 'tag:foo')
4931 var clauses = [];
4932 var clauseStrs = expr.split(/(?=[\+\-])/);
4933 for (var j = 0; j < clauseStrs.length; j++) {
4934 var clauseStr = clauseStrs[j] || '';
4935
4936 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
4937 var parts = clauseStr.split(':');
4938 var clause = {};
4939
4940 clause.attr = parts[0].replace(/^\s+|\s+$/g, '');
4941 if (clause.attr) {
4942 if (clause.attr.charAt(0) === '+') {
4943 clause.attr = clause.attr.substring(1);
4944 } else if (clause.attr.charAt(0) === '-') {
4945 clause.negative = true;
4946 clause.attr = clause.attr.substring(1);
4947 }
4948 }
4949
4950 if (parts.length > 1) {
4951 clause.value = parts[1].replace(/^\s+|\s+$/g, '');
4952 }
4953
4954 clauses.push(clause);
4955 }
4956
4957 if (!clauses.length) {
4958 continue;
4959 }
4960
4961 expressions.push(clauses);
4962 }
4963
4964 return expressions;
4965 }
4966
4967 return buildResourceList;
4968})(jQuery);
4969
4970/* global METADATA, getLangPref */
4971
4972window.metadata.search = (function() {
4973 'use strict';
4974
4975 var currentLang = getLangPref();
4976
4977 function search(query) {
4978 window.metadata.prepare();
4979 return {
4980 android: findDocsMatches(query, METADATA.androidReference),
4981 docs: findDocsMatches(query, METADATA.googleReference),
4982 resources: findResourceMatches(query)
4983 };
4984 }
4985
4986 function findDocsMatches(query, data) {
4987 var results = [];
4988
4989 for (var i = 0; i < data.length; i++) {
4990 var s = data[i];
4991 if (query.length !== 0 && s.label.toLowerCase().indexOf(query.toLowerCase()) !== -1) {
4992 results.push(s);
4993 }
4994 }
4995
4996 rankAutocompleteApiResults(query, results);
4997
4998 return results;
4999 }
5000
5001 function findResourceMatches(query) {
5002 var results = [];
5003
5004 // Search for matching JD docs
5005 if (query.length >= 2) {
5006 /* In some langs, spaces may be optional between certain non-Ascii word-glyphs. For
5007 * those langs, only match query at word boundaries if query includes Ascii chars only.
5008 */
5009 var NO_BOUNDARY_LANGUAGES = ['ja','ko','vi','zh-cn','zh-tw'];
5010 var isAsciiOnly = /^[\u0000-\u007f]*$/.test(query);
5011 var noBoundaries = (NO_BOUNDARY_LANGUAGES.indexOf(window.getLangPref()) !== -1);
5012 var exprBoundary = (!isAsciiOnly && noBoundaries) ? '' : '(?:^|\\s)';
5013 var queryRegex = new RegExp(exprBoundary + query.toLowerCase(), 'g');
smain@google.coma0a6d322016-02-05 17:34:22 -08005014
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005015 var all = METADATA.all;
5016 for (var i = 0; i < all.length; i++) {
5017 // current search comparison, with counters for tag and title,
5018 // used later to improve ranking
5019 var s = all[i];
5020 s.matched_tag = 0;
5021 s.matched_title = 0;
5022 var matched = false;
5023
5024 // Check if query matches any tags; work backwards toward 1 to assist ranking
5025 if (s.keywords) {
5026 for (var j = s.keywords.length - 1; j >= 0; j--) {
5027 // it matches a tag
5028 if (s.keywords[j].toLowerCase().match(queryRegex)) {
5029 matched = true;
5030 s.matched_tag = j + 1; // add 1 to index position
5031 }
5032 }
5033 }
5034
5035 // Check if query matches doc title
5036 if (s.title.toLowerCase().match(queryRegex)) {
5037 matched = true;
5038 s.matched_title = 1;
5039 }
5040
5041 // Remember the doc if it matches either
5042 if (matched) {
5043 results.push(s);
5044 }
5045 }
5046
5047 // Improve the current results
5048 results = lookupBetterResult(results);
5049
5050 // Rank/sort all the matched pages
5051 rankAutocompleteDocResults(results);
5052
5053 return results;
5054 }
5055 }
5056
5057 // Replaces a match with another resource by url, if it exists.
5058 function lookupReplacementByUrl(match, url) {
5059 var replacement = METADATA.byUrl[url];
5060
5061 // Replacement resource does not exists.
5062 if (!replacement) { return; }
5063
5064 replacement.matched_title = Math.max(replacement.matched_title, match.matched_title);
5065 replacement.matched_tag = Math.max(replacement.matched_tag, match.matched_tag);
5066
5067 return replacement;
5068 }
5069
5070 // Find the localized version of a page if it exists.
5071 function lookupLocalizedVersion(match) {
5072 return METADATA.byUrl[match.baseUrl] || METADATA.byUrl[match.url];
5073 }
5074
5075 // Find the main page for a tutorial when matching a subpage.
5076 function lookupTutorialIndex(match) {
5077 // Guard for non index tutorial pages.
5078 if (match.type !== 'training' || match.url.indexOf('index.html') >= 0) { return; }
5079
5080 var indexUrl = match.url.replace(/[^\/]+$/, 'index.html');
5081 return lookupReplacementByUrl(match, indexUrl);
5082 }
5083
5084 // Find related results which are a better match for the user.
5085 function lookupBetterResult(matches) {
5086 var newMatches = [];
5087
5088 matches = matches.filter(function(match) {
5089 var newMatch = match;
5090 newMatch = lookupTutorialIndex(newMatch) || newMatch;
5091 newMatch = lookupLocalizedVersion(newMatch) || newMatch;
5092
5093 if (newMatch !== match) {
5094 newMatches.push(newMatch);
5095 }
5096
5097 return newMatch === match;
5098 });
5099
5100 return toUnique(newMatches.concat(matches));
5101 }
5102
5103 /* Order the jd doc result list based on match quality */
5104 function rankAutocompleteDocResults(matches) {
5105 if (!matches || !matches.length) {
5106 return;
5107 }
5108
5109 var _resultScoreFn = function(match) {
5110 var score = 1.0;
5111
5112 // if the query matched a tag
5113 if (match.matched_tag > 0) {
5114 // multiply score by factor relative to position in tags list (max of 3)
5115 score *= 3 / match.matched_tag;
5116
5117 // if it also matched the title
5118 if (match.matched_title > 0) {
5119 score *= 2;
5120 }
5121 } else if (match.matched_title > 0) {
5122 score *= 3;
5123 }
5124
5125 if (match.lang === currentLang) {
5126 score *= 5;
5127 }
5128
5129 return score;
5130 };
5131
5132 for (var i = 0; i < matches.length; i++) {
5133 matches[i].__resultScore = _resultScoreFn(matches[i]);
5134 }
5135
5136 matches.sort(function(a, b) {
5137 var n = b.__resultScore - a.__resultScore;
5138
5139 if (n === 0) {
5140 // lexicographical sort if scores are the same
5141 n = (a.title < b.title) ? -1 : 1;
5142 }
5143
5144 return n;
5145 });
5146 }
5147
5148 /* Order the result list based on match quality */
5149 function rankAutocompleteApiResults(query, matches) {
5150 query = query || '';
5151 if (!matches || !matches.length) {
5152 return;
5153 }
5154
5155 // helper function that gets the last occurence index of the given regex
5156 // in the given string, or -1 if not found
5157 var _lastSearch = function(s, re) {
5158 if (s === '') {
5159 return -1;
5160 }
5161 var l = -1;
5162 var tmp;
5163 while ((tmp = s.search(re)) >= 0) {
5164 if (l < 0) {
5165 l = 0;
5166 }
5167 l += tmp;
5168 s = s.substr(tmp + 1);
5169 }
5170 return l;
5171 };
5172
5173 // helper function that counts the occurrences of a given character in
5174 // a given string
5175 var _countChar = function(s, c) {
5176 var n = 0;
5177 for (var i = 0; i < s.length; i++) {
5178 if (s.charAt(i) === c) {
5179 ++n;
5180 }
5181 }
5182 return n;
5183 };
5184
5185 var queryLower = query.toLowerCase();
5186 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
5187 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
5188 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
5189
5190 var _resultScoreFn = function(result) {
5191 // scores are calculated based on exact and prefix matches,
5192 // and then number of path separators (dots) from the last
5193 // match (i.e. favoring classes and deep package names)
5194 var score = 1.0;
5195 var labelLower = result.label.toLowerCase();
5196 var t;
5197 var partsAfter;
5198 t = _lastSearch(labelLower, partExactAlnumRE);
5199 if (t >= 0) {
5200 // exact part match
5201 partsAfter = _countChar(labelLower.substr(t + 1), '.');
5202 score *= 200 / (partsAfter + 1);
5203 } else {
5204 t = _lastSearch(labelLower, partPrefixAlnumRE);
5205 if (t >= 0) {
5206 // part prefix match
5207 partsAfter = _countChar(labelLower.substr(t + 1), '.');
5208 score *= 20 / (partsAfter + 1);
5209 }
5210 }
5211
5212 return score;
5213 };
5214
5215 for (var i = 0; i < matches.length; i++) {
5216 // if the API is deprecated, default score is 0; otherwise, perform scoring
5217 if (matches[i].deprecated === 'true') {
5218 matches[i].__resultScore = 0;
5219 } else {
5220 matches[i].__resultScore = _resultScoreFn(matches[i]);
5221 }
5222 }
5223
5224 matches.sort(function(a, b) {
5225 var n = b.__resultScore - a.__resultScore;
5226
5227 if (n === 0) {
5228 // lexicographical sort if scores are the same
5229 n = (a.label < b.label) ? -1 : 1;
5230 }
5231
5232 return n;
5233 });
5234 }
5235
5236 // Destructive but fast toUnique.
5237 // http://stackoverflow.com/a/25082874
5238 function toUnique(array) {
5239 var c;
5240 var b = array.length || 1;
5241
5242 while (c = --b) {
5243 while (c--) {
5244 if (array[b] === array[c]) {
5245 array.splice(c, 1);
5246 }
5247 }
5248 }
5249 return array;
5250 }
5251
5252 return search;
5253})();
5254
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005255(function($) {
5256 'use strict';
5257
5258 /**
5259 * Smoothly scroll to location on current page.
5260 * @param el
5261 * @param options
5262 * @constructor
5263 */
5264 function ScrollButton(el, options) {
5265 this.el = $(el);
5266 this.target = $(this.el.attr('href'));
5267 this.options = $.extend({}, ScrollButton.DEFAULTS_, options);
5268
5269 if (typeof this.options.offset === 'string') {
5270 this.options.offset = $(this.options.offset).height();
5271 }
5272
5273 this.el.on('click', this.clickHandler_.bind(this));
5274 }
5275
5276 /**
5277 * Default options
5278 * @type {{duration: number, easing: string, offset: number, scrollContainer: string}}
5279 * @private
5280 */
5281 ScrollButton.DEFAULTS_ = {
5282 duration: 300,
5283 easing: 'swing',
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005284 offset: '.dac-header',
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005285 scrollContainer: 'html, body'
5286 };
5287
5288 /**
5289 * Scroll logic
5290 * @param event
5291 * @private
5292 */
5293 ScrollButton.prototype.clickHandler_ = function(event) {
5294 if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
5295 return;
5296 }
5297
5298 event.preventDefault();
5299
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005300 var position = this.getTargetPosition();
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005301 $(this.options.scrollContainer).animate({
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005302 scrollTop: position - this.options.offset
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005303 }, this.options);
5304 };
5305
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005306 ScrollButton.prototype.getTargetPosition = function() {
5307 if (this.options.scrollContainer === ScrollButton.DEFAULTS_.scrollContainer) {
5308 return this.target.offset().top;
5309 }
5310 var scrollContainer = $(this.options.scrollContainer)[0];
5311 var currentEl = this.target[0];
5312 var pos = 0;
5313 while (currentEl !== scrollContainer && currentEl !== null) {
5314 pos += currentEl.offsetTop;
5315 currentEl = currentEl.offsetParent;
5316 }
5317 return pos;
5318 };
5319
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005320 /**
5321 * jQuery plugin
5322 * @param {object} options - Override default options.
5323 */
5324 $.fn.dacScrollButton = function(options) {
5325 return this.each(function() {
5326 new ScrollButton(this, options);
5327 });
5328 };
5329
5330 /**
5331 * Data Attribute API
5332 */
5333 $(document).on('ready.aranja', function() {
5334 $('[data-scroll-button]').each(function() {
5335 $(this).dacScrollButton($(this).data());
5336 });
5337 });
5338})(jQuery);
5339
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005340/* global getLangPref */
5341(function($) {
5342 var LANG;
5343
5344 function getSearchLang() {
5345 if (!LANG) {
5346 LANG = getLangPref();
5347
5348 // Fix zh-cn to be zh-CN.
5349 LANG = LANG.replace(/-\w+/, function(m) { return m.toUpperCase(); });
5350 }
5351 return LANG;
5352 }
5353
5354 function customSearch(query, start) {
5355 var searchParams = {
5356 // current cse instance:
5357 //cx: '001482626316274216503:zu90b7s047u',
5358 // new cse instance:
5359 cx: '000521750095050289010:zpcpi1ea4s8',
5360 key: 'AIzaSyCFhbGnjW06dYwvRCU8h_zjdpS4PYYbEe8',
5361 q: query,
5362 start: start || 1,
smain@google.com38b0e222016-03-16 11:30:53 -07005363 num: 9,
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005364 hl: getSearchLang(),
5365 fields: 'queries,items(pagemap,link,title,htmlSnippet,formattedUrl)'
5366 };
5367
5368 return $.get('https://content.googleapis.com/customsearch/v1?' + $.param(searchParams));
5369 }
5370
Amanda Kassayebcf3972016-03-31 10:03:57 -04005371 function renderResults(el, results, searchAppliance) {
5372 var referenceResults = searchAppliance.getReferenceResults();
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005373 if (!results.items) {
5374 el.append($('<div>').text('No results'));
5375 return;
5376 }
5377
5378 for (var i = 0; i < results.items.length; i++) {
5379 var item = results.items[i];
Amanda Kassayebcf3972016-03-31 10:03:57 -04005380 var isDuplicate = false;
5381 $(referenceResults.android).each(function(index, result) {
5382 if (item.link.indexOf(result.link) > -1) {
5383 isDuplicate = true;
5384 return false;
5385 }
5386 });
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005387
Amanda Kassayebcf3972016-03-31 10:03:57 -04005388 if (!isDuplicate) {
5389 var hasImage = item.pagemap && item.pagemap.cse_thumbnail;
5390 var sectionMatch = item.link.match(/developer\.android\.com\/(\w*)/);
5391 var section = (sectionMatch && sectionMatch[1]) || 'blog';
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005392
Amanda Kassayebcf3972016-03-31 10:03:57 -04005393 var entry = $('<div>').addClass('dac-custom-search-entry cols');
5394
5395 if (hasImage) {
5396 var image = item.pagemap.cse_thumbnail[0];
5397 entry.append($('<div>').addClass('dac-custom-search-image-wrapper')
5398 .append($('<div>').addClass('dac-custom-search-image').css('background-image', 'url(' + image.src + ')')));
5399 }
5400
5401 entry.append($('<div>').addClass('dac-custom-search-text-wrapper')
5402 .append($('<p>').addClass('dac-custom-search-section').text(section))
5403 .append(
5404 $('<a>').text(item.title).attr('href', item.link).wrap('<h2>').parent().addClass('dac-custom-search-title')
5405 )
5406 .append($('<p>').addClass('dac-custom-search-snippet').html(item.htmlSnippet.replace(/<br>/g, '')))
5407 .append($('<a>').addClass('dac-custom-search-link').text(item.formattedUrl).attr('href', item.link)));
5408
5409 el.append(entry);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005410 }
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005411 }
5412
5413 if (results.queries.nextPage) {
5414 var loadMoreButton = $('<button id="dac-custom-search-load-more">')
5415 .addClass('dac-custom-search-load-more')
5416 .text('Load more')
5417 .click(function() {
Amanda Kassayebcf3972016-03-31 10:03:57 -04005418 loadMoreResults(el, results, searchAppliance);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005419 });
5420
5421 el.append(loadMoreButton);
5422 }
Amanda Kassayebcf3972016-03-31 10:03:57 -04005423 };
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005424
Amanda Kassayebcf3972016-03-31 10:03:57 -04005425 function loadMoreResults(el, results, searchAppliance) {
5426 var query = results.queries.request[0].searchTerms;
5427 var start = results.queries.nextPage[0].startIndex;
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005428 var loadMoreButton = el.find('#dac-custom-search-load-more');
5429
5430 loadMoreButton.text('Loading more...');
5431
5432 customSearch(query, start).then(function(results) {
5433 loadMoreButton.remove();
Amanda Kassayebcf3972016-03-31 10:03:57 -04005434 renderResults(el, results, searchAppliance);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005435 });
5436 }
5437
Amanda Kassayebcf3972016-03-31 10:03:57 -04005438 $.fn.customSearch = function(query, searchAppliance) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005439 var el = $(this);
5440
5441 customSearch(query).then(function(results) {
5442 el.empty();
Amanda Kassayebcf3972016-03-31 10:03:57 -04005443 renderResults(el, results, searchAppliance);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005444 });
5445 };
5446})(jQuery);
5447
5448/* global METADATA */
5449
5450(function($) {
5451 $.fn.dacSearchRenderHero = function(resources, query) {
5452 var el = $(this);
5453 el.empty();
5454
5455 var resource = METADATA.searchHeroCollections[query];
5456
5457 if (resource) {
5458 el.dacHero(resource, true);
5459 el.show();
5460
5461 return true;
5462 } else {
5463 el.hide();
5464 }
5465 };
5466})(jQuery);
5467
5468(function($) {
5469 $.fn.dacSearchRenderReferences = function(results, query) {
5470 var referenceCard = $('.suggest-card.reference');
5471 referenceCard.data('searchreferences.dac', {results: results, query: query});
5472 renderResults(referenceCard, results, query, false);
5473 };
5474
smain@google.com38b0e222016-03-16 11:30:53 -07005475 var ROW_COUNT_COLLAPSED = 20;
5476 var ROW_COUNT_EXPANDED = 40;
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005477 var ROW_COUNT_GOOGLE_COLLAPSED = 1;
5478 var ROW_COUNT_GOOGLE_EXPANDED = 8;
5479
5480 function onSuggestionClick(e) {
5481 var normalClick = e.which === 1 && !e.ctrlKey && !e.shiftKey && !e.metaKey;
5482 if (normalClick) {
5483 e.preventDefault();
5484 }
5485
5486 // When user clicks a suggested document, track it
5487 var url = $(e.currentTarget).attr('href');
5488 ga('send', 'event', 'Suggestion Click', 'clicked: ' + url,
5489 'query: ' + $('#search_autocomplete').val().toLowerCase(),
5490 {hitCallback: function() {
5491 if (normalClick) {
5492 document.location = url;
5493 }
5494 }});
5495 }
5496
5497 function buildLink(match) {
5498 var link = $('<a>').attr('href', window.toRoot + match.link);
5499
5500 var label = match.label;
5501 var classNameStart = label.match(/[A-Z]/) ? label.search(/[A-Z]/) : label.lastIndexOf('.') + 1;
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005502 var newLink = '<span class="namespace">' +
5503 label.substr(0, classNameStart) +
smain@google.com1c8d4a42016-02-26 16:30:50 -08005504 '</span>' +
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005505 label.substr(classNameStart, label.length);
5506
5507 link.html(newLink);
5508 return link;
5509 }
5510
5511 function buildSuggestion(match, query) {
5512 var li = $('<li>').addClass('dac-search-results-reference-entry');
5513
5514 var link = buildLink(match);
5515 link.highlightMatches(query);
5516 li.append(link);
5517 return li[0];
5518 }
5519
5520 function buildResults(results, query) {
5521 return results.map(function(match) {
5522 return buildSuggestion(match, query);
5523 });
5524 }
5525
5526 function renderAndroidResults(list, gMatches, query) {
5527 list.empty();
5528
smain@google.com1c8d4a42016-02-26 16:30:50 -08005529 var header = $('<li class="dac-search-results-reference-header">android</li>');
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005530 list.append(header);
5531
5532 if (gMatches.length > 0) {
5533 list.removeClass('no-results');
5534
5535 var resources = buildResults(gMatches, query);
5536 list.append(resources);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005537 return true;
5538 } else {
5539 list.append('<li class="dac-search-results-reference-entry-empty">No results</li>');
5540 }
5541 }
5542
5543 function renderGoogleDocsResults(list, gGoogleMatches, query) {
5544 list = $('.suggest-card.reference ul');
5545
5546 if (gGoogleMatches.length > 0) {
5547 list.append('<li class="dac-search-results-reference-header">in Google Services</li>');
5548
5549 var resources = buildResults(gGoogleMatches, query);
5550 list.append(resources);
5551
5552 return true;
5553 }
5554 }
5555
5556 function renderResults(referenceCard, results, query, expanded) {
5557 var list = referenceCard.find('ul');
5558 list.toggleClass('is-expanded', !!expanded);
5559
5560 // Figure out how many results we can show in our fixed size box.
5561 var total = expanded ? ROW_COUNT_EXPANDED : ROW_COUNT_COLLAPSED;
5562 var googleCount = expanded ? ROW_COUNT_GOOGLE_EXPANDED : ROW_COUNT_GOOGLE_COLLAPSED;
5563 googleCount = Math.max(googleCount, total - results.android.length);
5564 googleCount = Math.min(googleCount, results.docs.length);
5565
5566 if (googleCount > 0) {
5567 // If there are google results, reserve space for its header.
5568 googleCount++;
5569 }
5570
5571 var androidCount = Math.max(0, total - googleCount);
5572 if (androidCount === 0) {
5573 // Reserve space for "No reference results"
5574 googleCount--;
5575 }
5576
5577 renderAndroidResults(list, results.android.slice(0, androidCount), query);
5578 renderGoogleDocsResults(list, results.docs.slice(0, googleCount - 1), query);
5579
5580 var totalResults = results.android.length + results.docs.length;
5581 if (totalResults === 0) {
5582 list.addClass('no-results');
5583 }
5584
5585 // Tweak see more logic to account for references.
5586 var hasMore = totalResults > ROW_COUNT_COLLAPSED && !util.matchesMedia('mobile');
smain@google.com1c8d4a42016-02-26 16:30:50 -08005587 if (hasMore) {
5588 // We can't actually show all matches, only as many as the expanded list
5589 // will fit, so we actually lie if the total results count is more
5590 var moreCount = Math.min(totalResults, ROW_COUNT_EXPANDED + ROW_COUNT_GOOGLE_EXPANDED);
smain@google.com38b0e222016-03-16 11:30:53 -07005591 var $moreLink = $('<li class="dac-search-results-reference-entry-empty " data-toggle="show-more">see more matches</li>');
smain@google.com1c8d4a42016-02-26 16:30:50 -08005592 list.append($moreLink.on('click', onToggleMore));
5593 }
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005594 var searchEl = $('#search-resources');
5595 searchEl.toggleClass('dac-has-more', searchEl.hasClass('dac-has-more') || (hasMore && !expanded));
5596 searchEl.toggleClass('dac-has-less', searchEl.hasClass('dac-has-less') || (hasMore && expanded));
5597 }
5598
5599 function onToggleMore(e) {
5600 var link = $(e.currentTarget);
5601 var referenceCard = $('.suggest-card.reference');
5602 var data = referenceCard.data('searchreferences.dac');
5603
5604 if (util.matchesMedia('mobile')) { return; }
5605
5606 renderResults(referenceCard, data.results, data.query, link.data('toggle') === 'show-more');
5607 }
5608
5609 $(document).on('click', '.dac-search-results-resources [data-toggle="show-more"]', onToggleMore);
5610 $(document).on('click', '.dac-search-results-resources [data-toggle="show-less"]', onToggleMore);
5611 $(document).on('click', '.suggest-card.reference a', onSuggestionClick);
5612})(jQuery);
5613
5614(function($) {
5615 function highlightPage(query, page) {
5616 page.find('.title').highlightMatches(query);
5617 }
5618
5619 $.fn.dacSearchRenderResources = function(gDocsMatches, query) {
5620 this.resourceWidget(gDocsMatches, {
5621 itemsPerPage: 18,
5622 initialResults: 6,
5623 cardSizes: ['6x2'],
5624 onRenderPage: highlightPage.bind(null, query)
5625 });
5626
5627 return this;
5628 };
5629})(jQuery);
5630
5631/*global metadata */
5632
5633(function($, metadata) {
5634 'use strict';
5635
5636 function Search() {
5637 this.body = $('body');
5638 this.lastQuery = null;
5639 this.searchResults = $('#search-results');
5640 this.searchClose = $('[data-search-close]');
5641 this.searchClear = $('[data-search-clear]');
5642 this.searchInput = $('#search_autocomplete');
5643 this.searchResultsContent = $('#dac-search-results-content');
5644 this.searchResultsFor = $('#search-results-for');
5645 this.searchResultsHistory = $('#dac-search-results-history');
5646 this.searchResultsResources = $('#search-resources');
5647 this.searchResultsHero = $('#dac-search-results-hero');
5648 this.searchResultsReference = $('#dac-search-results-reference');
5649 this.searchHeader = $('[data-search]').data('search-input.dac');
Amanda Kassayebcf3972016-03-31 10:03:57 -04005650 this.currQueryReferenceResults = {};
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005651 }
5652
5653 Search.prototype.init = function() {
Amanda Kassayebcf3972016-03-31 10:03:57 -04005654 if (!devsite && this.checkRedirectToIndex()) { return; }
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005655
5656 this.searchHistory = window.dacStore('search-history');
5657
5658 this.searchInput.focus(this.onSearchChanged.bind(this));
5659 this.searchInput.keydown(this.handleKeyboardShortcut.bind(this));
5660 this.searchInput.on('input', this.onSearchChanged.bind(this));
5661 this.searchClear.click(this.clear.bind(this));
5662 this.searchClose.click(this.close.bind(this));
5663
5664 this.customSearch = $.fn.debounce(function(query) {
Amanda Kassayebcf3972016-03-31 10:03:57 -04005665 $('#dac-custom-search-results').customSearch(query, this);
5666 }.bind(this), 1000);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005667 // Start search shortcut (/)
5668 $('body').keyup(function(event) {
5669 if (event.which === 191 && $(event.target).is(':not(:input)')) {
5670 this.searchInput.focus();
5671 }
5672 }.bind(this));
5673
5674 $(window).on('popstate', this.onPopState.bind(this));
5675 $(window).hashchange(this.onHashChange.bind(this));
5676 this.onHashChange();
5677 };
5678
5679 Search.prototype.checkRedirectToIndex = function() {
5680 var query = this.getUrlQuery();
5681 var target = window.getLangTarget();
5682 var prefix = (target !== 'en') ? '/intl/' + target : '';
5683 var pathname = location.pathname.slice(prefix.length);
5684 if (query != null && pathname !== '/index.html') {
5685 location.href = prefix + '/index.html' + location.hash;
5686 return true;
5687 }
5688 };
5689
5690 Search.prototype.handleKeyboardShortcut = function(event) {
5691 // Close (esc)
5692 if (event.which === 27) {
5693 this.searchClose.trigger('click');
5694 event.preventDefault();
5695 }
5696
5697 // Previous result (up arrow)
5698 if (event.which === 38) {
5699 this.previousResult();
5700 event.preventDefault();
5701 }
5702
5703 // Next result (down arrow)
5704 if (event.which === 40) {
5705 this.nextResult();
5706 event.preventDefault();
5707 }
5708
5709 // Navigate to result (enter)
5710 if (event.which === 13) {
5711 this.navigateToResult();
5712 event.preventDefault();
5713 }
5714 };
5715
5716 Search.prototype.goToResult = function(relativeIndex) {
5717 var links = this.searchResults.find('a').filter(':visible');
5718 var selectedLink = this.searchResults.find('.dac-selected');
5719
5720 if (selectedLink.length) {
5721 var found = $.inArray(selectedLink[0], links);
5722
5723 selectedLink.removeClass('dac-selected');
5724 links.eq(found + relativeIndex).addClass('dac-selected');
5725 return true;
5726 } else {
5727 if (relativeIndex > 0) {
5728 links.first().addClass('dac-selected');
5729 }
5730 }
5731 };
5732
5733 Search.prototype.previousResult = function() {
5734 this.goToResult(-1);
5735 };
5736
5737 Search.prototype.nextResult = function() {
5738 this.goToResult(1);
5739 };
5740
5741 Search.prototype.navigateToResult = function() {
5742 var query = this.getQuery();
5743 var selectedLink = this.searchResults.find('.dac-selected');
5744
5745 if (selectedLink.length) {
5746 selectedLink[0].click();
5747 } else {
5748 this.searchHistory.push(query);
5749 this.addQueryToUrl(query);
5750
5751 var isMobileOrTablet = typeof window.orientation !== 'undefined';
5752
5753 if (isMobileOrTablet) {
5754 this.searchInput.blur();
5755 }
5756 }
5757 };
5758
5759 Search.prototype.onHashChange = function() {
5760 var query = this.getUrlQuery();
5761 if (query != null && query !== this.getQuery()) {
5762 this.searchInput.val(query);
5763 this.onSearchChanged();
5764 }
5765 };
5766
5767 Search.prototype.clear = function() {
5768 this.searchInput.val('');
5769 window.location.hash = '';
5770 this.onSearchChanged();
5771 this.searchInput.focus();
5772 };
5773
5774 Search.prototype.close = function() {
5775 this.removeQueryFromUrl();
5776 this.searchInput.blur();
5777 this.hideOverlay();
5778 };
5779
5780 Search.prototype.getUrlQuery = function() {
5781 var queryMatch = location.hash.match(/q=(.*)&?/);
5782 return queryMatch && queryMatch[1] && decodeURI(queryMatch[1]);
5783 };
5784
5785 Search.prototype.getQuery = function() {
5786 return this.searchInput.val().replace(/(^ +)|( +$)/g, '');
5787 };
5788
Amanda Kassayebcf3972016-03-31 10:03:57 -04005789 Search.prototype.getReferenceResults = function() {
5790 return this.currQueryReferenceResults;
5791 };
5792
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005793 Search.prototype.onSearchChanged = function() {
5794 var query = this.getQuery();
5795
5796 this.showOverlay();
5797 this.render(query);
5798 };
5799
5800 Search.prototype.render = function(query) {
5801 if (this.lastQuery === query) { return; }
5802
5803 if (query.length < 2) {
5804 query = '';
5805 }
5806
5807 this.lastQuery = query;
5808 this.searchResultsFor.text(query);
Amanda Kassayebcf3972016-03-31 10:03:57 -04005809
5810 // CSE results lag behind the metadata/reference results. We need to empty
5811 // the CSE results and add 'Loading' text so user's aren't looking at two
5812 // different sets of search results at one time.
5813 var $loadingEl =
5814 $('<div class="loadingCustomSearchResults">Loading Results...</div>');
5815 $('#dac-custom-search-results').empty().prepend($loadingEl);
5816
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005817 this.customSearch(query);
5818 var metadataResults = metadata.search(query);
5819 this.searchResultsResources.dacSearchRenderResources(metadataResults.resources, query);
5820 this.searchResultsReference.dacSearchRenderReferences(metadataResults, query);
Amanda Kassayebcf3972016-03-31 10:03:57 -04005821 this.currQueryReferenceResults = metadataResults;
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005822 var hasHero = this.searchResultsHero.dacSearchRenderHero(metadataResults.resources, query);
5823 var hasQuery = !!query;
5824
5825 this.searchResultsReference.toggle(!hasHero);
5826 this.searchResultsContent.toggle(hasQuery);
5827 this.searchResultsHistory.toggle(!hasQuery);
5828 this.addQueryToUrl(query);
5829 this.pushState();
5830 };
5831
5832 Search.prototype.addQueryToUrl = function(query) {
5833 var hash = 'q=' + encodeURI(query);
5834
5835 if (query) {
5836 if (window.history.replaceState) {
5837 window.history.replaceState(null, '', '#' + hash);
5838 } else {
5839 window.location.hash = hash;
5840 }
5841 }
5842 };
5843
5844 Search.prototype.onPopState = function() {
5845 if (!this.getUrlQuery()) {
5846 this.hideOverlay();
5847 this.searchHeader.unsetActiveState();
5848 }
5849 };
5850
5851 Search.prototype.removeQueryFromUrl = function() {
5852 window.location.hash = '';
5853 };
5854
5855 Search.prototype.pushState = function() {
5856 if (window.history.pushState && !this.lastQuery.length) {
5857 window.history.pushState(null, '');
5858 }
5859 };
5860
5861 Search.prototype.showOverlay = function() {
5862 this.body.addClass('dac-modal-open dac-search-open');
5863 };
5864
5865 Search.prototype.hideOverlay = function() {
5866 this.body.removeClass('dac-modal-open dac-search-open');
5867 };
5868
5869 $(document).on('ready.aranja', function() {
5870 var search = new Search();
5871 search.init();
5872 });
5873})(jQuery, metadata);
5874
5875window.dacStore = (function(window) {
5876 /**
5877 * Creates a new persistent store.
5878 * If localStorage is unavailable, the items are stored in memory.
5879 *
5880 * @constructor
5881 * @param {string} name The name of the store
5882 * @param {number} maxSize The maximum number of items the store can hold.
5883 */
5884 var Store = function(name, maxSize) {
5885 var content = [];
5886
5887 var hasLocalStorage = !!window.localStorage;
5888
5889 if (hasLocalStorage) {
5890 try {
5891 content = JSON.parse(window.localStorage.getItem(name) || []);
5892 } catch (e) {
5893 // Store contains invalid data
5894 window.localStorage.removeItem(name);
5895 }
5896 }
5897
5898 function push(item) {
5899 if (content[0] === item) {
5900 return;
5901 }
5902
5903 content.unshift(item);
5904
5905 if (maxSize) {
5906 content.splice(maxSize, content.length);
5907 }
5908
5909 if (hasLocalStorage) {
5910 window.localStorage.setItem(name, JSON.stringify(content));
5911 }
5912 }
5913
5914 function all() {
5915 // Return a copy
5916 return content.slice();
5917 }
5918
5919 return {
5920 push: push,
5921 all: all
5922 };
5923 };
5924
5925 var stores = {
5926 'search-history': new Store('search-history', 3)
5927 };
5928
5929 /**
5930 * Get a named persistent store.
5931 * @param {string} name
5932 * @return {Store}
5933 */
5934 return function getStore(name) {
5935 return stores[name];
5936 };
5937})(window);
5938
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005939(function($) {
5940 'use strict';
5941
5942 /**
5943 * A component that swaps two dynamic height views with an animation.
5944 * Listens for the following events:
5945 * * swap-content: triggers SwapContent.swap_()
5946 * * swap-reset: triggers SwapContent.reset()
5947 * @param el
5948 * @param options
5949 * @constructor
5950 */
5951 function SwapContent(el, options) {
5952 this.el = $(el);
5953 this.options = $.extend({}, SwapContent.DEFAULTS_, options);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005954 this.options.dynamic = this.options.dynamic === 'true';
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005955 this.containers = this.el.find(this.options.container);
5956 this.initiallyActive = this.containers.children('.' + this.options.activeClass).eq(0);
5957 this.el.on('swap-content', this.swap.bind(this));
5958 this.el.on('swap-reset', this.reset.bind(this));
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005959 this.el.find(this.options.swapButton).on('click', this.swap.bind(this));
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005960 }
5961
5962 /**
5963 * SwapContent's default settings.
5964 * @type {{activeClass: string, container: string, transitionSpeed: number}}
5965 * @private
5966 */
5967 SwapContent.DEFAULTS_ = {
5968 activeClass: 'dac-active',
5969 container: '[data-swap-container]',
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08005970 dynamic: 'true',
5971 swapButton: '[data-swap-button]',
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08005972 transitionSpeed: 500
5973 };
5974
5975 /**
5976 * Returns container's visible height.
5977 * @param container
5978 * @returns {number}
5979 */
5980 SwapContent.prototype.currentHeight = function(container) {
5981 return container.children('.' + this.options.activeClass).outerHeight();
5982 };
5983
5984 /**
5985 * Reset to show initial content
5986 */
5987 SwapContent.prototype.reset = function() {
5988 if (!this.initiallyActive.hasClass(this.initiallyActive)) {
5989 this.containers.children().toggleClass(this.options.activeClass);
5990 }
5991 };
5992
5993 /**
5994 * Complete the swap.
5995 */
5996 SwapContent.prototype.complete = function() {
5997 this.containers.height('auto');
5998 this.containers.trigger('swap-complete');
5999 };
6000
6001 /**
6002 * Perform the swap of content.
6003 */
6004 SwapContent.prototype.swap = function() {
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006005 this.containers.each(function(index, container) {
6006 container = $(container);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08006007
6008 if (!this.options.dynamic) {
6009 container.children().toggleClass(this.options.activeClass);
6010 this.complete.bind(this);
6011 return;
6012 }
6013
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006014 container.height(this.currentHeight(container)).children().toggleClass(this.options.activeClass);
6015 container.animate({height: this.currentHeight(container)}, this.options.transitionSpeed,
6016 this.complete.bind(this));
6017 }.bind(this));
6018 };
6019
6020 /**
6021 * jQuery plugin
6022 * @param {object} options - Override default options.
6023 */
6024 $.fn.dacSwapContent = function(options) {
6025 return this.each(function() {
6026 new SwapContent(this, options);
6027 });
6028 };
6029
6030 /**
6031 * Data Attribute API
6032 */
6033 $(document).on('ready.aranja', function() {
6034 $('[data-swap]').each(function() {
6035 $(this).dacSwapContent($(this).data());
6036 });
6037 });
6038})(jQuery);
6039
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08006040/* Tabs */
6041(function($) {
6042 'use strict';
6043
6044 /**
6045 * @param {HTMLElement} el - The DOM element.
6046 * @param {Object} options
6047 * @constructor
6048 */
6049 function Tabs(el, options) {
6050 this.el = $(el);
6051 this.options = $.extend({}, Tabs.DEFAULTS_, options);
6052 this.init();
6053 }
6054
6055 Tabs.DEFAULTS_ = {
6056 activeClass: 'dac-active',
6057 viewDataAttr: 'tab-view',
6058 itemDataAttr: 'tab-item'
6059 };
6060
6061 Tabs.prototype.init = function() {
6062 var itemDataAttribute = '[data-' + this.options.itemDataAttr + ']';
6063 this.tabEl_ = this.el.find(itemDataAttribute);
6064 this.tabViewEl_ = this.el.find('[data-' + this.options.viewDataAttr + ']');
6065 this.el.on('click.dac-tabs', itemDataAttribute, this.changeTabs.bind(this));
6066 };
6067
6068 Tabs.prototype.changeTabs = function(event) {
6069 var current = $(event.currentTarget);
6070 var index = current.index();
6071
6072 if (current.hasClass(this.options.activeClass)) {
6073 current.add(this.tabViewEl_.eq(index)).removeClass(this.options.activeClass);
6074 } else {
6075 this.tabEl_.add(this.tabViewEl_).removeClass(this.options.activeClass);
6076 current.add(this.tabViewEl_.eq(index)).addClass(this.options.activeClass);
6077 }
6078 };
6079
6080 /**
6081 * jQuery plugin
6082 */
6083 $.fn.dacTabs = function() {
6084 return this.each(function() {
6085 var el = $(this);
6086 new Tabs(el, el.data());
6087 });
6088 };
6089
6090 /**
6091 * Data Attribute API
6092 */
6093 $(function() {
6094 $('[data-tabs]').dacTabs();
6095 });
6096})(jQuery);
6097
6098/* Toast Component */
6099(function($) {
6100 'use strict';
6101 /**
6102 * @constant
6103 * @type {String}
6104 */
6105 var LOCAL_STORAGE_KEY = 'toast-closed-index';
6106
6107 /**
6108 * Dictionary from local storage.
6109 */
6110 var toastDictionary = localStorage.getItem(LOCAL_STORAGE_KEY);
6111 toastDictionary = toastDictionary ? JSON.parse(toastDictionary) : {};
6112
6113 /**
6114 * Variable used for caching the body.
6115 */
6116 var bodyCached;
6117
6118 /**
6119 * @param {HTMLElement} el - The DOM element.
6120 * @param {Object} options
6121 * @constructor
6122 */
6123 function Toast(el, options) {
6124 this.el = $(el);
6125 this.options = $.extend({}, Toast.DEFAULTS_, options);
6126 this.init();
6127 }
6128
6129 Toast.DEFAULTS_ = {
6130 closeBtnClass: 'dac-toast-close-btn',
6131 closeDuration: 200,
6132 visibleClass: 'dac-visible',
6133 wrapClass: 'dac-toast-wrap'
6134 };
6135
6136 /**
6137 * Generate a close button.
6138 * @returns {*|HTMLElement}
6139 */
6140 Toast.prototype.closeBtn = function() {
6141 this.closeBtnEl = this.closeBtnEl || $('<button class="' + this.options.closeBtnClass + '">' +
6142 '<i class="dac-sprite dac-close-black"></i>' +
6143 '</button>');
6144 return this.closeBtnEl;
6145 };
6146
6147 /**
6148 * Initialize a new toast element
6149 */
6150 Toast.prototype.init = function() {
6151 this.hash = this.el.text().replace(/[\s\n\t]/g, '').split('').slice(0, 128).join('');
6152
6153 if (toastDictionary[this.hash]) {
6154 return;
6155 }
6156
6157 this.closeBtn().on('click', this.onClickHandler.bind(this));
6158 this.el.find('.' + this.options.wrapClass).append(this.closeBtn());
6159 this.el.addClass(this.options.visibleClass);
6160 this.dynamicPadding(this.el.outerHeight());
6161 };
6162
6163 /**
6164 * Add padding to make sure all page is visible.
6165 */
6166 Toast.prototype.dynamicPadding = function(val) {
6167 var currentPadding = parseInt(bodyCached.css('padding-bottom') || 0);
6168 bodyCached.css('padding-bottom', val + currentPadding);
6169 };
6170
6171 /**
6172 * Remove a toast from the DOM
6173 */
6174 Toast.prototype.remove = function() {
6175 this.dynamicPadding(-this.el.outerHeight());
6176 this.el.remove();
6177 };
6178
6179 /**
6180 * Handle removal of the toast.
6181 */
6182 Toast.prototype.onClickHandler = function() {
6183 // Only fadeout toasts from top of stack. Others are removed immediately.
6184 var duration = this.el.index() === 0 ? this.options.closeDuration : 0;
6185 this.el.fadeOut(duration, this.remove.bind(this));
6186
6187 // Save closed state.
6188 toastDictionary[this.hash] = 1;
6189 localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(toastDictionary));
6190 };
6191
6192 /**
6193 * jQuery plugin
6194 * @param {object} options - Override default options.
6195 */
6196 $.fn.dacToast = function() {
6197 return this.each(function() {
6198 var el = $(this);
6199 new Toast(el, el.data());
6200 });
6201 };
6202
6203 /**
6204 * Data Attribute API
6205 */
6206 $(function() {
6207 bodyCached = $('#body-content');
6208 $('[data-toast]').dacToast();
6209 });
6210})(jQuery);
6211
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006212(function($) {
6213 function Toggle(el) {
6214 $(el).on('click.dac.togglesection', this.toggle);
6215 }
6216
6217 Toggle.prototype.toggle = function() {
6218 var $this = $(this);
6219
6220 var $parent = getParent($this);
6221 var isExpanded = $parent.hasClass('is-expanded');
6222
6223 transitionMaxHeight($parent.find('.dac-toggle-content'), !isExpanded);
6224 $parent.toggleClass('is-expanded');
6225
6226 return false;
6227 };
6228
6229 function getParent($this) {
6230 var selector = $this.attr('data-target');
6231
6232 if (!selector) {
6233 selector = $this.attr('href');
6234 selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '');
6235 }
6236
6237 var $parent = selector && $(selector);
6238
6239 $parent = $parent && $parent.length ? $parent : $this.closest('.dac-toggle');
6240
6241 return $parent.length ? $parent : $this.parent();
6242 }
6243
6244 /**
6245 * Runs a transition of max-height along with responsive styles which hide or expand the element.
6246 * @param $el
6247 * @param visible
6248 */
6249 function transitionMaxHeight($el, visible) {
6250 var contentHeight = $el.prop('scrollHeight');
6251 var targetHeight = visible ? contentHeight : 0;
6252 var duration = $el.transitionDuration();
6253
6254 // If we're hiding, first set the maxHeight we're transitioning from.
6255 if (!visible) {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08006256 $el.css({
6257 transitionDuration: '0s',
6258 maxHeight: contentHeight + 'px'
6259 })
6260 .resolveStyles()
6261 .css('transitionDuration', '');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006262 }
6263
6264 // Transition to new state
6265 $el.css('maxHeight', targetHeight);
6266
6267 // Reset maxHeight to css value after transition.
6268 setTimeout(function() {
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08006269 $el.css({
6270 transitionDuration: '0s',
6271 maxHeight: ''
6272 })
6273 .resolveStyles()
6274 .css('transitionDuration', '');
Dirk Dougherty0dc81b92015-12-08 14:49:52 -08006275 }, duration);
6276 }
6277
6278 // Utility to get the transition duration for the element.
6279 $.fn.transitionDuration = function() {
6280 var d = $(this).css('transitionDuration') || '0s';
6281
6282 return +(parseFloat(d) * (/ms/.test(d) ? 1 : 1000)).toFixed(0);
6283 };
6284
6285 // jQuery plugin
6286 $.fn.toggleSection = function(option) {
6287 return this.each(function() {
6288 var $this = $(this);
6289 var data = $this.data('dac.togglesection');
6290 if (!data) {$this.data('dac.togglesection', (data = new Toggle(this)));}
6291 if (typeof option === 'string') {data[option].call($this);}
6292 });
6293 };
6294
6295 // Data api
6296 $(document)
6297 .on('click.toggle', '[data-toggle="section"]', Toggle.prototype.toggle);
6298})(jQuery);
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -08006299
6300(function(window) {
6301 /**
6302 * Media query breakpoints. Should match CSS.
6303 */
6304 var BREAKPOINTS = {
6305 mobile: [0, 719],
6306 tablet: [720, 959],
6307 desktop: [960, 9999]
6308 };
6309
6310 /**
6311 * Fisher-Yates Shuffle (Knuth shuffle).
6312 * @param {Array} input
6313 * @returns {Array} shuffled array.
6314 */
6315 function shuffle(input) {
6316 for (var i = input.length; i >= 0; i--) {
6317 var randomIndex = Math.floor(Math.random() * (i + 1));
6318 var randomItem = input[randomIndex];
6319 input[randomIndex] = input[i];
6320 input[i] = randomItem;
6321 }
6322
6323 return input;
6324 }
6325
6326 /**
6327 * Matches media breakpoints like in CSS.
6328 * @param {string} form of either mobile, tablet or desktop.
6329 */
6330 function matchesMedia(form) {
6331 var breakpoint = BREAKPOINTS[form];
6332 return window.innerWidth >= breakpoint[0] && window.innerWidth <= breakpoint[1];
6333 }
6334
6335 window.util = {
6336 shuffle: shuffle,
6337 matchesMedia: matchesMedia
6338 };
6339})(window);
6340
6341(function($, window) {
6342 'use strict';
6343
6344 var YouTubePlayer = (function() {
6345 var player;
6346
6347 function VideoPlayer() {
6348 this.mPlayerPaused = false;
6349 this.doneSetup = false;
6350 }
6351
6352 VideoPlayer.prototype.setup = function() {
6353 // loads the IFrame Player API code asynchronously.
6354 $.getScript('https://www.youtube.com/iframe_api');
6355
6356 // Add the shadowbox HTML to the body
6357 $('body').prepend(
6358'<div id="video-player" class="Video">' +
6359 '<div id="video-overlay" class="Video-overlay" />' +
6360 '<div class="Video-container">' +
6361 '<div class="Video-frame">' +
6362 '<span class="Video-loading">Loading&hellip;</span>' +
6363 '<div id="youTubePlayer"></div>' +
6364 '</div>' +
6365 '<div class="Video-controls">' +
6366 '<button id="picture-in-picture" class="Video-button Video-button--picture-in-picture">' +
6367 '<button id="close-video" class="Video-button Video-button--close" />' +
6368 '</div>' +
6369 '</div>' +
6370'</div>');
6371
6372 this.videoPlayer = $('#video-player');
6373
6374 var pictureInPictureButton = this.videoPlayer.find('#picture-in-picture');
6375 pictureInPictureButton.on('click.aranja', this.toggleMinimizeVideo.bind(this));
6376
6377 var videoOverlay = this.videoPlayer.find('#video-overlay');
6378 var closeButton = this.videoPlayer.find('#close-video');
6379 var closeVideo = this.closeVideo.bind(this);
6380 videoOverlay.on('click.aranja', closeVideo);
6381 closeButton.on('click.aranja', closeVideo);
6382
6383 this.doneSetup = true;
6384 };
6385
6386 VideoPlayer.prototype.startYouTubePlayer = function(videoId) {
6387 this.videoPlayer.show();
6388
6389 if (!this.isLoaded) {
6390 this.queueVideo = videoId;
6391 return;
6392 }
6393
6394 this.mPlayerPaused = false;
6395 // check if we've already created this player
6396 if (!this.youTubePlayer) {
6397 // check if there's a start time specified
6398 var idAndHash = videoId.split('#');
6399 var startTime = 0;
6400 if (idAndHash.length > 1) {
6401 startTime = idAndHash[1].split('t=')[1] !== undefined ? idAndHash[1].split('t=')[1] : 0;
6402 }
6403 // enable localized player
6404 var lang = getLangPref();
6405 var captionsOn = lang === 'en' ? 0 : 1;
6406
6407 this.youTubePlayer = new YT.Player('youTubePlayer', {
6408 height: 720,
6409 width: 1280,
6410 videoId: idAndHash[0],
6411 // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
6412 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
6413 // jscs:enable
6414 events: {
6415 'onReady': this.onPlayerReady.bind(this),
6416 'onStateChange': this.onPlayerStateChange.bind(this)
6417 }
6418 });
6419 } else {
6420 // if a video different from the one already playing was requested, cue it up
6421 if (videoId !== this.getVideoId()) {
6422 this.youTubePlayer.cueVideoById(videoId);
6423 }
6424 this.youTubePlayer.playVideo();
6425 }
6426 };
6427
6428 VideoPlayer.prototype.onPlayerReady = function(event) {
6429 if (!isMobile) {
6430 event.target.playVideo();
6431 this.mPlayerPaused = false;
6432 }
6433 };
6434
6435 VideoPlayer.prototype.toggleMinimizeVideo = function(event) {
6436 event.stopPropagation();
6437 this.videoPlayer.toggleClass('Video--picture-in-picture');
6438 };
6439
6440 VideoPlayer.prototype.closeVideo = function() {
6441 try {
6442 this.youTubePlayer.pauseVideo();
6443 } catch (e) {
6444 }
6445 this.videoPlayer.fadeOut(200, function() {
6446 this.videoPlayer.removeClass('Video--picture-in-picture');
6447 }.bind(this));
6448 };
6449
6450 VideoPlayer.prototype.getVideoId = function() {
6451 // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
6452 return this.youTubePlayer && this.youTubePlayer.getVideoData().video_id;
6453 // jscs:enable
6454 };
6455
6456 /* Track youtube playback for analytics */
6457 VideoPlayer.prototype.onPlayerStateChange = function(event) {
6458 var videoId = this.getVideoId();
6459 var currentTime = this.youTubePlayer && this.youTubePlayer.getCurrentTime();
6460
6461 // Video starts, send the video ID
6462 if (event.data === YT.PlayerState.PLAYING) {
6463 if (this.mPlayerPaused) {
6464 ga('send', 'event', 'Videos', 'Resume', videoId);
6465 } else {
6466 // track the start playing event so we know from which page the video was selected
6467 ga('send', 'event', 'Videos', 'Start: ' + videoId, 'on: ' + document.location.href);
6468 }
6469 this.mPlayerPaused = false;
6470 }
6471
6472 // Video paused, send video ID and video elapsed time
6473 if (event.data === YT.PlayerState.PAUSED) {
6474 ga('send', 'event', 'Videos', 'Paused', videoId, currentTime);
6475 this.mPlayerPaused = true;
6476 }
6477
6478 // Video finished, send video ID and video elapsed time
6479 if (event.data === YT.PlayerState.ENDED) {
6480 ga('send', 'event', 'Videos', 'Finished', videoId, currentTime);
6481 this.mPlayerPaused = true;
6482 }
6483 };
6484
6485 return {
6486 getPlayer: function() {
6487 if (!player) {
6488 player = new VideoPlayer();
6489 }
6490
6491 return player;
6492 }
6493 };
6494 })();
6495
6496 var videoPlayer = YouTubePlayer.getPlayer();
6497
6498 window.onYouTubeIframeAPIReady = function() {
6499 videoPlayer.isLoaded = true;
6500
6501 if (videoPlayer.queueVideo) {
6502 videoPlayer.startYouTubePlayer(videoPlayer.queueVideo);
6503 }
6504 };
6505
6506 function wrapLinkInPlayer(e) {
6507 e.preventDefault();
6508
6509 if (!videoPlayer.doneSetup) {
6510 videoPlayer.setup();
6511 }
6512
6513 var videoIdMatches = $(e.currentTarget).attr('href').match(/(?:youtu.be\/|v=)([^&]*)/);
6514 var videoId = videoIdMatches && videoIdMatches[1];
6515
6516 if (videoId) {
6517 videoPlayer.startYouTubePlayer(videoId);
6518 }
6519 }
6520
6521 $(document).on('click.video', 'a[href*="youtube.com/watch"], a[href*="youtu.be"]', wrapLinkInPlayer);
6522})(jQuery, window);
6523
6524/**
6525 * Wide table
6526 *
6527 * Wraps tables in a scrollable area so you can read them on mobile.
6528 */
6529(function($) {
6530 function initWideTable() {
6531 $('table.jd-sumtable').each(function(i, table) {
6532 $(table).wrap('<div class="dac-expand wide-table">');
6533 });
6534 }
6535
6536 $(function() {
6537 initWideTable();
6538 });
6539})(jQuery);
Amanda Kassayebcf3972016-03-31 10:03:57 -04006540
6541/** Utilities */
6542
6543/* returns the given string with all HTML brackets converted to entities
6544 TODO: move this to the site's JS library */
6545function escapeHTML(string) {
6546 return string.replace(/</g,"&lt;")
6547 .replace(/>/g,"&gt;");
6548};
6549
6550function getQueryVariable(variable) {
6551 var query = window.location.search.substring(1);
6552 var vars = query.split("&");
6553 for (var i=0;i<vars.length;i++) {
6554 var pair = vars[i].split("=");
6555 if(pair[0] == variable){return pair[1];}
6556 }
6557 return(false);
6558};