blob: d6aa351e8fb712ad6b576d37382379f30535628b [file] [log] [blame]
Scott Maine4d8f1b2012-06-21 18:03:05 -07001var classesNav;
2var devdocNav;
3var sidenav;
4var cookie_namespace = 'android_developer';
5var NAV_PREF_TREE = "tree";
6var NAV_PREF_PANELS = "panels";
7var nav_pref;
Scott Maine4d8f1b2012-06-21 18:03:05 -07008var isMobile = false; // true if mobile, so we can adjust some layout
Scott Mainf6145542013-04-01 16:38:11 -07009var mPagePath; // initialized in ready() function
Scott Maine4d8f1b2012-06-21 18:03:05 -070010
Scott Main1b3db112012-07-03 14:06:22 -070011var basePath = getBaseUri(location.pathname);
12var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
Scott Main7e447ed2013-02-19 17:22:37 -080013var GOOGLE_DATA; // combined data for google service apis, used for search suggest
Scott Main3b90aff2013-08-01 18:09:35 -070014
Scott Main25e73002013-03-27 15:24:06 -070015// Ensure that all ajax getScript() requests allow caching
16$.ajaxSetup({
17 cache: true
18});
Scott Maine4d8f1b2012-06-21 18:03:05 -070019
20/****** ON LOAD SET UP STUFF *********/
21
Scott Maine4d8f1b2012-06-21 18:03:05 -070022$(document).ready(function() {
smain@google.com698fff02014-11-20 20:39:33 -080023
smain@google.comf887aec2016-04-28 16:54:06 -070024 showStudioSurveyButton();
25
Dirk Doughertyb87e3002014-11-18 19:34:34 -080026 // show lang dialog if the URL includes /intl/
27 //if (location.pathname.substring(0,6) == "/intl/") {
28 // var lang = location.pathname.split('/')[2];
29 // if (lang != getLangPref()) {
30 // $("#langMessage a.yes").attr("onclick","changeLangPref('" + lang
31 // + "', true); $('#langMessage').hide(); return false;");
32 // $("#langMessage .lang." + lang).show();
33 // $("#langMessage").show();
34 // }
35 //}
Scott Main7e447ed2013-02-19 17:22:37 -080036
Scott Main0e76e7e2013-03-12 10:24:07 -070037 // load json file for JD doc search suggestions
Scott Main719acb42013-12-05 16:05:09 -080038 $.getScript(toRoot + 'jd_lists_unified.js');
Scott Main7e447ed2013-02-19 17:22:37 -080039 // load json file for Android API search suggestions
40 $.getScript(toRoot + 'reference/lists.js');
41 // load json files for Google services API suggestions
Scott Main9f2971d2013-02-26 13:07:41 -080042 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080043 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
44 if(jqxhr.status === 200) {
Scott Main9f2971d2013-02-26 13:07:41 -080045 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080046 if(jqxhr.status === 200) {
47 // combine GCM and GMS data
48 GOOGLE_DATA = GMS_DATA;
49 var start = GOOGLE_DATA.length;
50 for (var i=0; i<GCM_DATA.length; i++) {
51 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
52 link:GCM_DATA[i].link, type:GCM_DATA[i].type});
53 }
54 }
55 });
56 }
57 });
58
Scott Main0e76e7e2013-03-12 10:24:07 -070059 // setup keyboard listener for search shortcut
60 $('body').keyup(function(event) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -070061 if (event.which == 191 && $(event.target).is(':not(:input)')) {
Scott Main0e76e7e2013-03-12 10:24:07 -070062 $('#search_autocomplete').focus();
63 }
64 });
Scott Main015d6162013-01-29 09:01:52 -080065
Scott Maine4d8f1b2012-06-21 18:03:05 -070066 // init the fullscreen toggle click event
67 $('#nav-swap .fullscreen').click(function(){
68 if ($(this).hasClass('disabled')) {
69 toggleFullscreen(true);
70 } else {
71 toggleFullscreen(false);
72 }
73 });
Scott Main3b90aff2013-08-01 18:09:35 -070074
Scott Maine4d8f1b2012-06-21 18:03:05 -070075 // initialize the divs with custom scrollbars
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -070076 if (window.innerWidth >= 720) {
77 $('.scroll-pane').jScrollPane({verticalGutter: 0});
78 }
Scott Maine4d8f1b2012-06-21 18:03:05 -070079
80 // set up the search close button
Dirk Doughertycbe032f2015-05-22 11:41:40 -070081 $('#search-close').on('click touchend', function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -070082 $searchInput = $('#search_autocomplete');
83 $searchInput.attr('value', '');
84 $(this).addClass("hide");
85 $("#search-container").removeClass('active');
86 $("#search_autocomplete").blur();
Scott Main0e76e7e2013-03-12 10:24:07 -070087 search_focus_changed($searchInput.get(), false);
88 hideResults();
Scott Maine4d8f1b2012-06-21 18:03:05 -070089 });
90
Scott Main3b90aff2013-08-01 18:09:35 -070091
Scott Maine4d8f1b2012-06-21 18:03:05 -070092 //Set up search
93 $("#search_autocomplete").focus(function() {
94 $("#search-container").addClass('active');
95 })
Dirk Doughertycbe032f2015-05-22 11:41:40 -070096 $("#search-container").on('mouseover touchend', function(e) {
97 if ($(e.target).is('#search-close')) { return; }
Scott Maine4d8f1b2012-06-21 18:03:05 -070098 $("#search-container").addClass('active');
99 $("#search_autocomplete").focus();
100 })
101 $("#search-container").mouseout(function() {
102 if ($("#search_autocomplete").is(":focus")) return;
103 if ($("#search_autocomplete").val() == '') {
104 setTimeout(function(){
105 $("#search-container").removeClass('active');
106 $("#search_autocomplete").blur();
107 },250);
108 }
109 })
110 $("#search_autocomplete").blur(function() {
111 if ($("#search_autocomplete").val() == '') {
112 $("#search-container").removeClass('active');
113 }
114 })
115
Scott Main3b90aff2013-08-01 18:09:35 -0700116
Scott Maine4d8f1b2012-06-21 18:03:05 -0700117 // prep nav expandos
118 var pagePath = document.location.pathname;
119 // account for intl docs by removing the intl/*/ path
120 if (pagePath.indexOf("/intl/") == 0) {
121 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
122 }
Scott Mainac2aef52013-02-12 14:15:23 -0800123
Scott Maine4d8f1b2012-06-21 18:03:05 -0700124 if (pagePath.indexOf(SITE_ROOT) == 0) {
125 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
126 pagePath += 'index.html';
127 }
128 }
129
Scott Main01a25452013-02-12 17:32:27 -0800130 // Need a copy of the pagePath before it gets changed in the next block;
131 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
132 var pagePathOriginal = pagePath;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700133 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
134 // If running locally, SITE_ROOT will be a relative path, so account for that by
135 // finding the relative URL to this page. This will allow us to find links on the page
136 // leading back to this page.
137 var pathParts = pagePath.split('/');
138 var relativePagePathParts = [];
139 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
140 for (var i = 0; i < upDirs; i++) {
141 relativePagePathParts.push('..');
142 }
143 for (var i = 0; i < upDirs; i++) {
144 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
145 }
146 relativePagePathParts.push(pathParts[pathParts.length - 1]);
147 pagePath = relativePagePathParts.join('/');
148 } else {
149 // Otherwise the page path is already an absolute URL
150 }
151
Scott Mainac2aef52013-02-12 14:15:23 -0800152 // Highlight the header tabs...
153 // highlight Design tab
Dirk Dougherty29e93432015-05-05 18:17:13 -0700154 var urlSegments = pagePathOriginal.split('/');
155 var navEl = $(".dac-nav-list");
156 var subNavEl = navEl.find(".dac-nav-secondary");
157 var parentNavEl;
Scott Mainac2aef52013-02-12 14:15:23 -0800158
Dirk Dougherty29e93432015-05-05 18:17:13 -0700159 if ($("body").hasClass("design")) {
160 navEl.find("> li.design > a").addClass("selected");
smain@google.com6040ffa2014-06-13 15:06:23 -0700161 // highlight About tabs
162 } else if ($("body").hasClass("about")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700163 if (urlSegments[1] == "about" || urlSegments[1] == "wear" || urlSegments[1] == "tv" || urlSegments[1] == "auto") {
164 navEl.find("> li.home > a").addClass('has-subnav');
165 subNavEl.find("li." + urlSegments[1] + " > a").addClass("selected");
166 } else {
167 navEl.find("> li.home > a").addClass('selected');
smain@google.com6040ffa2014-06-13 15:06:23 -0700168 }
Dirk Doughertycf7a3b92015-05-21 00:52:33 -0700169
170// highlight NDK tabs
171 } else if ($("body").hasClass("ndk")) {
172 parentNavEl = navEl.find("> li.ndk > a");
173 parentNavEl.addClass('has-subnav');
174 if ($("body").hasClass("guide")) {
175 navEl.find("> li.guides > a").addClass("selected ndk");
176 } else if ($("body").hasClass("reference")) {
177 navEl.find("> li.reference > a").addClass("selected ndk");
178 } else if ($("body").hasClass("samples")) {
179 navEl.find("> li.samples > a").addClass("selected ndk");
180 } else if ($("body").hasClass("downloads")) {
181 navEl.find("> li.downloads > a").addClass("selected ndk");
182 }
183
Scott Mainac2aef52013-02-12 14:15:23 -0800184 // highlight Develop tab
185 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700186 parentNavEl = navEl.find("> li.develop > a");
187 parentNavEl.addClass('has-subnav');
188
Scott Mainac2aef52013-02-12 14:15:23 -0800189 // In Develop docs, also highlight appropriate sub-tab
Dirk Dougherty29e93432015-05-05 18:17:13 -0700190 if (urlSegments[1] == "training") {
191 subNavEl.find("li.training > a").addClass("selected");
192 } else if (urlSegments[1] == "guide") {
193 subNavEl.find("li.guide > a").addClass("selected");
194 } else if (urlSegments[1] == "reference") {
Scott Mainac2aef52013-02-12 14:15:23 -0800195 // If the root is reference, but page is also part of Google Services, select Google
196 if ($("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700197 subNavEl.find("li.google > a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800198 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700199 subNavEl.find("li.reference > a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800200 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700201 } else if ((urlSegments[1] == "tools") || (urlSegments[1] == "sdk")) {
202 subNavEl.find("li.tools > a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800203 } else if ($("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700204 subNavEl.find("li.google > a").addClass("selected");
Dirk Dougherty4f7e5152010-09-16 10:43:40 -0700205 } else if ($("body").hasClass("samples")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700206 subNavEl.find("li.samples > a").addClass("selected");
Dirk Dougherty86120022016-02-09 18:00:05 -0800207 } else if ($("body").hasClass("preview")) {
208 subNavEl.find("li.preview > a").addClass("selected");
Dirk Dougherty29e93432015-05-05 18:17:13 -0700209 } else {
210 parentNavEl.removeClass('has-subnav').addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800211 }
Scott Mainac2aef52013-02-12 14:15:23 -0800212 // highlight Distribute tab
213 } else if ($("body").hasClass("distribute")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700214 parentNavEl = navEl.find("> li.distribute > a");
215 parentNavEl.addClass('has-subnav');
Dirk Doughertyc3921652014-05-13 16:55:26 -0700216
Dirk Dougherty29e93432015-05-05 18:17:13 -0700217 if (urlSegments[2] == "users") {
218 subNavEl.find("li.users > a").addClass("selected");
219 } else if (urlSegments[2] == "engage") {
220 subNavEl.find("li.engage > a").addClass("selected");
221 } else if (urlSegments[2] == "monetize") {
222 subNavEl.find("li.monetize > a").addClass("selected");
223 } else if (urlSegments[2] == "analyze") {
224 subNavEl.find("li.analyze > a").addClass("selected");
225 } else if (urlSegments[2] == "tools") {
Dirk Dougherty825c1aa2015-05-26 14:45:09 -0700226 subNavEl.find("li.essentials > a").addClass("selected");
Dirk Dougherty29e93432015-05-05 18:17:13 -0700227 } else if (urlSegments[2] == "stories") {
228 subNavEl.find("li.stories > a").addClass("selected");
229 } else if (urlSegments[2] == "essentials") {
230 subNavEl.find("li.essentials > a").addClass("selected");
231 } else if (urlSegments[2] == "googleplay") {
232 subNavEl.find("li.googleplay > a").addClass("selected");
233 } else {
234 parentNavEl.removeClass('has-subnav').addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700235 }
Scott Mainb16376f2014-05-21 20:35:47 -0700236 }
Scott Mainac2aef52013-02-12 14:15:23 -0800237
Scott Mainf6145542013-04-01 16:38:11 -0700238 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
239 // and highlight the sidenav
240 mPagePath = pagePath;
241 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700242 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800243
Scott Mainf6145542013-04-01 16:38:11 -0700244 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700245 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700246 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700247 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800248 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700249
250 // set up prev links
251 var $prevLink = [];
252 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700253
Scott Maine4d8f1b2012-06-21 18:03:05 -0700254 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
255false; // navigate across topic boundaries only in design docs
256 if ($prevListItem.length) {
smain@google.comc91ecb72014-06-23 10:22:23 -0700257 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Scott Main5a1123e2012-09-26 12:51:28 -0700258 // jump to last topic of previous section
259 $prevLink = $prevListItem.find('a:last');
260 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700261 // jump to previous topic in this section
262 $prevLink = $prevListItem.find('a:eq(0)');
263 }
264 } else {
265 // jump to this section's index page (if it exists)
266 var $parentListItem = $selListItem.parents('li');
267 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700268
Scott Maine4d8f1b2012-06-21 18:03:05 -0700269 // except if cross boundaries aren't allowed, and we're at the top of a section already
270 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700271 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700272 && $selListItem.hasClass('nav-section')) {
273 $prevLink = [];
274 }
275 }
276
Scott Maine4d8f1b2012-06-21 18:03:05 -0700277 // set up next links
278 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700279 var startClass = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700280 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700281
Scott Main1a00f7f2013-10-29 11:11:19 -0700282 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700283 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700284 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700285
286 // if there aren't any children, go to the next section (required for About pages)
287 if($nextLink.length == 0) {
288 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700289 } else if ($('.topic-start-link').length) {
290 // as long as there's a child link and there is a "topic start link" (we're on a landing)
291 // then set the landing page "start link" text to be the first doc title
292 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700293 }
Scott Main3b90aff2013-08-01 18:09:35 -0700294
Scott Main5a1123e2012-09-26 12:51:28 -0700295 // If the selected page has a description, then it's a class or article homepage
296 if ($selListItem.find('a[description]').length) {
297 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700298 startClass = true;
299 }
300 } else {
301 // jump to the next topic in this section (if it exists)
302 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700303 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700304 isCrossingBoundary = true;
305 // no more topics in this section, jump to the first topic in the next section
smain@google.comabf34112014-06-23 11:39:02 -0700306 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Scott Main5a1123e2012-09-26 12:51:28 -0700307 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
308 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700309 if ($nextLink.length == 0) {
310 // if that doesn't work, we're at the end of the list, so disable NEXT link
311 $('.next-page-link').attr('href','').addClass("disabled")
312 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700313 // and completely hide the one in the footer
314 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700315 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700316 }
317 }
318 }
Scott Main5a1123e2012-09-26 12:51:28 -0700319
320 if (startClass) {
321 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
322
Scott Main3b90aff2013-08-01 18:09:35 -0700323 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700324 // then we need to add a bottom border to button
325 if (!$("#tb").length) {
326 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700327 }
Scott Main5a1123e2012-09-26 12:51:28 -0700328 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
329 $('.content-footer.next-class').show();
330 $('.next-page-link').attr('href','')
331 .removeClass("hide").addClass("disabled")
332 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700333 // and completely hide the one in the footer
334 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700335 if ($nextLink.length) {
336 $('.next-class-link').attr('href',$nextLink.attr('href'))
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700337 .removeClass("hide")
338 .append(": " + $nextLink.html());
Scott Main1a00f7f2013-10-29 11:11:19 -0700339 $('.next-class-link').find('.new').empty();
340 }
Scott Main5a1123e2012-09-26 12:51:28 -0700341 } else {
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700342 $('.next-page-link').attr('href', $nextLink.attr('href'))
343 .removeClass("hide");
344 // for the footer link, also add the next page title
345 $('.content-footer .next-page-link').append(": " + $nextLink.html());
Scott Main5a1123e2012-09-26 12:51:28 -0700346 }
347
348 if (!startClass && $prevLink.length) {
349 var prevHref = $prevLink.attr('href');
350 if (prevHref == SITE_ROOT + 'index.html') {
351 // Don't show Previous when it leads to the homepage
352 } else {
353 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
354 }
Scott Main3b90aff2013-08-01 18:09:35 -0700355 }
Scott Main5a1123e2012-09-26 12:51:28 -0700356
Scott Maine4d8f1b2012-06-21 18:03:05 -0700357 }
Scott Main3b90aff2013-08-01 18:09:35 -0700358
359
360
Scott Main5a1123e2012-09-26 12:51:28 -0700361 // Set up the course landing pages for Training with class names and descriptions
362 if ($('body.trainingcourse').length) {
363 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700364
365 // create an array for all the class descriptions
366 var $classDescriptions = new Array($classLinks.length);
367 var lang = getLangPref();
368 $classLinks.each(function(index) {
369 var langDescr = $(this).attr(lang + "-description");
370 if (typeof langDescr !== 'undefined' && langDescr !== false) {
371 // if there's a class description in the selected language, use that
372 $classDescriptions[index] = langDescr;
373 } else {
374 // otherwise, use the default english description
375 $classDescriptions[index] = $(this).attr("description");
376 }
377 });
Scott Main3b90aff2013-08-01 18:09:35 -0700378
Scott Main5a1123e2012-09-26 12:51:28 -0700379 var $olClasses = $('<ol class="class-list"></ol>');
380 var $liClass;
Scott Main5a1123e2012-09-26 12:51:28 -0700381 var $h2Title;
382 var $pSummary;
383 var $olLessons;
384 var $liLesson;
385 $classLinks.each(function(index) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700386 $liClass = $('<li class="clearfix"></li>');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700387 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2 class="norule">' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700388 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700389
Scott Main5a1123e2012-09-26 12:51:28 -0700390 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700391
Scott Main5a1123e2012-09-26 12:51:28 -0700392 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700393
Scott Main5a1123e2012-09-26 12:51:28 -0700394 if ($lessons.length) {
Scott Main5a1123e2012-09-26 12:51:28 -0700395 $lessons.each(function(index) {
396 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
397 });
398 } else {
Scott Main5a1123e2012-09-26 12:51:28 -0700399 $pSummary.addClass('article');
400 }
401
Dirk Dougherty29e93432015-05-05 18:17:13 -0700402 $liClass.append($h2Title).append($pSummary).append($olLessons);
Scott Main5a1123e2012-09-26 12:51:28 -0700403 $olClasses.append($liClass);
404 });
405 $('.jd-descr').append($olClasses);
406 }
407
Scott Maine4d8f1b2012-06-21 18:03:05 -0700408 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700409 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700410
Scott Main3b90aff2013-08-01 18:09:35 -0700411
Scott Maine4d8f1b2012-06-21 18:03:05 -0700412 $(".scroll-pane").scroll(function(event) {
413 event.preventDefault();
414 return false;
415 });
416
417 /* Resize nav height when window height changes */
418 $(window).resize(function() {
419 if ($('#side-nav').length == 0) return;
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700420 setNavBarDimensions(); // do this even if sidenav isn't fixed because it could become fixed
Scott Maine4d8f1b2012-06-21 18:03:05 -0700421 // make sidenav behave when resizing the window and side-scolling is a concern
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700422 updateSideNavDimensions();
423 checkSticky();
424 resizeNav(250);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700425 });
426
Scott Maine4d8f1b2012-06-21 18:03:05 -0700427 if ($('#devdoc-nav').length) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700428 setNavBarDimensions();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700429 }
430
431
Scott Maine4d8f1b2012-06-21 18:03:05 -0700432 // Set up play-on-hover <video> tags.
433 $('video.play-on-hover').bind('click', function(){
434 $(this).get(0).load(); // in case the video isn't seekable
435 $(this).get(0).play();
436 });
437
438 // Set up tooltips
439 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700440 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700441 var $target = $(this);
442 var $tooltip = $('<div>')
443 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700444 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700445 .hide()
446 .appendTo('body');
447 $target.removeAttr('title');
448
449 $target.hover(function() {
450 // in
451 var targetRect = $target.offset();
452 targetRect.width = $target.width();
453 targetRect.height = $target.height();
454
455 $tooltip.css({
456 left: targetRect.left,
457 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
458 });
459 $tooltip.addClass('below');
460 $tooltip.show();
461 }, function() {
462 // out
463 $tooltip.hide();
464 });
465 });
466
467 // Set up <h2> deeplinks
468 $('h2').click(function() {
469 var id = $(this).attr('id');
470 if (id) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700471 if (history && history.replaceState) {
472 // Change url without scrolling.
473 history.replaceState({}, '', '#' + id);
474 } else {
475 document.location.hash = id;
476 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700477 }
478 });
479
480 //Loads the +1 button
481 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
482 po.src = 'https://apis.google.com/js/plusone.js';
483 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
484
Scott Maine4d8f1b2012-06-21 18:03:05 -0700485 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700486
Scott Maine4d8f1b2012-06-21 18:03:05 -0700487 if ($(".scroll-pane").length > 1) {
488 // Check if there's a user preference for the panel heights
489 var cookieHeight = readCookie("reference_height");
490 if (cookieHeight) {
491 restoreHeight(cookieHeight);
492 }
493 }
Scott Main3b90aff2013-08-01 18:09:35 -0700494
Scott Main06f3f2c2014-05-30 11:23:00 -0700495 // Resize once loading is finished
Scott Maine4d8f1b2012-06-21 18:03:05 -0700496 resizeNav();
Scott Main06f3f2c2014-05-30 11:23:00 -0700497 // Check if there's an anchor that we need to scroll into view.
498 // A delay is needed, because some browsers do not immediately scroll down to the anchor
499 window.setTimeout(offsetScrollForSticky, 100);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700500
Scott Main015d6162013-01-29 09:01:52 -0800501 /* init the language selector based on user cookie for lang */
502 loadLangPref();
503 changeNavLang(getLangPref());
504
505 /* setup event handlers to ensure the overflow menu is visible while picking lang */
506 $("#language select")
507 .mousedown(function() {
508 $("div.morehover").addClass("hover"); })
509 .blur(function() {
510 $("div.morehover").removeClass("hover"); });
511
512 /* some global variable setup */
513 resizePackagesNav = $("#resize-packages-nav");
514 classesNav = $("#classes-nav");
515 devdocNav = $("#devdoc-nav");
516
517 var cookiePath = "";
518 if (location.href.indexOf("/reference/") != -1) {
519 cookiePath = "reference_";
520 } else if (location.href.indexOf("/guide/") != -1) {
521 cookiePath = "guide_";
522 } else if (location.href.indexOf("/tools/") != -1) {
523 cookiePath = "tools_";
524 } else if (location.href.indexOf("/training/") != -1) {
525 cookiePath = "training_";
526 } else if (location.href.indexOf("/design/") != -1) {
527 cookiePath = "design_";
528 } else if (location.href.indexOf("/distribute/") != -1) {
529 cookiePath = "distribute_";
530 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700531
smain@google.com698fff02014-11-20 20:39:33 -0800532
533 /* setup shadowbox for any videos that want it */
smain@google.comf75ee212014-11-24 09:42:59 -0800534 var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
smain@google.com698fff02014-11-20 20:39:33 -0800535 if ($videoLinks.length) {
536 // if there's at least one, add the shadowbox HTML to the body
537 $('body').prepend(
538'<div id="video-container">'+
539 '<div id="video-frame">'+
540 '<div class="video-close">'+
541 '<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
542 '</div>'+
543 '<div id="youTubePlayer"></div>'+
544 '</div>'+
545'</div>');
546
547 // loads the IFrame Player API code asynchronously.
548 $.getScript("https://www.youtube.com/iframe_api");
549
550 $videoLinks.each(function() {
551 var videoId = $(this).attr('href').split('?v=')[1];
552 $(this).click(function(event) {
553 event.preventDefault();
554 startYouTubePlayer(videoId);
555 });
556 });
smain@google.com698fff02014-11-20 20:39:33 -0800557 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700558});
Scott Main7e447ed2013-02-19 17:22:37 -0800559// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700560
561
smain@google.com698fff02014-11-20 20:39:33 -0800562var youTubePlayer;
563function onYouTubeIframeAPIReady() {
564}
565
smain@google.com3de83c12014-12-12 19:06:52 -0800566/* Returns the height the shadowbox video should be. It's based on the current
567 height of the "video-frame" element, which is 100% height for the window.
568 Then minus the margin so the video isn't actually the full window height. */
569function getVideoHeight() {
570 var frameHeight = $("#video-frame").height();
571 var marginTop = $("#video-frame").css('margin-top').split('px')[0];
572 return frameHeight - (marginTop * 2);
573}
574
smain@google.comd162be52015-02-05 13:27:16 -0800575var mPlayerPaused = false;
576
smain@google.com698fff02014-11-20 20:39:33 -0800577function startYouTubePlayer(videoId) {
smain@google.com3de83c12014-12-12 19:06:52 -0800578 $("#video-container").show();
579 $("#video-frame").show();
smain@google.comd162be52015-02-05 13:27:16 -0800580 mPlayerPaused = false;
smain@google.com3de83c12014-12-12 19:06:52 -0800581
582 // compute the size of the player so it's centered in window
583 var maxWidth = 940; // the width of the web site content
584 var videoAspect = .5625; // based on 1280x720 resolution
585 var maxHeight = maxWidth * videoAspect;
586 var videoHeight = getVideoHeight();
587 var videoWidth = videoHeight / videoAspect;
588 if (videoWidth > maxWidth) {
589 videoWidth = maxWidth;
590 videoHeight = maxHeight;
smain@google.com570c2212014-11-25 19:01:40 -0800591 }
smain@google.com3de83c12014-12-12 19:06:52 -0800592 $("#video-frame").css('width', videoWidth);
593
594 // check if we've already created this player
smain@google.com698fff02014-11-20 20:39:33 -0800595 if (youTubePlayer == null) {
smain@google.com3de83c12014-12-12 19:06:52 -0800596 // check if there's a start time specified
597 var idAndHash = videoId.split("#");
598 var startTime = 0;
599 if (idAndHash.length > 1) {
600 startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0;
601 }
602 // enable localized player
603 var lang = getLangPref();
604 var captionsOn = lang == 'en' ? 0 : 1;
605
smain@google.com698fff02014-11-20 20:39:33 -0800606 youTubePlayer = new YT.Player('youTubePlayer', {
smain@google.com3de83c12014-12-12 19:06:52 -0800607 height: videoHeight,
608 width: videoWidth,
smain@google.com570c2212014-11-25 19:01:40 -0800609 videoId: idAndHash[0],
smain@google.com481f15c2014-12-12 11:57:27 -0800610 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
smain@google.com698fff02014-11-20 20:39:33 -0800611 events: {
smain@google.comf75ee212014-11-24 09:42:59 -0800612 'onReady': onPlayerReady,
613 'onStateChange': onPlayerStateChange
smain@google.com698fff02014-11-20 20:39:33 -0800614 }
615 });
616 } else {
smain@google.com3de83c12014-12-12 19:06:52 -0800617 // reset the size in case the user adjusted the window since last play
618 youTubePlayer.setSize(videoWidth, videoHeight);
smain@google.com94e0b532014-12-16 19:07:08 -0800619 // if a video different from the one already playing was requested, cue it up
smain@google.comd162be52015-02-05 13:27:16 -0800620 if (videoId != youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]) {
smain@google.com94e0b532014-12-16 19:07:08 -0800621 youTubePlayer.cueVideoById(videoId);
622 }
smain@google.com698fff02014-11-20 20:39:33 -0800623 youTubePlayer.playVideo();
624 }
smain@google.com698fff02014-11-20 20:39:33 -0800625}
626
627function onPlayerReady(event) {
628 event.target.playVideo();
smain@google.comd162be52015-02-05 13:27:16 -0800629 mPlayerPaused = false;
smain@google.com698fff02014-11-20 20:39:33 -0800630}
631
632function closeVideo() {
633 try {
smain@google.comf75ee212014-11-24 09:42:59 -0800634 youTubePlayer.pauseVideo();
smain@google.com698fff02014-11-20 20:39:33 -0800635 } catch(e) {
smain@google.com698fff02014-11-20 20:39:33 -0800636 }
smain@google.com3de83c12014-12-12 19:06:52 -0800637 $("#video-container").fadeOut(200);
smain@google.com698fff02014-11-20 20:39:33 -0800638}
639
smain@google.comf75ee212014-11-24 09:42:59 -0800640/* Track youtube playback for analytics */
641function onPlayerStateChange(event) {
642 // Video starts, send the video ID
643 if (event.data == YT.PlayerState.PLAYING) {
smain@google.comd162be52015-02-05 13:27:16 -0800644 if (mPlayerPaused) {
645 ga('send', 'event', 'Videos', 'Resume',
646 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]);
647 } else {
648 // track the start playing event so we know from which page the video was selected
649 ga('send', 'event', 'Videos', 'Start: ' +
650 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
651 'on: ' + document.location.href);
652 }
653 mPlayerPaused = false;
smain@google.comf75ee212014-11-24 09:42:59 -0800654 }
655 // Video paused, send video ID and video elapsed time
656 if (event.data == YT.PlayerState.PAUSED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800657 ga('send', 'event', 'Videos', 'Paused',
smain@google.comd162be52015-02-05 13:27:16 -0800658 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
659 youTubePlayer.getCurrentTime());
660 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800661 }
662 // Video finished, send video ID and video elapsed time
663 if (event.data == YT.PlayerState.ENDED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800664 ga('send', 'event', 'Videos', 'Finished',
smain@google.comd162be52015-02-05 13:27:16 -0800665 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
666 youTubePlayer.getCurrentTime());
667 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800668 }
669}
670
smain@google.com698fff02014-11-20 20:39:33 -0800671
672
Scott Mainad08f072013-08-20 16:49:57 -0700673function initExpandableNavItems(rootTag) {
674 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
675 var section = $(this).closest('li.nav-section');
676 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700677 /* hide me and descendants */
678 section.find('ul').slideUp(250, function() {
679 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700680 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700681 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700682 resizeNav();
683 });
684 } else {
685 /* show me */
686 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700687 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700688 $others.removeClass('expanded').children('ul').slideUp(250);
689
690 // now expand me
691 section.closest('li').addClass('expanded');
692 section.children('ul').slideDown(250, function() {
693 resizeNav();
694 });
695 }
696 });
Scott Mainf0093852013-08-22 11:37:11 -0700697
698 // Stop expand/collapse behavior when clicking on nav section links
699 // (since we're navigating away from the page)
700 // This selector captures the first instance of <a>, but not those with "#" as the href.
701 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
702 window.location.href = $(this).attr('href');
703 return false;
704 });
Scott Mainad08f072013-08-20 16:49:57 -0700705}
706
Dirk Doughertyc3921652014-05-13 16:55:26 -0700707
708/** Create the list of breadcrumb links in the sticky header */
709function buildBreadcrumbs() {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700710 var $breadcrumbUl = $(".dac-header-crumbs");
711 var primaryNavLink = ".dac-nav-list > .dac-nav-item > .dac-nav-link";
712
Dirk Doughertyc3921652014-05-13 16:55:26 -0700713 // Add the secondary horizontal nav item, if provided
Dirk Dougherty29e93432015-05-05 18:17:13 -0700714 var $selectedSecondNav = $(".dac-nav-secondary .dac-nav-link.selected").clone()
715 .attr('class', 'dac-header-crumbs-link');
716
Dirk Doughertyc3921652014-05-13 16:55:26 -0700717 if ($selectedSecondNav.length) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700718 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedSecondNav));
Dirk Doughertyc3921652014-05-13 16:55:26 -0700719 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700720
Dirk Doughertyc3921652014-05-13 16:55:26 -0700721 // Add the primary horizontal nav
Dirk Dougherty29e93432015-05-05 18:17:13 -0700722 var $selectedFirstNav = $(primaryNavLink + ".selected, " + primaryNavLink + ".has-subnav").clone()
723 .attr('class', 'dac-header-crumbs-link');
724
Dirk Doughertyc3921652014-05-13 16:55:26 -0700725 // If there's no header nav item, use the logo link and title from alt text
726 if ($selectedFirstNav.length < 1) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700727 $selectedFirstNav = $('<a class="dac-header-crumbs-link">')
Dirk Doughertyc3921652014-05-13 16:55:26 -0700728 .attr('href', $("div#header .logo a").attr('href'))
729 .text($("div#header .logo img").attr('alt'));
730 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700731 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedFirstNav));
Dirk Doughertyc3921652014-05-13 16:55:26 -0700732}
733
734
735
Scott Maine624b3f2013-09-12 12:56:41 -0700736/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700737function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700738 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
739 if ($("ul#nav li.selected").length) {
740 unHighlightSidenav();
741 }
742 // look for URL in sidenav, including the hash
743 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
744
745 // If the selNavLink is still empty, look for it without the hash
746 if ($selNavLink.length == 0) {
747 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
748 }
749
Scott Mainf6145542013-04-01 16:38:11 -0700750 var $selListItem;
751 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700752 // Find this page's <li> in sidenav and set selected
753 $selListItem = $selNavLink.closest('li');
754 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700755
Scott Mainf6145542013-04-01 16:38:11 -0700756 // Traverse up the tree and expand all parent nav-sections
757 $selNavLink.parents('li.nav-section').each(function() {
758 $(this).addClass('expanded');
759 $(this).children('ul').show();
760 });
761 }
762}
763
Scott Maine624b3f2013-09-12 12:56:41 -0700764function unHighlightSidenav() {
765 $("ul#nav li.selected").removeClass("selected");
766 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
767}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700768
769function toggleFullscreen(enable) {
770 var delay = 20;
771 var enabled = true;
772 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
773 if (enable) {
774 // Currently NOT USING fullscreen; enable fullscreen
775 stylesheet.removeAttr('disabled');
776 $('#nav-swap .fullscreen').removeClass('disabled');
777 $('#devdoc-nav').css({left:''});
778 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
779 enabled = true;
780 } else {
781 // Currently USING fullscreen; disable fullscreen
782 stylesheet.attr('disabled', 'disabled');
783 $('#nav-swap .fullscreen').addClass('disabled');
784 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
785 enabled = false;
786 }
smain@google.com6bdcb982014-11-14 11:53:07 -0800787 writeCookie("fullscreen", enabled, null);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700788 setNavBarDimensions();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700789 resizeNav(delay);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700790 updateSideNavDimensions();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700791 setTimeout(initSidenavHeightResize,delay);
792}
793
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700794// TODO: Refactor into a closure.
795var navBarLeftPos;
796var navBarWidth;
797function setNavBarDimensions() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700798 navBarLeftPos = $('#body-content').offset().left;
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700799 navBarWidth = $('#side-nav').width();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700800}
801
802
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700803function updateSideNavDimensions() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700804 var newLeft = $(window).scrollLeft() - navBarLeftPos;
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700805 $('#devdoc-nav').css({left: -newLeft, width: navBarWidth});
Dirk Dougherty29e93432015-05-05 18:17:13 -0700806 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('padding-left')))});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700807}
Scott Main3b90aff2013-08-01 18:09:35 -0700808
Scott Maine4d8f1b2012-06-21 18:03:05 -0700809// TODO: use $(document).ready instead
810function addLoadEvent(newfun) {
811 var current = window.onload;
812 if (typeof window.onload != 'function') {
813 window.onload = newfun;
814 } else {
815 window.onload = function() {
816 current();
817 newfun();
818 }
819 }
820}
821
822var agent = navigator['userAgent'].toLowerCase();
823// If a mobile phone, set flag and do mobile setup
824if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
825 (agent.indexOf("blackberry") != -1) ||
826 (agent.indexOf("webos") != -1) ||
827 (agent.indexOf("mini") != -1)) { // opera mini browsers
828 isMobile = true;
829}
830
831
Scott Main498d7102013-08-21 15:47:38 -0700832$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700833 $("pre:not(.no-pretty-print)").addClass("prettyprint");
834 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700835});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700836
Scott Maine4d8f1b2012-06-21 18:03:05 -0700837
838
839
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700840/* ######### RESIZE THE SIDENAV ########## */
Scott Maine4d8f1b2012-06-21 18:03:05 -0700841
842function resizeNav(delay) {
843 var $nav = $("#devdoc-nav");
844 var $window = $(window);
845 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700846
Scott Maine4d8f1b2012-06-21 18:03:05 -0700847 // Get the height of entire window and the total header height.
848 // Then figure out based on scroll position whether the header is visible
849 var windowHeight = $window.height();
850 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700851 var headerHeight = $('#header-wrapper').outerHeight();
852 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700853
854 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700855 // Could be either margin or top position, depending on whether the nav is fixed.
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700856 var topMargin = (parseInt($nav.css('top')) || 20) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700857 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700858
Scott Maine4d8f1b2012-06-21 18:03:05 -0700859 // Depending on whether the header is visible, set the side nav's height.
860 if (headerVisible) {
861 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700862 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700863 } else {
864 // Once header is off screen, the nav height is almost full window height
865 navHeight = windowHeight - topMargin;
866 }
Scott Main3b90aff2013-08-01 18:09:35 -0700867
868
869
Scott Maine4d8f1b2012-06-21 18:03:05 -0700870 $scrollPanes = $(".scroll-pane");
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700871 if ($window.width() < 720) {
872 $nav.css('height', '');
873 } else if ($scrollPanes.length > 1) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700874 // subtract the height of the api level widget and nav swapper from the available nav height
875 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700876
Scott Maine4d8f1b2012-06-21 18:03:05 -0700877 $("#swapper").css({height:navHeight + "px"});
878 if ($("#nav-tree").is(":visible")) {
879 $("#nav-tree").css({height:navHeight});
880 }
Scott Main3b90aff2013-08-01 18:09:35 -0700881
882 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700883 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700884
885 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700886 // then the package panel should begin to shrink
887 if (parseInt(classesHeight) <= 0) {
888 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
889 $("#packages-nav").css({height:navHeight - 10});
890 }
Scott Main3b90aff2013-08-01 18:09:35 -0700891
Scott Maine4d8f1b2012-06-21 18:03:05 -0700892 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
893 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700894
895
Scott Maine4d8f1b2012-06-21 18:03:05 -0700896 } else {
897 $nav.height(navHeight);
898 }
Scott Main3b90aff2013-08-01 18:09:35 -0700899
Scott Maine4d8f1b2012-06-21 18:03:05 -0700900 if (delay) {
901 updateFromResize = true;
902 delayedReInitScrollbars(delay);
903 } else {
904 reInitScrollbars();
905 }
Scott Main3b90aff2013-08-01 18:09:35 -0700906
Scott Maine4d8f1b2012-06-21 18:03:05 -0700907}
908
909var updateScrollbars = false;
910var updateFromResize = false;
911
912/* Re-initialize the scrollbars to account for changed nav size.
913 * This method postpones the actual update by a 1/4 second in order to optimize the
914 * scroll performance while the header is still visible, because re-initializing the
915 * scroll panes is an intensive process.
916 */
917function delayedReInitScrollbars(delay) {
918 // If we're scheduled for an update, but have received another resize request
919 // before the scheduled resize has occured, just ignore the new request
920 // (and wait for the scheduled one).
921 if (updateScrollbars && updateFromResize) {
922 updateFromResize = false;
923 return;
924 }
Scott Main3b90aff2013-08-01 18:09:35 -0700925
Scott Maine4d8f1b2012-06-21 18:03:05 -0700926 // We're scheduled for an update and the update request came from this method's setTimeout
927 if (updateScrollbars && !updateFromResize) {
928 reInitScrollbars();
929 updateScrollbars = false;
930 } else {
931 updateScrollbars = true;
932 updateFromResize = false;
933 setTimeout('delayedReInitScrollbars()',delay);
934 }
935}
936
937/* Re-initialize the scrollbars to account for changed nav size. */
938function reInitScrollbars() {
939 var pane = $(".scroll-pane").each(function(){
940 var api = $(this).data('jsp');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700941 if (!api) {return;}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700942 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700943 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700944 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
945}
946
947
948/* Resize the height of the nav panels in the reference,
949 * and save the new size to a cookie */
950function saveNavPanels() {
951 var basePath = getBaseUri(location.pathname);
952 var section = basePath.substring(1,basePath.indexOf("/",1));
smain@google.com6bdcb982014-11-14 11:53:07 -0800953 writeCookie("height", resizePackagesNav.css("height"), section);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700954}
955
956
957
958function restoreHeight(packageHeight) {
959 $("#resize-packages-nav").height(packageHeight);
960 $("#packages-nav").height(packageHeight);
961 // var classesHeight = navHeight - packageHeight;
962 // $("#classes-nav").css({height:classesHeight});
963 // $("#classes-nav .jspContainer").css({height:classesHeight});
964}
965
966
967
968/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
969
970
971
972
973
Scott Main3b90aff2013-08-01 18:09:35 -0700974/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700975 This is called when the page finished loading. */
976function scrollIntoView(nav) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700977 return;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700978 var $nav = $("#"+nav);
979 var element = $nav.jScrollPane({/* ...settings... */});
980 var api = element.data('jsp');
981
982 if ($nav.is(':visible')) {
983 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700984 if ($selected.length == 0) {
985 // If no selected item found, exit
986 return;
987 }
Scott Main52dd2062013-08-15 12:22:28 -0700988 // get the selected item's offset from its container nav by measuring the item's offset
989 // relative to the document then subtract the container nav's offset relative to the document
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800990 var selectedOffset = $selected.offset().top - $nav.offset().top + 60;
Scott Main52dd2062013-08-15 12:22:28 -0700991 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
992 // if it's more than 80% down the nav
993 // scroll the item up by an amount equal to 80% the container nav's height
994 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700995 }
996 }
997}
998
999
1000
1001
1002
1003
1004/* Show popup dialogs */
1005function showDialog(id) {
1006 $dialog = $("#"+id);
1007 $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>');
1008 $dialog.wrapInner('<div/>');
1009 $dialog.removeClass("hide");
1010}
1011
1012
1013
1014
1015
1016/* ######### COOKIES! ########## */
1017
1018function readCookie(cookie) {
1019 var myCookie = cookie_namespace+"_"+cookie+"=";
1020 if (document.cookie) {
1021 var index = document.cookie.indexOf(myCookie);
1022 if (index != -1) {
1023 var valStart = index + myCookie.length;
1024 var valEnd = document.cookie.indexOf(";", valStart);
1025 if (valEnd == -1) {
1026 valEnd = document.cookie.length;
1027 }
1028 var val = document.cookie.substring(valStart, valEnd);
1029 return val;
1030 }
1031 }
1032 return 0;
1033}
1034
smain@google.com6bdcb982014-11-14 11:53:07 -08001035function writeCookie(cookie, val, section) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001036 if (val==undefined) return;
1037 section = section == null ? "_" : "_"+section+"_";
smain@google.com6bdcb982014-11-14 11:53:07 -08001038 var age = 2*365*24*60*60; // set max-age to 2 years
Scott Main3b90aff2013-08-01 18:09:35 -07001039 var cookieValue = cookie_namespace + section + cookie + "=" + val
smain@google.com80e38f42014-11-03 10:47:12 -08001040 + "; max-age=" + age +"; path=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -07001041 document.cookie = cookieValue;
1042}
1043
1044/* ######### END COOKIES! ########## */
1045
1046
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001047var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -07001048var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001049var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -07001050/* Sets the vertical scoll position at which the sticky bar should appear.
1051 This method is called to reset the position when search results appear or hide */
1052function setStickyTop() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001053 stickyTop = $('#header-wrapper').outerHeight() - $('#header > .dac-header-inner').outerHeight();
Dirk Doughertyc3921652014-05-13 16:55:26 -07001054}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001055
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001056/*
Scott Mainb16376f2014-05-21 20:35:47 -07001057 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -07001058 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001059$(window).scroll(function(event) {
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001060 // Exit if the mouse target is a DIV, because that means the event is coming
1061 // from a scrollable div and so there's no need to make adjustments to our layout
1062 if ($(event.target).nodeName == "DIV") {
1063 return;
1064 }
1065
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001066 checkSticky();
1067});
1068
1069function checkSticky() {
1070 setStickyTop();
1071 var $headerEl = $('#header');
1072 // Exit if there's no sidenav
1073 if ($('#side-nav').length == 0) return;
1074
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001075 var top = $(window).scrollTop();
1076 // we set the navbar fixed when the scroll position is beyond the height of the site header...
Dirk Doughertycf7a3b92015-05-21 00:52:33 -07001077 var shouldBeSticky = top > stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001078 // ... except if the document content is shorter than the sidenav height.
1079 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1080 if ($("#doc-col").height() < $("#side-nav").height()) {
1081 shouldBeSticky = false;
1082 }
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001083 // Nor on mobile
1084 if (window.innerWidth < 720) {
1085 shouldBeSticky = false;
1086 }
Scott Mainf5257812014-05-22 17:26:38 -07001087 // Account for horizontal scroll
1088 var scrollLeft = $(window).scrollLeft();
1089 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1090 if (sticky && (scrollLeft != prevScrollLeft)) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001091 updateSideNavDimensions();
Scott Mainf5257812014-05-22 17:26:38 -07001092 prevScrollLeft = scrollLeft;
1093 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001094
1095 // Don't continue if the header is sufficently far away
1096 // (to avoid intensive resizing that slows scrolling)
1097 if (sticky == shouldBeSticky) {
1098 return;
1099 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001100
1101 // If sticky header visible and position is now near top, hide sticky
1102 if (sticky && !shouldBeSticky) {
1103 sticky = false;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001104 // make the sidenav static again
1105 $('#devdoc-nav')
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001106 .removeClass('fixed')
1107 .css({'width':'auto','margin':''});
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001108 // delay hide the sticky
Dirk Dougherty29e93432015-05-05 18:17:13 -07001109 $headerEl.removeClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001110
1111 // update the sidenaav position for side scrolling
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001112 updateSideNavDimensions();
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001113 } else if (!sticky && shouldBeSticky) {
1114 sticky = true;
Dirk Dougherty29e93432015-05-05 18:17:13 -07001115 $headerEl.addClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001116
1117 // make the sidenav fixed
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001118 $('#devdoc-nav')
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001119 .addClass('fixed');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001120
1121 // update the sidenaav position for side scrolling
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001122 updateSideNavDimensions();
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001123
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001124 }
1125 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001126}
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001127
1128/*
1129 * Manages secion card states and nav resize to conclude loading
1130 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001131(function() {
1132 $(document).ready(function() {
1133
Dirk Doughertyc3921652014-05-13 16:55:26 -07001134 // Stack hover states
1135 $('.section-card-menu').each(function(index, el) {
1136 var height = $(el).height();
1137 $(el).css({height:height+'px', position:'relative'});
1138 var $cardInfo = $(el).find('.card-info');
1139
1140 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1141 });
1142
Dirk Doughertyc3921652014-05-13 16:55:26 -07001143 });
1144
1145})();
1146
Scott Maine4d8f1b2012-06-21 18:03:05 -07001147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
Scott Maind7026f72013-06-17 15:08:49 -07001160/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001161
1162
1163
1164
1165
1166function toggle(obj, slide) {
1167 var ul = $("ul:first", obj);
1168 var li = ul.parent();
1169 if (li.hasClass("closed")) {
1170 if (slide) {
1171 ul.slideDown("fast");
1172 } else {
1173 ul.show();
1174 }
1175 li.removeClass("closed");
1176 li.addClass("open");
1177 $(".toggle-img", li).attr("title", "hide pages");
1178 } else {
1179 ul.slideUp("fast");
1180 li.removeClass("open");
1181 li.addClass("closed");
1182 $(".toggle-img", li).attr("title", "show pages");
1183 }
1184}
1185
1186
Scott Maine4d8f1b2012-06-21 18:03:05 -07001187function buildToggleLists() {
1188 $(".toggle-list").each(
1189 function(i) {
1190 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1191 $(this).addClass("closed");
1192 });
1193}
1194
1195
1196
Scott Maind7026f72013-06-17 15:08:49 -07001197function hideNestedItems(list, toggle) {
1198 $list = $(list);
1199 // hide nested lists
1200 if($list.hasClass('showing')) {
1201 $("li ol", $list).hide('fast');
1202 $list.removeClass('showing');
1203 // show nested lists
1204 } else {
1205 $("li ol", $list).show('fast');
1206 $list.addClass('showing');
1207 }
1208 $(".more,.less",$(toggle)).toggle();
1209}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001210
1211
smain@google.com95948b82014-06-16 19:24:25 -07001212/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1213function setupIdeDocToggle() {
1214 $( "select.ide" ).change(function() {
1215 var selected = $(this).find("option:selected").attr("value");
1216 $(".select-ide").hide();
1217 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001218
smain@google.com95948b82014-06-16 19:24:25 -07001219 $("select.ide").val(selected);
1220 });
1221}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246/* REFERENCE NAV SWAP */
1247
1248
1249function getNavPref() {
1250 var v = readCookie('reference_nav');
1251 if (v != NAV_PREF_TREE) {
1252 v = NAV_PREF_PANELS;
1253 }
1254 return v;
1255}
1256
1257function chooseDefaultNav() {
1258 nav_pref = getNavPref();
1259 if (nav_pref == NAV_PREF_TREE) {
1260 $("#nav-panels").toggle();
1261 $("#panel-link").toggle();
1262 $("#nav-tree").toggle();
1263 $("#tree-link").toggle();
1264 }
1265}
1266
1267function swapNav() {
1268 if (nav_pref == NAV_PREF_TREE) {
1269 nav_pref = NAV_PREF_PANELS;
1270 } else {
1271 nav_pref = NAV_PREF_TREE;
1272 init_default_navtree(toRoot);
1273 }
smain@google.com6bdcb982014-11-14 11:53:07 -08001274 writeCookie("nav", nav_pref, "reference");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001275
1276 $("#nav-panels").toggle();
1277 $("#panel-link").toggle();
1278 $("#nav-tree").toggle();
1279 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001280
Scott Maine4d8f1b2012-06-21 18:03:05 -07001281 resizeNav();
1282
1283 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1284 $("#nav-tree .jspContainer:visible")
1285 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1286 // Another nasty hack to make the scrollbar appear now that we have height
1287 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001288
Scott Maine4d8f1b2012-06-21 18:03:05 -07001289 if ($("#nav-tree").is(':visible')) {
1290 scrollIntoView("nav-tree");
1291 } else {
1292 scrollIntoView("packages-nav");
1293 scrollIntoView("classes-nav");
1294 }
1295}
1296
1297
1298
Scott Mainf5089842012-08-14 16:31:07 -07001299/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001300/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001301/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001302
1303function getBaseUri(uri) {
1304 var intlUrl = (uri.substring(0,6) == "/intl/");
1305 if (intlUrl) {
1306 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1307 base = base.substring(base.indexOf('/')+1, base.length);
1308 //alert("intl, returning base url: /" + base);
1309 return ("/" + base);
1310 } else {
1311 //alert("not intl, returning uri as found.");
1312 return uri;
1313 }
1314}
1315
1316function requestAppendHL(uri) {
1317//append "?hl=<lang> to an outgoing request (such as to blog)
1318 var lang = getLangPref();
1319 if (lang) {
1320 var q = 'hl=' + lang;
1321 uri += '?' + q;
1322 window.location = uri;
1323 return false;
1324 } else {
1325 return true;
1326 }
1327}
1328
1329
Scott Maine4d8f1b2012-06-21 18:03:05 -07001330function changeNavLang(lang) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001331 if (lang === 'en') { return; }
1332
Dirk Doughertya2c9b912015-10-05 16:45:19 -07001333 var $links = $("a[" + lang + "-lang],p[" + lang + "-lang]");
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001334 $links.each(function(){ // for each link with a translation
Scott Main6eb95f12012-10-02 17:12:23 -07001335 var $link = $(this);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001336 // put the desired language from the attribute as the text
1337 $link.text($link.attr(lang + '-lang'))
Scott Main6eb95f12012-10-02 17:12:23 -07001338 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001339}
1340
Scott Main015d6162013-01-29 09:01:52 -08001341function changeLangPref(lang, submit) {
smain@google.com6bdcb982014-11-14 11:53:07 -08001342 writeCookie("pref_lang", lang, null);
Scott Main015d6162013-01-29 09:01:52 -08001343
1344 // ####### TODO: Remove this condition once we're stable on devsite #######
1345 // This condition is only needed if we still need to support legacy GAE server
1346 if (devsite) {
1347 // Switch language when on Devsite server
1348 if (submit) {
1349 $("#setlang").submit();
1350 }
1351 } else {
1352 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001353 if (submit) {
1354 window.location = getBaseUri(location.pathname);
1355 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001356 }
1357}
1358
1359function loadLangPref() {
1360 var lang = readCookie("pref_lang");
1361 if (lang != 0) {
1362 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1363 }
1364}
1365
1366function getLangPref() {
1367 var lang = $("#language").find(":selected").attr("value");
1368 if (!lang) {
1369 lang = readCookie("pref_lang");
1370 }
1371 return (lang != 0) ? lang : 'en';
1372}
1373
1374/* ########## END LOCALIZATION ############ */
1375
1376
1377
1378
1379
1380
1381/* Used to hide and reveal supplemental content, such as long code samples.
1382 See the companion CSS in android-developer-docs.css */
1383function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001384 var div = $(obj).closest(".toggle-content");
1385 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001386 if (div.hasClass("closed")) { // if it's closed, open it
1387 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001388 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001389 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001390 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
smain@google.comc0401872016-03-16 11:48:23 -07001391 + "assets/images/styles/disclosure_up.png");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001392 } else { // if it's open, close it
1393 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001394 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001395 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001396 div.find(".toggle-content").removeClass("open").addClass("closed")
1397 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001398 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
smain@google.comc0401872016-03-16 11:48:23 -07001399 + "assets/images/styles/disclosure_down.png");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001400 });
1401 }
1402 return false;
1403}
Scott Mainf5089842012-08-14 16:31:07 -07001404
1405
Scott Maindb3678b2012-10-23 14:13:41 -07001406/* New version of expandable content */
1407function toggleExpandable(link,id) {
1408 if($(id).is(':visible')) {
1409 $(id).slideUp();
1410 $(link).removeClass('expanded');
1411 } else {
1412 $(id).slideDown();
1413 $(link).addClass('expanded');
1414 }
1415}
1416
1417function hideExpandable(ids) {
1418 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001419 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001420}
1421
Scott Mainf5089842012-08-14 16:31:07 -07001422
1423
1424
1425
Scott Main3b90aff2013-08-01 18:09:35 -07001426/*
Scott Mainf5089842012-08-14 16:31:07 -07001427 * Slideshow 1.0
1428 * Used on /index.html and /develop/index.html for carousel
1429 *
1430 * Sample usage:
1431 * HTML -
1432 * <div class="slideshow-container">
1433 * <a href="" class="slideshow-prev">Prev</a>
1434 * <a href="" class="slideshow-next">Next</a>
1435 * <ul>
1436 * <li class="item"><img src="images/marquee1.jpg"></li>
1437 * <li class="item"><img src="images/marquee2.jpg"></li>
1438 * <li class="item"><img src="images/marquee3.jpg"></li>
1439 * <li class="item"><img src="images/marquee4.jpg"></li>
1440 * </ul>
1441 * </div>
1442 *
1443 * <script type="text/javascript">
1444 * $('.slideshow-container').dacSlideshow({
1445 * auto: true,
1446 * btnPrev: '.slideshow-prev',
1447 * btnNext: '.slideshow-next'
1448 * });
1449 * </script>
1450 *
1451 * Options:
1452 * btnPrev: optional identifier for previous button
1453 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001454 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001455 * auto: whether or not to auto-proceed
1456 * speed: animation speed
1457 * autoTime: time between auto-rotation
1458 * easing: easing function for transition
1459 * start: item to select by default
1460 * scroll: direction to scroll in
1461 * pagination: whether or not to include dotted pagination
1462 *
1463 */
1464
1465 (function($) {
1466 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001467
Scott Mainf5089842012-08-14 16:31:07 -07001468 //Options - see above
1469 o = $.extend({
1470 btnPrev: null,
1471 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001472 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001473 auto: true,
1474 speed: 500,
1475 autoTime: 12000,
1476 easing: null,
1477 start: 0,
1478 scroll: 1,
1479 pagination: true
1480
1481 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001482
1483 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001484 return this.each(function() {
1485
1486 var running = false;
1487 var animCss = o.vertical ? "top" : "left";
1488 var sizeCss = o.vertical ? "height" : "width";
1489 var div = $(this);
1490 var ul = $("ul", div);
1491 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001492 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001493 var timer = null;
1494
1495 var li = $("li", ul);
1496 var itemLength = li.size();
1497 var curr = o.start;
1498
1499 li.css({float: o.vertical ? "none" : "left"});
1500 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1501 div.css({position: "relative", "z-index": "2", left: "0px"});
1502
1503 var liSize = o.vertical ? height(li) : width(li);
1504 var ulSize = liSize * itemLength;
1505 var divSize = liSize;
1506
1507 li.css({width: li.width(), height: li.height()});
1508 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1509
1510 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001511
Scott Mainf5089842012-08-14 16:31:07 -07001512 //Pagination
1513 if (o.pagination) {
1514 var pagination = $("<div class='pagination'></div>");
1515 var pag_ul = $("<ul></ul>");
1516 if (tl > 1) {
1517 for (var i=0;i<tl;i++) {
1518 var li = $("<li>"+i+"</li>");
1519 pag_ul.append(li);
1520 if (i==o.start) li.addClass('active');
1521 li.click(function() {
1522 go(parseInt($(this).text()));
1523 })
1524 }
1525 pagination.append(pag_ul);
1526 div.append(pagination);
1527 }
1528 }
Scott Main3b90aff2013-08-01 18:09:35 -07001529
Scott Mainf5089842012-08-14 16:31:07 -07001530 //Previous button
1531 if(o.btnPrev)
1532 $(o.btnPrev).click(function(e) {
1533 e.preventDefault();
1534 return go(curr-o.scroll);
1535 });
1536
1537 //Next button
1538 if(o.btnNext)
1539 $(o.btnNext).click(function(e) {
1540 e.preventDefault();
1541 return go(curr+o.scroll);
1542 });
Scott Maineb410352013-01-14 19:03:40 -08001543
1544 //Pause button
1545 if(o.btnPause)
1546 $(o.btnPause).click(function(e) {
1547 e.preventDefault();
1548 if ($(this).hasClass('paused')) {
1549 startRotateTimer();
1550 } else {
1551 pauseRotateTimer();
1552 }
1553 });
Scott Main3b90aff2013-08-01 18:09:35 -07001554
Scott Mainf5089842012-08-14 16:31:07 -07001555 //Auto rotation
1556 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001557
Scott Mainf5089842012-08-14 16:31:07 -07001558 function startRotateTimer() {
1559 clearInterval(timer);
1560 timer = setInterval(function() {
1561 if (curr == tl-1) {
1562 go(0);
1563 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001564 go(curr+o.scroll);
1565 }
Scott Mainf5089842012-08-14 16:31:07 -07001566 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001567 $(o.btnPause).removeClass('paused');
1568 }
1569
1570 function pauseRotateTimer() {
1571 clearInterval(timer);
1572 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001573 }
1574
1575 //Go to an item
1576 function go(to) {
1577 if(!running) {
1578
1579 if(to<0) {
1580 to = itemLength-1;
1581 } else if (to>itemLength-1) {
1582 to = 0;
1583 }
1584 curr = to;
1585
1586 running = true;
1587
1588 ul.animate(
1589 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1590 function() {
1591 running = false;
1592 }
1593 );
1594
1595 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1596 $( (curr-o.scroll<0 && o.btnPrev)
1597 ||
1598 (curr+o.scroll > itemLength && o.btnNext)
1599 ||
1600 []
1601 ).addClass("disabled");
1602
Scott Main3b90aff2013-08-01 18:09:35 -07001603
Scott Mainf5089842012-08-14 16:31:07 -07001604 var nav_items = $('li', pagination);
1605 nav_items.removeClass('active');
1606 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001607
Scott Mainf5089842012-08-14 16:31:07 -07001608
1609 }
1610 if(o.auto) startRotateTimer();
1611 return false;
1612 };
1613 });
1614 };
1615
1616 function css(el, prop) {
1617 return parseInt($.css(el[0], prop)) || 0;
1618 };
1619 function width(el) {
1620 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1621 };
1622 function height(el) {
1623 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1624 };
1625
1626 })(jQuery);
1627
1628
Scott Main3b90aff2013-08-01 18:09:35 -07001629/*
Scott Mainf5089842012-08-14 16:31:07 -07001630 * dacSlideshow 1.0
1631 * Used on develop/index.html for side-sliding tabs
1632 *
1633 * Sample usage:
1634 * HTML -
1635 * <div class="slideshow-container">
1636 * <a href="" class="slideshow-prev">Prev</a>
1637 * <a href="" class="slideshow-next">Next</a>
1638 * <ul>
1639 * <li class="item"><img src="images/marquee1.jpg"></li>
1640 * <li class="item"><img src="images/marquee2.jpg"></li>
1641 * <li class="item"><img src="images/marquee3.jpg"></li>
1642 * <li class="item"><img src="images/marquee4.jpg"></li>
1643 * </ul>
1644 * </div>
1645 *
1646 * <script type="text/javascript">
1647 * $('.slideshow-container').dacSlideshow({
1648 * auto: true,
1649 * btnPrev: '.slideshow-prev',
1650 * btnNext: '.slideshow-next'
1651 * });
1652 * </script>
1653 *
1654 * Options:
1655 * btnPrev: optional identifier for previous button
1656 * btnNext: optional identifier for next button
1657 * auto: whether or not to auto-proceed
1658 * speed: animation speed
1659 * autoTime: time between auto-rotation
1660 * easing: easing function for transition
1661 * start: item to select by default
1662 * scroll: direction to scroll in
1663 * pagination: whether or not to include dotted pagination
1664 *
1665 */
1666 (function($) {
1667 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001668
Scott Mainf5089842012-08-14 16:31:07 -07001669 //Options - see above
1670 o = $.extend({
1671 speed : 250,
1672 easing: null,
1673 nav_id: null,
1674 frame_id: null
1675 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001676
1677 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001678 return this.each(function() {
1679
1680 var curr = 0;
1681 var running = false;
1682 var animCss = "margin-left";
1683 var sizeCss = "width";
1684 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001685
Scott Mainf5089842012-08-14 16:31:07 -07001686 var nav = $(o.nav_id, div);
1687 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001688 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001689 var frame = div.find(o.frame_id);
1690 var content_width = $(frame).find('ul').width();
1691 //Buttons
1692 $(nav_li).click(function(e) {
1693 go($(nav_li).index($(this)));
1694 })
Scott Main3b90aff2013-08-01 18:09:35 -07001695
Scott Mainf5089842012-08-14 16:31:07 -07001696 //Go to an item
1697 function go(to) {
1698 if(!running) {
1699 curr = to;
1700 running = true;
1701
1702 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1703 function() {
1704 running = false;
1705 }
1706 );
1707
Scott Main3b90aff2013-08-01 18:09:35 -07001708
Scott Mainf5089842012-08-14 16:31:07 -07001709 nav_li.removeClass('active');
1710 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001711
Scott Mainf5089842012-08-14 16:31:07 -07001712
1713 }
1714 return false;
1715 };
1716 });
1717 };
1718
1719 function css(el, prop) {
1720 return parseInt($.css(el[0], prop)) || 0;
1721 };
1722 function width(el) {
1723 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1724 };
1725 function height(el) {
1726 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1727 };
1728
1729 })(jQuery);
1730
1731
1732
1733
1734
1735/* ######################################################## */
1736/* ################ SEARCH SUGGESTIONS ################## */
1737/* ######################################################## */
1738
1739
Scott Main7e447ed2013-02-19 17:22:37 -08001740
Scott Main0e76e7e2013-03-12 10:24:07 -07001741var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1742var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1743
Scott Mainf5089842012-08-14 16:31:07 -07001744var gMatches = new Array();
1745var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001746var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001747var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1748var gListLength = 0;
1749
1750
1751var gGoogleMatches = new Array();
1752var ROW_COUNT_GOOGLE = 15; // max number of results in list
1753var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001754
Scott Main0e76e7e2013-03-12 10:24:07 -07001755var gDocsMatches = new Array();
1756var ROW_COUNT_DOCS = 100; // max number of results in list
1757var gDocsListLength = 0;
1758
Scott Mainde295272013-03-25 15:48:35 -07001759function onSuggestionClick(link) {
1760 // When user clicks a suggested document, track it
smain@google.comed677d72014-12-12 10:19:17 -08001761 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
1762 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07001763}
1764
Scott Mainf5089842012-08-14 16:31:07 -07001765function set_item_selected($li, selected)
1766{
1767 if (selected) {
1768 $li.attr('class','jd-autocomplete jd-selected');
1769 } else {
1770 $li.attr('class','jd-autocomplete');
1771 }
1772}
1773
1774function set_item_values(toroot, $li, match)
1775{
1776 var $link = $('a',$li);
1777 $link.html(match.__hilabel || match.label);
1778 $link.attr('href',toroot + match.link);
1779}
1780
Scott Main719acb42013-12-05 16:05:09 -08001781function set_item_values_jd(toroot, $li, match)
1782{
1783 var $link = $('a',$li);
1784 $link.html(match.title);
1785 $link.attr('href',toroot + match.url);
1786}
1787
Scott Main0e76e7e2013-03-12 10:24:07 -07001788function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001789 var $li = $("<li class='jd-autocomplete'></li>");
1790 $list.append($li);
1791
1792 $li.mousedown(function() {
1793 window.location = this.firstChild.getAttribute("href");
1794 });
1795 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001796 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001797 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001798 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1799 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001800 });
Scott Mainde295272013-03-25 15:48:35 -07001801 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001802 $li.attr('class','show-item');
1803 return $li;
1804}
1805
Scott Mainf5089842012-08-14 16:31:07 -07001806function sync_selection_table(toroot)
1807{
Scott Mainf5089842012-08-14 16:31:07 -07001808 var $li; //list item jquery object
1809 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001810
Scott Main0e76e7e2013-03-12 10:24:07 -07001811 // if there are NO results at all, hide all columns
1812 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1813 $('.suggest-card').hide(300);
1814 return;
1815 }
1816
1817 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001818 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001819 // reveal suggestion list
Scott Main0e76e7e2013-03-12 10:24:07 -07001820 $('.suggest-card.reference').show();
1821 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001822
Scott Main0e76e7e2013-03-12 10:24:07 -07001823 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001824 $(".suggest-card.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001825
Scott Main0e76e7e2013-03-12 10:24:07 -07001826 // ########### ANDROID RESULTS #############
1827 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001828
Scott Main0e76e7e2013-03-12 10:24:07 -07001829 // determine android results to show
1830 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1831 gMatches.length : ROW_COUNT_FRAMEWORK;
1832 for (i=0; i<gListLength; i++) {
1833 var $li = new_suggestion($(".suggest-card.reference ul"));
1834 set_item_values(toroot, $li, gMatches[i]);
1835 set_item_selected($li, i == gSelectedIndex);
1836 }
1837 }
Scott Main7e447ed2013-02-19 17:22:37 -08001838
Scott Main0e76e7e2013-03-12 10:24:07 -07001839 // ########### GOOGLE RESULTS #############
1840 if (gGoogleMatches.length > 0) {
1841 // show header for list
1842 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001843
Scott Main0e76e7e2013-03-12 10:24:07 -07001844 // determine google results to show
1845 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1846 for (i=0; i<gGoogleListLength; i++) {
1847 var $li = new_suggestion($(".suggest-card.reference ul"));
1848 set_item_values(toroot, $li, gGoogleMatches[i]);
1849 set_item_selected($li, i == gSelectedIndex);
1850 }
1851 }
Scott Mainf5089842012-08-14 16:31:07 -07001852 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001853 $('.suggest-card.reference').hide();
Scott Main0e76e7e2013-03-12 10:24:07 -07001854 }
1855
1856 // ########### JD DOC RESULTS #############
1857 if (gDocsMatches.length > 0) {
1858 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001859 $(".suggest-card:not(.reference) li").remove();
Scott Main0e76e7e2013-03-12 10:24:07 -07001860
1861 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001862 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1863 // The order must match the reverse order that each section appears as a card in
1864 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001865 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1866 for (i=0; i<gDocsListLength; i++) {
1867 var sugg = gDocsMatches[i];
1868 var $li;
1869 if (sugg.type == "design") {
1870 $li = new_suggestion($(".suggest-card.design ul"));
1871 } else
1872 if (sugg.type == "distribute") {
1873 $li = new_suggestion($(".suggest-card.distribute ul"));
1874 } else
Scott Main719acb42013-12-05 16:05:09 -08001875 if (sugg.type == "samples") {
1876 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1877 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001878 if (sugg.type == "training") {
1879 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1880 } else
Scott Main719acb42013-12-05 16:05:09 -08001881 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001882 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1883 } else {
1884 continue;
1885 }
1886
Scott Main719acb42013-12-05 16:05:09 -08001887 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001888 set_item_selected($li, i == gSelectedIndex);
1889 }
1890
1891 // add heading and show or hide card
1892 if ($(".suggest-card.design li").length > 0) {
1893 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1894 $(".suggest-card.design").show(300);
1895 } else {
1896 $('.suggest-card.design').hide(300);
1897 }
1898 if ($(".suggest-card.distribute li").length > 0) {
1899 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1900 $(".suggest-card.distribute").show(300);
1901 } else {
1902 $('.suggest-card.distribute').hide(300);
1903 }
1904 if ($(".child-card.guides li").length > 0) {
1905 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1906 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1907 }
1908 if ($(".child-card.training li").length > 0) {
1909 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1910 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1911 }
Scott Main719acb42013-12-05 16:05:09 -08001912 if ($(".child-card.samples li").length > 0) {
1913 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1914 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1915 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001916
1917 if ($(".suggest-card.develop li").length > 0) {
1918 $(".suggest-card.develop").show(300);
1919 } else {
1920 $('.suggest-card.develop').hide(300);
1921 }
1922
1923 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001924 $('.suggest-card:not(.reference)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001925 }
1926}
1927
Scott Main0e76e7e2013-03-12 10:24:07 -07001928/** Called by the search input's onkeydown and onkeyup events.
1929 * Handles navigation with keyboard arrows, Enter key to invoke search,
1930 * otherwise invokes search suggestions on key-up event.
1931 * @param e The JS event
1932 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001933 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001934 * @returns True if the event should bubble up
1935 */
Scott Mainf5089842012-08-14 16:31:07 -07001936function search_changed(e, kd, toroot)
1937{
Scott Main719acb42013-12-05 16:05:09 -08001938 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001939 var search = document.getElementById("search_autocomplete");
1940 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001941 // get the ul hosting the currently selected item
1942 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1943 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1944 var $selectedUl = $columns[gSelectedColumn];
1945
Scott Mainf5089842012-08-14 16:31:07 -07001946 // show/hide the close button
1947 if (text != '') {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001948 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07001949 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001950 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07001951 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001952 // 27 = esc
1953 if (e.keyCode == 27) {
1954 // close all search results
Dirk Dougherty29e93432015-05-05 18:17:13 -07001955 if (kd) $('#search-close').trigger('click');
Scott Main0e76e7e2013-03-12 10:24:07 -07001956 return true;
1957 }
Scott Mainf5089842012-08-14 16:31:07 -07001958 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001959 else if (e.keyCode == 13) {
1960 if (gSelectedIndex < 0) {
1961 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001962 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1963 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001964 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001965 return true;
1966 } else {
1967 // otherwise, results are already showing, so allow ajax to auto refresh the results
1968 // and ignore this Enter press to avoid the reload.
1969 return false;
1970 }
1971 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001972 // click the link corresponding to selected item
1973 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001974 return false;
1975 }
1976 }
Scott Mainb16376f2014-05-21 20:35:47 -07001977 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001978 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001979 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001980 if ((sticky ) && (search.value != "")) {
1981 $('body,html').animate({scrollTop:0}, '500', 'swing');
1982 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001983 return true;
1984 }
1985 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001986 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001987 // if the next item is a header, skip it
1988 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001989 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001990 }
1991 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001992 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001993 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001994 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1995 // If user reaches top, reset selected column
1996 if (gSelectedIndex < 0) {
1997 gSelectedColumn = -1;
1998 }
Scott Mainf5089842012-08-14 16:31:07 -07001999 }
2000 return false;
2001 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002002 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07002003 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002004 // if the next item is a header, skip it
2005 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07002006 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08002007 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002008 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
2009 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
2010 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08002011 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07002012 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07002013 }
2014 return false;
2015 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002016 // Consider left/right arrow navigation
2017 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
2018 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
2019 // 37 LEFT ARROW
2020 // go left only if current column is not left-most column (last column)
2021 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
2022 $('li', $selectedUl).removeClass('jd-selected');
2023 gSelectedColumn++;
2024 $selectedUl = $columns[gSelectedColumn];
2025 // keep or reset the selected item to last item as appropriate
2026 gSelectedIndex = gSelectedIndex >
2027 $("li", $selectedUl).length-1 ?
2028 $("li", $selectedUl).length-1 : gSelectedIndex;
2029 // if the corresponding item is a header, move down
2030 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2031 gSelectedIndex++;
2032 }
Scott Main3b90aff2013-08-01 18:09:35 -07002033 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002034 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2035 return false;
2036 }
2037 // 39 RIGHT ARROW
2038 // go right only if current column is not the right-most column (first column)
2039 else if (e.keyCode == 39 && gSelectedColumn > 0) {
2040 $('li', $selectedUl).removeClass('jd-selected');
2041 gSelectedColumn--;
2042 $selectedUl = $columns[gSelectedColumn];
2043 // keep or reset the selected item to last item as appropriate
2044 gSelectedIndex = gSelectedIndex >
2045 $("li", $selectedUl).length-1 ?
2046 $("li", $selectedUl).length-1 : gSelectedIndex;
2047 // if the corresponding item is a header, move down
2048 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2049 gSelectedIndex++;
2050 }
Scott Main3b90aff2013-08-01 18:09:35 -07002051 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002052 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2053 return false;
2054 }
2055 }
2056
Scott Main719acb42013-12-05 16:05:09 -08002057 // if key-up event and not arrow down/up/left/right,
2058 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07002059 else if (!kd && (e.keyCode != 40)
2060 && (e.keyCode != 38)
2061 && (e.keyCode != 37)
2062 && (e.keyCode != 39)) {
2063 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07002064 gMatches = new Array();
2065 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002066 gGoogleMatches = new Array();
2067 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07002068 gDocsMatches = new Array();
2069 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002070
2071 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07002072 for (var i=0; i<DATA.length; i++) {
2073 var s = DATA[i];
2074 if (text.length != 0 &&
2075 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2076 gMatches[matchedCount] = s;
2077 matchedCount++;
2078 }
2079 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002080 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07002081 for (var i=0; i<gMatches.length; i++) {
2082 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08002083 }
2084
2085
2086 // Search for Google matches
2087 for (var i=0; i<GOOGLE_DATA.length; i++) {
2088 var s = GOOGLE_DATA[i];
2089 if (text.length != 0 &&
2090 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2091 gGoogleMatches[matchedCountGoogle] = s;
2092 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07002093 }
2094 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002095 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08002096 for (var i=0; i<gGoogleMatches.length; i++) {
2097 var s = gGoogleMatches[i];
2098 }
2099
Scott Mainf5089842012-08-14 16:31:07 -07002100 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07002101
2102
2103
Scott Main719acb42013-12-05 16:05:09 -08002104 // Search for matching JD docs
Dirk Dougherty9b7f8f22014-11-01 17:08:56 -07002105 if (text.length >= 2) {
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002106 // match only the beginning of a word
2107 var queryStr = text.toLowerCase();
Scott Main719acb42013-12-05 16:05:09 -08002108
2109 // Search for Training classes
2110 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002111 // current search comparison, with counters for tag and title,
2112 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08002113 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07002114 s.matched_tag = 0;
2115 s.matched_title = 0;
2116 var matched = false;
2117
2118 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08002119 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002120 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002121 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002122 matched = true;
2123 s.matched_tag = j + 1; // add 1 to index position
2124 }
2125 }
Scott Main719acb42013-12-05 16:05:09 -08002126 // Don't consider doc title for lessons (only for class landing pages),
2127 // unless the lesson has a tag that already matches
2128 if ((s.lang == currentLang) &&
2129 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002130 // it matches the doc title
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002131 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002132 matched = true;
2133 s.matched_title = 1;
2134 }
2135 }
2136 if (matched) {
2137 gDocsMatches[matchedCountDocs] = s;
2138 matchedCountDocs++;
2139 }
2140 }
Scott Main719acb42013-12-05 16:05:09 -08002141
2142
2143 // Search for API Guides
2144 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2145 // current search comparison, with counters for tag and title,
2146 // used later to improve ranking
2147 var s = GUIDE_RESOURCES[i];
2148 s.matched_tag = 0;
2149 s.matched_title = 0;
2150 var matched = false;
2151
2152 // Check if query matches any tags; work backwards toward 1 to assist ranking
2153 for (var j = s.keywords.length - 1; j >= 0; j--) {
2154 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002155 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
2156
Scott Main719acb42013-12-05 16:05:09 -08002157 matched = true;
2158 s.matched_tag = j + 1; // add 1 to index position
2159 }
2160 }
2161 // Check if query matches the doc title, but only for current language
2162 if (s.lang == currentLang) {
2163 // if query matches the doc title
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002164 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002165 matched = true;
2166 s.matched_title = 1;
2167 }
2168 }
2169 if (matched) {
2170 gDocsMatches[matchedCountDocs] = s;
2171 matchedCountDocs++;
2172 }
2173 }
2174
2175
2176 // Search for Tools Guides
2177 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2178 // current search comparison, with counters for tag and title,
2179 // used later to improve ranking
2180 var s = TOOLS_RESOURCES[i];
2181 s.matched_tag = 0;
2182 s.matched_title = 0;
2183 var matched = false;
2184
2185 // Check if query matches any tags; work backwards toward 1 to assist ranking
2186 for (var j = s.keywords.length - 1; j >= 0; j--) {
2187 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002188 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002189 matched = true;
2190 s.matched_tag = j + 1; // add 1 to index position
2191 }
2192 }
2193 // Check if query matches the doc title, but only for current language
2194 if (s.lang == currentLang) {
2195 // if query matches the doc title
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002196 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002197 matched = true;
2198 s.matched_title = 1;
2199 }
2200 }
2201 if (matched) {
2202 gDocsMatches[matchedCountDocs] = s;
2203 matchedCountDocs++;
2204 }
2205 }
2206
2207
2208 // Search for About docs
2209 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2210 // current search comparison, with counters for tag and title,
2211 // used later to improve ranking
2212 var s = ABOUT_RESOURCES[i];
2213 s.matched_tag = 0;
2214 s.matched_title = 0;
2215 var matched = false;
2216
2217 // Check if query matches any tags; work backwards toward 1 to assist ranking
2218 for (var j = s.keywords.length - 1; j >= 0; j--) {
2219 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002220 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002221 matched = true;
2222 s.matched_tag = j + 1; // add 1 to index position
2223 }
2224 }
2225 // Check if query matches the doc title, but only for current language
2226 if (s.lang == currentLang) {
2227 // if query matches the doc title
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002228 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002229 matched = true;
2230 s.matched_title = 1;
2231 }
2232 }
2233 if (matched) {
2234 gDocsMatches[matchedCountDocs] = s;
2235 matchedCountDocs++;
2236 }
2237 }
2238
2239
2240 // Search for Design guides
2241 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2242 // current search comparison, with counters for tag and title,
2243 // used later to improve ranking
2244 var s = DESIGN_RESOURCES[i];
2245 s.matched_tag = 0;
2246 s.matched_title = 0;
2247 var matched = false;
2248
2249 // Check if query matches any tags; work backwards toward 1 to assist ranking
2250 for (var j = s.keywords.length - 1; j >= 0; j--) {
2251 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002252 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002253 matched = true;
2254 s.matched_tag = j + 1; // add 1 to index position
2255 }
2256 }
2257 // Check if query matches the doc title, but only for current language
2258 if (s.lang == currentLang) {
2259 // if query matches the doc title
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002260 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002261 matched = true;
2262 s.matched_title = 1;
2263 }
2264 }
2265 if (matched) {
2266 gDocsMatches[matchedCountDocs] = s;
2267 matchedCountDocs++;
2268 }
2269 }
2270
2271
2272 // Search for Distribute guides
2273 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2274 // current search comparison, with counters for tag and title,
2275 // used later to improve ranking
2276 var s = DISTRIBUTE_RESOURCES[i];
2277 s.matched_tag = 0;
2278 s.matched_title = 0;
2279 var matched = false;
2280
2281 // Check if query matches any tags; work backwards toward 1 to assist ranking
2282 for (var j = s.keywords.length - 1; j >= 0; j--) {
2283 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002284 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002285 matched = true;
2286 s.matched_tag = j + 1; // add 1 to index position
2287 }
2288 }
2289 // Check if query matches the doc title, but only for current language
2290 if (s.lang == currentLang) {
2291 // if query matches the doc title
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002292 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002293 matched = true;
2294 s.matched_title = 1;
2295 }
2296 }
2297 if (matched) {
2298 gDocsMatches[matchedCountDocs] = s;
2299 matchedCountDocs++;
2300 }
2301 }
2302
2303
2304 // Search for Google guides
2305 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2306 // current search comparison, with counters for tag and title,
2307 // used later to improve ranking
2308 var s = GOOGLE_RESOURCES[i];
2309 s.matched_tag = 0;
2310 s.matched_title = 0;
2311 var matched = false;
2312
2313 // Check if query matches any tags; work backwards toward 1 to assist ranking
2314 for (var j = s.keywords.length - 1; j >= 0; j--) {
2315 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002316 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002317 matched = true;
2318 s.matched_tag = j + 1; // add 1 to index position
2319 }
2320 }
2321 // Check if query matches the doc title, but only for current language
2322 if (s.lang == currentLang) {
2323 // if query matches the doc title
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002324 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002325 matched = true;
2326 s.matched_title = 1;
2327 }
2328 }
2329 if (matched) {
2330 gDocsMatches[matchedCountDocs] = s;
2331 matchedCountDocs++;
2332 }
2333 }
2334
2335
2336 // Search for Samples
2337 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2338 // current search comparison, with counters for tag and title,
2339 // used later to improve ranking
2340 var s = SAMPLES_RESOURCES[i];
2341 s.matched_tag = 0;
2342 s.matched_title = 0;
2343 var matched = false;
2344 // Check if query matches any tags; work backwards toward 1 to assist ranking
2345 for (var j = s.keywords.length - 1; j >= 0; j--) {
2346 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002347 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002348 matched = true;
2349 s.matched_tag = j + 1; // add 1 to index position
2350 }
2351 }
2352 // Check if query matches the doc title, but only for current language
2353 if (s.lang == currentLang) {
2354 // if query matches the doc title.t
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002355 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002356 matched = true;
2357 s.matched_title = 1;
2358 }
2359 }
2360 if (matched) {
2361 gDocsMatches[matchedCountDocs] = s;
2362 matchedCountDocs++;
2363 }
2364 }
2365
Dirk Dougherty86120022016-02-09 18:00:05 -08002366 // Search for Preview Guides
Dirk Dougherty49a56682016-03-09 14:07:31 -08002367 for (var i=0; i<PREVIEW_RESOURCES.length; i++) {
Dirk Dougherty86120022016-02-09 18:00:05 -08002368 // current search comparison, with counters for tag and title,
2369 // used later to improve ranking
Dirk Dougherty49a56682016-03-09 14:07:31 -08002370 var s = PREVIEW_RESOURCES[i];
Dirk Dougherty86120022016-02-09 18:00:05 -08002371 s.matched_tag = 0;
2372 s.matched_title = 0;
2373 var matched = false;
2374
2375 // Check if query matches any tags; work backwards toward 1 to assist ranking
2376 for (var j = s.keywords.length - 1; j >= 0; j--) {
2377 // it matches a tag
2378 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
2379 matched = true;
2380 s.matched_tag = j + 1; // add 1 to index position
2381 }
2382 }
2383 // Check if query matches the doc title, but only for current language
2384 if (s.lang == currentLang) {
2385 // if query matches the doc title
2386 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
2387 matched = true;
2388 s.matched_title = 1;
2389 }
2390 }
2391 if (matched) {
2392 gDocsMatches[matchedCountDocs] = s;
2393 matchedCountDocs++;
2394 }
2395 }
2396
Scott Main719acb42013-12-05 16:05:09 -08002397 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002398 rank_autocomplete_doc_results(text, gDocsMatches);
2399 }
2400
2401 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002402 sync_selection_table(toroot);
2403 return true; // allow the event to bubble up to the search api
2404 }
2405}
2406
Scott Main0e76e7e2013-03-12 10:24:07 -07002407/* Order the jd doc result list based on match quality */
2408function rank_autocomplete_doc_results(query, matches) {
2409 query = query || '';
2410 if (!matches || !matches.length)
2411 return;
2412
2413 var _resultScoreFn = function(match) {
2414 var score = 1.0;
2415
2416 // if the query matched a tag
2417 if (match.matched_tag > 0) {
2418 // multiply score by factor relative to position in tags list (max of 3)
2419 score *= 3 / match.matched_tag;
2420
2421 // if it also matched the title
2422 if (match.matched_title > 0) {
2423 score *= 2;
2424 }
2425 } else if (match.matched_title > 0) {
2426 score *= 3;
2427 }
2428
2429 return score;
2430 };
2431
2432 for (var i=0; i<matches.length; i++) {
2433 matches[i].__resultScore = _resultScoreFn(matches[i]);
2434 }
2435
2436 matches.sort(function(a,b){
2437 var n = b.__resultScore - a.__resultScore;
2438 if (n == 0) // lexicographical sort if scores are the same
2439 n = (a.label < b.label) ? -1 : 1;
2440 return n;
2441 });
2442}
2443
Scott Main7e447ed2013-02-19 17:22:37 -08002444/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002445function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002446 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002447 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002448 return;
2449
2450 // helper function that gets the last occurence index of the given regex
2451 // in the given string, or -1 if not found
2452 var _lastSearch = function(s, re) {
2453 if (s == '')
2454 return -1;
2455 var l = -1;
2456 var tmp;
2457 while ((tmp = s.search(re)) >= 0) {
2458 if (l < 0) l = 0;
2459 l += tmp;
2460 s = s.substr(tmp + 1);
2461 }
2462 return l;
2463 };
2464
2465 // helper function that counts the occurrences of a given character in
2466 // a given string
2467 var _countChar = function(s, c) {
2468 var n = 0;
2469 for (var i=0; i<s.length; i++)
2470 if (s.charAt(i) == c) ++n;
2471 return n;
2472 };
2473
2474 var queryLower = query.toLowerCase();
2475 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2476 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2477 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2478
2479 var _resultScoreFn = function(result) {
2480 // scores are calculated based on exact and prefix matches,
2481 // and then number of path separators (dots) from the last
2482 // match (i.e. favoring classes and deep package names)
2483 var score = 1.0;
2484 var labelLower = result.label.toLowerCase();
2485 var t;
2486 t = _lastSearch(labelLower, partExactAlnumRE);
2487 if (t >= 0) {
2488 // exact part match
2489 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2490 score *= 200 / (partsAfter + 1);
2491 } else {
2492 t = _lastSearch(labelLower, partPrefixAlnumRE);
2493 if (t >= 0) {
2494 // part prefix match
2495 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2496 score *= 20 / (partsAfter + 1);
2497 }
2498 }
2499
2500 return score;
2501 };
2502
Scott Main7e447ed2013-02-19 17:22:37 -08002503 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002504 // if the API is deprecated, default score is 0; otherwise, perform scoring
2505 if (matches[i].deprecated == "true") {
2506 matches[i].__resultScore = 0;
2507 } else {
2508 matches[i].__resultScore = _resultScoreFn(matches[i]);
2509 }
Scott Mainf5089842012-08-14 16:31:07 -07002510 }
2511
Scott Main7e447ed2013-02-19 17:22:37 -08002512 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002513 var n = b.__resultScore - a.__resultScore;
2514 if (n == 0) // lexicographical sort if scores are the same
2515 n = (a.label < b.label) ? -1 : 1;
2516 return n;
2517 });
2518}
2519
Scott Main7e447ed2013-02-19 17:22:37 -08002520/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002521function highlight_autocomplete_result_labels(query) {
2522 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002523 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002524 return;
2525
2526 var queryLower = query.toLowerCase();
2527 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2528 var queryRE = new RegExp(
2529 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2530 for (var i=0; i<gMatches.length; i++) {
2531 gMatches[i].__hilabel = gMatches[i].label.replace(
2532 queryRE, '<b>$1</b>');
2533 }
Scott Main7e447ed2013-02-19 17:22:37 -08002534 for (var i=0; i<gGoogleMatches.length; i++) {
2535 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2536 queryRE, '<b>$1</b>');
2537 }
Scott Mainf5089842012-08-14 16:31:07 -07002538}
2539
2540function search_focus_changed(obj, focused)
2541{
Scott Main3b90aff2013-08-01 18:09:35 -07002542 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002543 if(obj.value == ""){
Dirk Dougherty29e93432015-05-05 18:17:13 -07002544 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002545 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002546 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002547 }
2548}
2549
2550function submit_search() {
Amanda Kassay2bb33282016-04-14 11:29:32 -04002551 var query = escapeHTML(document.getElementById('search_autocomplete').value);
Scott Mainf5089842012-08-14 16:31:07 -07002552 location.hash = 'q=' + query;
Amanda Kassay843649b2016-03-17 14:11:13 -04002553 searchControl.query = query;
2554 searchControl.init();
2555 searchControl.trackSearchRequest(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002556 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002557 return false;
2558}
2559
Scott Mainf5089842012-08-14 16:31:07 -07002560function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002561 $("#searchResults").slideUp('fast', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002562 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002563 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002564
Scott Mainf5089842012-08-14 16:31:07 -07002565 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002566
Scott Mainf5089842012-08-14 16:31:07 -07002567 // reset the ajax search callback to nothing, so results don't appear unless ENTER
Amanda Kassay843649b2016-03-17 14:11:13 -04002568 searchControl.reset();
Scott Main0e76e7e2013-03-12 10:24:07 -07002569
Scott Mainf5089842012-08-14 16:31:07 -07002570 return false;
2571}
2572
Scott Mainf5089842012-08-14 16:31:07 -07002573/* ########################################################## */
2574/* ################ CUSTOM SEARCH ENGINE ################## */
2575/* ########################################################## */
Amanda Kassay843649b2016-03-17 14:11:13 -04002576var searchControl = null;
2577var dacsearch = dacsearch || {};
Scott Mainf5089842012-08-14 16:31:07 -07002578
Amanda Kassay843649b2016-03-17 14:11:13 -04002579/**
2580 * The custom search engine API.
2581 * @constructor
2582 */
2583dacsearch.CustomSearchEngine = function() {
2584 /**
2585 * The last response from Google CSE.
2586 * @private {Object}
2587 */
2588 this.resultQuery_ = {};
Scott Mainf5089842012-08-14 16:31:07 -07002589
Amanda Kassay843649b2016-03-17 14:11:13 -04002590 /** @private {?Element} */
2591 this.searchResultEl_ = null;
Scott Mainf5089842012-08-14 16:31:07 -07002592
Amanda Kassay843649b2016-03-17 14:11:13 -04002593 /** @private {?Element} */
2594 this.searchInputEl_ = null;
Scott Mainf5089842012-08-14 16:31:07 -07002595
Amanda Kassay843649b2016-03-17 14:11:13 -04002596 /** @private {string} */
2597 this.query = '';
2598};
Scott Mainf5089842012-08-14 16:31:07 -07002599
Amanda Kassay843649b2016-03-17 14:11:13 -04002600/**
2601 * Initializes DAC's Google custom search engine.
2602 * @export
2603 */
2604dacsearch.CustomSearchEngine.prototype.init = function() {
2605 this.searchResultEl_ = $('#leftSearchControl');
2606 this.searchResultEl_.empty();
2607 this.searchInputEl_ = $('#search_autocomplete');
2608 this.searchInputEl_.focus().val(this.query);
2609 this.getResults_();
2610 this.bindEvents_();
2611};
Scott Mainf5089842012-08-14 16:31:07 -07002612
Scott Mainf5089842012-08-14 16:31:07 -07002613
Amanda Kassay843649b2016-03-17 14:11:13 -04002614/**
2615 * Binds the keyup event to the search input.
2616 * @private
2617 */
2618dacsearch.CustomSearchEngine.prototype.bindEvents_ = function() {
2619 this.searchInputEl_.keyup(this.debounce_(function(e) {
2620 var code = e.which;
2621 if (code != 13) {
Amanda Kassay2bb33282016-04-14 11:29:32 -04002622 this.query = escapeHTML(this.searchInputEl_.val());
Amanda Kassay843649b2016-03-17 14:11:13 -04002623 location.hash = 'q=' + encodeURI(this.query);
2624 this.searchResultEl_.empty();
2625 this.getResults_();
2626 }
2627 }.bind(this), 250));
2628};
Scott Mainf5089842012-08-14 16:31:07 -07002629
Scott Mainf5089842012-08-14 16:31:07 -07002630
Amanda Kassay843649b2016-03-17 14:11:13 -04002631/**
2632 * Resets the search control.
2633 */
2634dacsearch.CustomSearchEngine.prototype.reset = function() {
2635 this.query = '';
2636 this.searchInputEl_.off('keyup');
2637 this.searchResultEl_.empty();
2638 this.updateResultTitle_();
2639};
Scott Mainf5089842012-08-14 16:31:07 -07002640
Scott Mainf5089842012-08-14 16:31:07 -07002641
Amanda Kassay843649b2016-03-17 14:11:13 -04002642/**
2643 * Updates the search query text at the top of the results.
2644 * @private
2645 */
2646dacsearch.CustomSearchEngine.prototype.updateResultTitle_ = function() {
2647 $("#searchTitle").html("Results for <em>" + this.query + "</em>");
2648};
Scott Maindf08ada2012-12-03 08:54:37 -08002649
Scott Mainf5089842012-08-14 16:31:07 -07002650
Amanda Kassay843649b2016-03-17 14:11:13 -04002651/**
2652 * Makes the CSE api call and gets the results.
2653 * @param {number=} opt_start The optional start index.
2654 * @private
2655 */
2656dacsearch.CustomSearchEngine.prototype.getResults_ = function(opt_start) {
2657 var lang = getLangPref();
2658 // Fix zh-cn to be zh-CN.
2659 lang = lang.replace(/-\w+/, function(m) { return m.toUpperCase(); });
2660 var cseUrl = 'https://content.googleapis.com/customsearch/v1?';
2661 var searchParams = {
2662 cx: '000521750095050289010:zpcpi1ea4s8',
2663 key: 'AIzaSyCFhbGnjW06dYwvRCU8h_zjdpS4PYYbEe8',
2664 q: this.query,
2665 start: opt_start || 1,
2666 num: 6,
2667 hl: lang,
2668 fields: 'queries,items(pagemap,link,title,htmlSnippet,formattedUrl)'
2669 };
Scott Mainf5089842012-08-14 16:31:07 -07002670
Amanda Kassay843649b2016-03-17 14:11:13 -04002671 $.get(cseUrl + $.param(searchParams), function(data) {
2672 this.resultQuery_ = data;
2673 this.renderResults_(data);
2674 this.updateResultTitle_(this.query);
2675 }.bind(this));
2676};
Scott Mainf5089842012-08-14 16:31:07 -07002677
Scott Mainf5089842012-08-14 16:31:07 -07002678
Amanda Kassay843649b2016-03-17 14:11:13 -04002679/**
2680 * Renders the results.
2681 * @private
2682 */
2683dacsearch.CustomSearchEngine.prototype.renderResults_ = function(results) {
2684 var el = this.searchResultEl_;
Scott Mainde295272013-03-25 15:48:35 -07002685
Amanda Kassay843649b2016-03-17 14:11:13 -04002686 if (!results.items) {
2687 el.append($('<div>').text('No results'));
2688 return;
2689 }
Scott Mainf5089842012-08-14 16:31:07 -07002690
Amanda Kassay843649b2016-03-17 14:11:13 -04002691 for (var i = 0; i < results.items.length; i++) {
2692 var item = results.items[i];
2693 var hasImage = item.pagemap && item.pagemap.cse_thumbnail;
2694 var sectionMatch = item.link.match(/developer\.android\.com\/(\w*)/);
2695 var section = (sectionMatch && sectionMatch[1]) || 'blog';
Scott Mainf5089842012-08-14 16:31:07 -07002696
Amanda Kassay843649b2016-03-17 14:11:13 -04002697 var entry = $('<div>').addClass('dac-custom-search-entry cols');
2698
2699 if (hasImage) {
2700 var image = item.pagemap.cse_thumbnail[0];
2701 entry.append($('<div>').addClass('col-1of6')
2702 .append($('<div>').addClass('dac-custom-search-image').css(
2703 'background-image', 'url(' + image.src + ')')));
2704 }
2705
2706 var linkTitleEl = $('<a>').text(item.title).attr('href', item.link);
2707 linkTitleEl.click(function(e) {
2708 ga('send', 'event', 'Google Custom Search',
2709 'clicked: ' + linkTitleEl.attr('href'),
2710 'query: ' + $("#search_autocomplete").val().toLowerCase());
2711 });
2712
2713 var linkUrlEl = $('<a>').addClass('dac-custom-search-link').text(
2714 item.formattedUrl).attr('href', item.link);
2715 linkUrlEl.click(function(e) {
2716 ga('send', 'event', 'Google Custom Search',
2717 'clicked: ' + linkUrlEl.attr('href'),
2718 'query: ' + $("#search_autocomplete").val().toLowerCase());
2719 });
2720
2721
2722 entry.append($('<div>').addClass(hasImage ? 'col-5of6' : 'col-6of6')
2723 .append($('<p>').addClass('dac-custom-search-section').text(section))
2724 .append(
2725 linkTitleEl.wrap('<h2>').parent().addClass('dac-custom-search-title'))
2726 .append($('<p>').addClass('dac-custom-search-snippet')
2727 .html(item.htmlSnippet.replace(/<br>/g, ''))).append(linkUrlEl));
2728
2729 el.append(entry);
2730 }
2731
2732 if ($('#dac-custom-search-load-more')) {
2733 $('#dac-custom-search-load-more').remove();
2734 }
2735
2736 if (results.queries.nextPage) {
2737 var loadMoreButton = $('<button id="dac-custom-search-load-more">')
2738 .addClass('dac-custom-search-load-more')
2739 .text('Load more')
2740 .click(function() {
2741 this.loadMoreResults_();
2742 }.bind(this));
2743
2744 el.append(loadMoreButton);
2745 }
2746};
2747
2748
2749/**
2750 * Loads more results.
2751 * @private
2752 */
2753dacsearch.CustomSearchEngine.prototype.loadMoreResults_ = function() {
2754 this.query = this.resultQuery_.queries.request[0].searchTerms;
2755 var start = this.resultQuery_.queries.nextPage[0].startIndex;
2756 var loadMoreButton = this.searchResultEl_.find(
2757 '#dac-custom-search-load-more');
2758 loadMoreButton.text('Loading more...');
2759 this.getResults_(start);
2760 this.trackSearchRequest(this.query + ' startIndex = ' + start);
2761};
2762
2763
2764/**
2765 * Tracks a search request.
2766 * @param {string} query The query for the request,
2767 * includes start index if loading more results.
2768 */
2769dacsearch.CustomSearchEngine.prototype.trackSearchRequest = function(query) {
2770 ga('send', 'event', 'Google Custom Search Submit', 'submit search query',
2771 'query: ' + query);
2772};
2773
2774
2775/**
2776 * Returns a function, that, as long as it continues to be invoked, will not
2777 * be triggered. The function will be called after it stops being called for
2778 * N milliseconds.
2779 * @param {Function} func The function to debounce.
2780 * @param {number} wait The number of milliseconds to wait before calling the function.
2781 * @private
2782 */
2783dacsearch.CustomSearchEngine.prototype.debounce_ = function(func, wait) {
2784 var timeout;
2785 return function() {
2786 var context = this, args = arguments;
2787 var later = function() {
2788 timeout = null;
2789 func.apply(context, args);
2790 };
2791 clearTimeout(timeout);
2792 timeout = setTimeout(later, wait);
2793 };
2794};
Scott Mainf5089842012-08-14 16:31:07 -07002795
2796
2797google.setOnLoadCallback(function(){
Amanda Kassay843649b2016-03-17 14:11:13 -04002798 searchControl = new dacsearch.CustomSearchEngine();
Scott Mainf5089842012-08-14 16:31:07 -07002799 if (location.hash.indexOf("q=") == -1) {
2800 // if there's no query in the url, don't search and make sure results are hidden
2801 $('#searchResults').hide();
2802 return;
2803 } else {
2804 // first time loading search results for this page
Amanda Kassay2bb33282016-04-14 11:29:32 -04002805 searchControl.query = escapeHTML(decodeURI(location.hash.split('q=')[1]));
Amanda Kassay843649b2016-03-17 14:11:13 -04002806 searchControl.init();
2807 searchControl.trackSearchRequest(searchControl.query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002808 $('#searchResults').slideDown('slow', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002809 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002810 }
2811}, true);
2812
smain@google.com9a818f52014-10-03 09:25:59 -07002813/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2814 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002815function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002816 // Ignore if there's no search bar (some special pages have no header)
2817 if ($("#search-container").length < 1) return;
2818
smain@google.com3b77ab52014-06-17 11:57:27 -07002819 var hash = escape(location.hash.substr(1));
2820 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002821 // Sanity check that there's an element with that ID on the page
2822 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002823 // If the position of the target element is near the top of the page (<20px, where we expect it
2824 // to be because we need to move it down 60px to become in view), then move it down 60px
2825 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2826 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002827 }
2828 }
2829}
2830
Scott Mainf5089842012-08-14 16:31:07 -07002831// when an event on the browser history occurs (back, forward, load) requery hash and do search
2832$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002833 // Ignore if there's no search bar (some special pages have no header)
2834 if ($("#search-container").length < 1) return;
2835
Dirk Doughertyc3921652014-05-13 16:55:26 -07002836 // If the hash isn't a search query or there's an error in the query,
2837 // then adjust the scroll position to account for sticky header, then exit.
Amanda Kassay843649b2016-03-17 14:11:13 -04002838 if ((location.hash.indexOf("q=") == -1) || (searchControl.query == "undefined")) {
Scott Mainf5089842012-08-14 16:31:07 -07002839 // If the results pane is open, close it.
2840 if (!$("#searchResults").is(":hidden")) {
2841 hideResults();
2842 }
Scott Mainb16376f2014-05-21 20:35:47 -07002843 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002844 return;
2845 }
2846
Dirk Doughertyc3921652014-05-13 16:55:26 -07002847 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002848 $("#search_autocomplete").focus();
Dirk Dougherty29e93432015-05-05 18:17:13 -07002849 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002850});
2851
Scott Mainf5089842012-08-14 16:31:07 -07002852/* returns the given string with all HTML brackets converted to entities
2853 TODO: move this to the site's JS library */
2854function escapeHTML(string) {
2855 return string.replace(/</g,"&lt;")
2856 .replace(/>/g,"&gt;");
2857}
2858
2859
2860
2861
2862
2863
2864
2865/* ######################################################## */
2866/* ################# JAVADOC REFERENCE ################### */
2867/* ######################################################## */
2868
Scott Main65511c02012-09-07 15:51:32 -07002869/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002870if (location.pathname.indexOf("/reference") == 0) {
2871 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2872 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2873 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002874 $(document).ready(function() {
2875 // init available apis based on user pref
2876 changeApiLevel();
2877 initSidenavHeightResize()
2878 });
2879 }
Scott Main65511c02012-09-07 15:51:32 -07002880}
Scott Mainf5089842012-08-14 16:31:07 -07002881
2882var API_LEVEL_COOKIE = "api_level";
2883var minLevel = 1;
2884var maxLevel = 1;
2885
2886/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002887
Scott Mainf5089842012-08-14 16:31:07 -07002888 function initSidenavHeightResize() {
2889 // Change the drag bar size to nicely fit the scrollbar positions
2890 var $dragBar = $(".ui-resizable-s");
2891 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002892
2893 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002894 containment: "#nav-panels",
2895 handles: "s",
2896 alsoResize: "#packages-nav",
2897 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2898 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2899 });
Scott Main3b90aff2013-08-01 18:09:35 -07002900
Scott Mainf5089842012-08-14 16:31:07 -07002901 }
Scott Main3b90aff2013-08-01 18:09:35 -07002902
Scott Mainf5089842012-08-14 16:31:07 -07002903function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002904 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002905 $('#devdoc-nav').css({
2906 'width' : $('#side-nav').css('width'),
2907 'margin' : $('#side-nav').css('margin')
2908 });
2909 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002910
Scott Mainf5089842012-08-14 16:31:07 -07002911 initSidenavHeightResize();
2912}
2913
2914function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002915 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002916 $('#devdoc-nav').css({
2917 'width' : $('#side-nav').css('width'),
2918 'margin' : $('#side-nav').css('margin')
2919 });
2920 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002921
Scott Mainf5089842012-08-14 16:31:07 -07002922 initSidenavHeightResize();
2923}
2924
2925function buildApiLevelSelector() {
2926 maxLevel = SINCE_DATA.length;
2927 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2928 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2929
2930 minLevel = parseInt($("#doc-api-level").attr("class"));
2931 // Handle provisional api levels; the provisional level will always be the highest possible level
2932 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2933 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2934 if (isNaN(minLevel) && minLevel.length) {
2935 minLevel = maxLevel;
2936 }
2937 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2938 for (var i = maxLevel-1; i >= 0; i--) {
2939 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2940 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2941 select.append(option);
2942 }
2943
2944 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2945 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2946 selectedLevelItem.setAttribute('selected',true);
2947}
2948
2949function changeApiLevel() {
2950 maxLevel = SINCE_DATA.length;
2951 var selectedLevel = maxLevel;
2952
2953 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2954 toggleVisisbleApis(selectedLevel, "body");
2955
smain@google.com6bdcb982014-11-14 11:53:07 -08002956 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002957
2958 if (selectedLevel < minLevel) {
2959 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002960 $("#naMessage").show().html("<div><p><strong>This " + thing
2961 + " requires API level " + minLevel + " or higher.</strong></p>"
2962 + "<p>This document is hidden because your selected API level for the documentation is "
2963 + selectedLevel + ". You can change the documentation API level with the selector "
2964 + "above the left navigation.</p>"
2965 + "<p>For more information about specifying the API level your app requires, "
2966 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2967 + ">Supporting Different Platform Versions</a>.</p>"
2968 + "<input type='button' value='OK, make this page visible' "
2969 + "title='Change the API level to " + minLevel + "' "
2970 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2971 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002972 } else {
2973 $("#naMessage").hide();
2974 }
2975}
2976
2977function toggleVisisbleApis(selectedLevel, context) {
2978 var apis = $(".api",context);
2979 apis.each(function(i) {
2980 var obj = $(this);
2981 var className = obj.attr("class");
2982 var apiLevelIndex = className.lastIndexOf("-")+1;
2983 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2984 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2985 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2986 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2987 return;
2988 }
2989 apiLevel = parseInt(apiLevel);
2990
2991 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2992 var selectedLevelNum = parseInt(selectedLevel)
2993 var apiLevelNum = parseInt(apiLevel);
2994 if (isNaN(apiLevelNum)) {
2995 apiLevelNum = maxLevel;
2996 }
2997
2998 // Grey things out that aren't available and give a tooltip title
2999 if (apiLevelNum > selectedLevelNum) {
3000 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07003001 + apiLevel + "\" or higher. To reveal, change the target API level "
3002 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07003003 }
Scott Mainf5089842012-08-14 16:31:07 -07003004 else obj.removeClass("absent").removeAttr("title");
3005 });
3006}
3007
3008
3009
3010
3011/* ################# SIDENAV TREE VIEW ################### */
3012
3013function new_node(me, mom, text, link, children_data, api_level)
3014{
3015 var node = new Object();
3016 node.children = Array();
3017 node.children_data = children_data;
3018 node.depth = mom.depth + 1;
3019
3020 node.li = document.createElement("li");
3021 mom.get_children_ul().appendChild(node.li);
3022
3023 node.label_div = document.createElement("div");
3024 node.label_div.className = "label";
3025 if (api_level != null) {
3026 $(node.label_div).addClass("api");
3027 $(node.label_div).addClass("api-level-"+api_level);
3028 }
3029 node.li.appendChild(node.label_div);
3030
3031 if (children_data != null) {
3032 node.expand_toggle = document.createElement("a");
3033 node.expand_toggle.href = "javascript:void(0)";
3034 node.expand_toggle.onclick = function() {
3035 if (node.expanded) {
3036 $(node.get_children_ul()).slideUp("fast");
3037 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
3038 node.expanded = false;
3039 } else {
3040 expand_node(me, node);
3041 }
3042 };
3043 node.label_div.appendChild(node.expand_toggle);
3044
3045 node.plus_img = document.createElement("img");
3046 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
3047 node.plus_img.className = "plus";
3048 node.plus_img.width = "8";
3049 node.plus_img.border = "0";
3050 node.expand_toggle.appendChild(node.plus_img);
3051
3052 node.expanded = false;
3053 }
3054
3055 var a = document.createElement("a");
3056 node.label_div.appendChild(a);
3057 node.label = document.createTextNode(text);
3058 a.appendChild(node.label);
3059 if (link) {
3060 a.href = me.toroot + link;
3061 } else {
3062 if (children_data != null) {
3063 a.className = "nolink";
3064 a.href = "javascript:void(0)";
3065 a.onclick = node.expand_toggle.onclick;
3066 // This next line shouldn't be necessary. I'll buy a beer for the first
3067 // person who figures out how to remove this line and have the link
3068 // toggle shut on the first try. --joeo@android.com
3069 node.expanded = false;
3070 }
3071 }
Scott Main3b90aff2013-08-01 18:09:35 -07003072
Scott Mainf5089842012-08-14 16:31:07 -07003073
3074 node.children_ul = null;
3075 node.get_children_ul = function() {
3076 if (!node.children_ul) {
3077 node.children_ul = document.createElement("ul");
3078 node.children_ul.className = "children_ul";
3079 node.children_ul.style.display = "none";
3080 node.li.appendChild(node.children_ul);
3081 }
3082 return node.children_ul;
3083 };
3084
3085 return node;
3086}
3087
Robert Lyd2dd6e52012-11-29 21:28:48 -08003088
3089
3090
Scott Mainf5089842012-08-14 16:31:07 -07003091function expand_node(me, node)
3092{
3093 if (node.children_data && !node.expanded) {
3094 if (node.children_visited) {
3095 $(node.get_children_ul()).slideDown("fast");
3096 } else {
3097 get_node(me, node);
3098 if ($(node.label_div).hasClass("absent")) {
3099 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07003100 }
Scott Mainf5089842012-08-14 16:31:07 -07003101 $(node.get_children_ul()).slideDown("fast");
3102 }
3103 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
3104 node.expanded = true;
3105
3106 // perform api level toggling because new nodes are new to the DOM
3107 var selectedLevel = $("#apiLevelSelector option:selected").val();
3108 toggleVisisbleApis(selectedLevel, "#side-nav");
3109 }
3110}
3111
3112function get_node(me, mom)
3113{
3114 mom.children_visited = true;
3115 for (var i in mom.children_data) {
3116 var node_data = mom.children_data[i];
3117 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
3118 node_data[2], node_data[3]);
3119 }
3120}
3121
3122function this_page_relative(toroot)
3123{
3124 var full = document.location.pathname;
3125 var file = "";
3126 if (toroot.substr(0, 1) == "/") {
3127 if (full.substr(0, toroot.length) == toroot) {
3128 return full.substr(toroot.length);
3129 } else {
3130 // the file isn't under toroot. Fail.
3131 return null;
3132 }
3133 } else {
3134 if (toroot != "./") {
3135 toroot = "./" + toroot;
3136 }
3137 do {
3138 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3139 var pos = full.lastIndexOf("/");
3140 file = full.substr(pos) + file;
3141 full = full.substr(0, pos);
3142 toroot = toroot.substr(0, toroot.length-3);
3143 }
3144 } while (toroot != "" && toroot != "/");
3145 return file.substr(1);
3146 }
3147}
3148
3149function find_page(url, data)
3150{
3151 var nodes = data;
3152 var result = null;
3153 for (var i in nodes) {
3154 var d = nodes[i];
3155 if (d[1] == url) {
3156 return new Array(i);
3157 }
3158 else if (d[2] != null) {
3159 result = find_page(url, d[2]);
3160 if (result != null) {
3161 return (new Array(i).concat(result));
3162 }
3163 }
3164 }
3165 return null;
3166}
3167
Scott Mainf5089842012-08-14 16:31:07 -07003168function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07003169 // load json file for navtree data
3170 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3171 // when the file is loaded, initialize the tree
3172 if(jqxhr.status === 200) {
3173 init_navtree("tree-list", toroot, NAVTREE_DATA);
3174 }
3175 });
Scott Main3b90aff2013-08-01 18:09:35 -07003176
Scott Mainf5089842012-08-14 16:31:07 -07003177 // perform api level toggling because because the whole tree is new to the DOM
3178 var selectedLevel = $("#apiLevelSelector option:selected").val();
3179 toggleVisisbleApis(selectedLevel, "#side-nav");
3180}
3181
3182function init_navtree(navtree_id, toroot, root_nodes)
3183{
3184 var me = new Object();
3185 me.toroot = toroot;
3186 me.node = new Object();
3187
3188 me.node.li = document.getElementById(navtree_id);
3189 me.node.children_data = root_nodes;
3190 me.node.children = new Array();
3191 me.node.children_ul = document.createElement("ul");
3192 me.node.get_children_ul = function() { return me.node.children_ul; };
3193 //me.node.children_ul.className = "children_ul";
3194 me.node.li.appendChild(me.node.children_ul);
3195 me.node.depth = 0;
3196
3197 get_node(me, me.node);
3198
3199 me.this_page = this_page_relative(toroot);
3200 me.breadcrumbs = find_page(me.this_page, root_nodes);
3201 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3202 var mom = me.node;
3203 for (var i in me.breadcrumbs) {
3204 var j = me.breadcrumbs[i];
3205 mom = mom.children[j];
3206 expand_node(me, mom);
3207 }
3208 mom.label_div.className = mom.label_div.className + " selected";
3209 addLoadEvent(function() {
3210 scrollIntoView("nav-tree");
3211 });
3212 }
3213}
3214
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003215
3216
3217
3218
3219
3220
3221
Robert Lyd2dd6e52012-11-29 21:28:48 -08003222/* TODO: eliminate redundancy with non-google functions */
3223function init_google_navtree(navtree_id, toroot, root_nodes)
3224{
3225 var me = new Object();
3226 me.toroot = toroot;
3227 me.node = new Object();
3228
3229 me.node.li = document.getElementById(navtree_id);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003230 if (!me.node.li) {
3231 return;
3232 }
3233
Robert Lyd2dd6e52012-11-29 21:28:48 -08003234 me.node.children_data = root_nodes;
3235 me.node.children = new Array();
3236 me.node.children_ul = document.createElement("ul");
3237 me.node.get_children_ul = function() { return me.node.children_ul; };
3238 //me.node.children_ul.className = "children_ul";
3239 me.node.li.appendChild(me.node.children_ul);
3240 me.node.depth = 0;
3241
3242 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003243}
3244
3245function new_google_node(me, mom, text, link, children_data, api_level)
3246{
3247 var node = new Object();
3248 var child;
3249 node.children = Array();
3250 node.children_data = children_data;
3251 node.depth = mom.depth + 1;
3252 node.get_children_ul = function() {
3253 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003254 node.children_ul = document.createElement("ul");
3255 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003256 node.li.appendChild(node.children_ul);
3257 }
3258 return node.children_ul;
3259 };
3260 node.li = document.createElement("li");
3261
3262 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003263
3264
Robert Lyd2dd6e52012-11-29 21:28:48 -08003265 if(link) {
3266 child = document.createElement("a");
3267
3268 }
3269 else {
3270 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003271 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003272
3273 }
3274 if (children_data != null) {
3275 node.li.className="nav-section";
3276 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003277 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003278 node.li.appendChild(node.label_div);
3279 get_google_node(me, node);
3280 node.label_div.appendChild(child);
3281 }
3282 else {
3283 node.li.appendChild(child);
3284 }
3285 if(link) {
3286 child.href = me.toroot + link;
3287 }
3288 node.label = document.createTextNode(text);
3289 child.appendChild(node.label);
3290
3291 node.children_ul = null;
3292
3293 return node;
3294}
3295
3296function get_google_node(me, mom)
3297{
3298 mom.children_visited = true;
3299 var linkText;
3300 for (var i in mom.children_data) {
3301 var node_data = mom.children_data[i];
3302 linkText = node_data[0];
3303
3304 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3305 linkText = linkText.substr(19, linkText.length);
3306 }
3307 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3308 node_data[2], node_data[3]);
3309 }
3310}
Scott Mainad08f072013-08-20 16:49:57 -07003311
3312
3313
3314
3315
3316
3317/****** NEW version of script to build google and sample navs dynamically ******/
3318// TODO: update Google reference docs to tolerate this new implementation
3319
Scott Maine624b3f2013-09-12 12:56:41 -07003320var NODE_NAME = 0;
3321var NODE_HREF = 1;
3322var NODE_GROUP = 2;
3323var NODE_TAGS = 3;
3324var NODE_CHILDREN = 4;
3325
Scott Mainad08f072013-08-20 16:49:57 -07003326function init_google_navtree2(navtree_id, data)
3327{
3328 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003329 for (var i in data) {
3330 var node_data = data[i];
3331 $containerUl.append(new_google_node2(node_data));
3332 }
3333
Scott Main70557ee2013-10-30 14:47:40 -07003334 // Make all third-generation list items 'sticky' to prevent them from collapsing
3335 $containerUl.find('li li li.nav-section').addClass('sticky');
3336
Scott Mainad08f072013-08-20 16:49:57 -07003337 initExpandableNavItems("#"+navtree_id);
3338}
3339
3340function new_google_node2(node_data)
3341{
Scott Maine624b3f2013-09-12 12:56:41 -07003342 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003343 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3344 linkText = linkText.substr(19, linkText.length);
3345 }
3346 var $li = $('<li>');
3347 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003348 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003349 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3350 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003351 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003352 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3353 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003354 }
3355 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003356 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003357 $li.addClass("nav-section");
3358 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003359 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003360
Scott Maine624b3f2013-09-12 12:56:41 -07003361 for (var i in node_data[NODE_CHILDREN]) {
3362 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003363 $childUl.append(new_google_node2(child_node_data));
3364 }
3365 $li.append($childUl);
3366 }
3367 $li.prepend($a);
3368
3369 return $li;
3370}
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
Robert Lyd2dd6e52012-11-29 21:28:48 -08003382function showGoogleRefTree() {
3383 init_default_google_navtree(toRoot);
3384 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003385}
3386
3387function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003388 // load json file for navtree data
3389 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3390 // when the file is loaded, initialize the tree
3391 if(jqxhr.status === 200) {
3392 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3393 highlightSidenav();
3394 resizeNav();
3395 }
3396 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003397}
3398
3399function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003400 // load json file for navtree data
3401 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3402 // when the file is loaded, initialize the tree
3403 if(jqxhr.status === 200) {
3404 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3405 highlightSidenav();
3406 resizeNav();
3407 }
3408 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003409}
3410
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003411function showSamplesRefTree() {
3412 init_default_samples_navtree(toRoot);
3413}
3414
3415function init_default_samples_navtree(toroot) {
3416 // load json file for navtree data
3417 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3418 // when the file is loaded, initialize the tree
3419 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003420 // hack to remove the "about the samples" link then put it back in
3421 // after we nuke the list to remove the dummy static list of samples
3422 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3423 $("#nav.samples-nav").empty();
3424 $("#nav.samples-nav").append($firstLi);
3425
Scott Mainad08f072013-08-20 16:49:57 -07003426 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003427 highlightSidenav();
3428 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003429 if ($("#jd-content #samples").length) {
3430 showSamples();
3431 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003432 }
3433 });
3434}
3435
Scott Mainf5089842012-08-14 16:31:07 -07003436/* TOGGLE INHERITED MEMBERS */
3437
3438/* Toggle an inherited class (arrow toggle)
3439 * @param linkObj The link that was clicked.
3440 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3441 * 'null' to simply toggle.
3442 */
3443function toggleInherited(linkObj, expand) {
3444 var base = linkObj.getAttribute("id");
3445 var list = document.getElementById(base + "-list");
3446 var summary = document.getElementById(base + "-summary");
3447 var trigger = document.getElementById(base + "-trigger");
3448 var a = $(linkObj);
3449 if ( (expand == null && a.hasClass("closed")) || expand ) {
3450 list.style.display = "none";
3451 summary.style.display = "block";
smain@google.comc0401872016-03-16 11:48:23 -07003452 trigger.src = toRoot + "assets/images/styles/disclosure_up.png";
Scott Mainf5089842012-08-14 16:31:07 -07003453 a.removeClass("closed");
3454 a.addClass("opened");
3455 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3456 list.style.display = "block";
3457 summary.style.display = "none";
smain@google.comc0401872016-03-16 11:48:23 -07003458 trigger.src = toRoot + "assets/images/styles/disclosure_down.png";
Scott Mainf5089842012-08-14 16:31:07 -07003459 a.removeClass("opened");
3460 a.addClass("closed");
3461 }
3462 return false;
3463}
3464
3465/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3466 * @param linkObj The link that was clicked.
3467 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3468 * 'null' to simply toggle.
3469 */
3470function toggleAllInherited(linkObj, expand) {
3471 var a = $(linkObj);
3472 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3473 var expandos = $(".jd-expando-trigger", table);
3474 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3475 expandos.each(function(i) {
3476 toggleInherited(this, true);
3477 });
3478 a.text("[Collapse]");
3479 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3480 expandos.each(function(i) {
3481 toggleInherited(this, false);
3482 });
3483 a.text("[Expand]");
3484 }
3485 return false;
3486}
3487
3488/* Toggle all inherited members in the class (link in the class title)
3489 */
3490function toggleAllClassInherited() {
3491 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3492 var toggles = $(".toggle-all", $("#body-content"));
3493 if (a.text() == "[Expand All]") {
3494 toggles.each(function(i) {
3495 toggleAllInherited(this, true);
3496 });
3497 a.text("[Collapse All]");
3498 } else {
3499 toggles.each(function(i) {
3500 toggleAllInherited(this, false);
3501 });
3502 a.text("[Expand All]");
3503 }
3504 return false;
3505}
3506
3507/* Expand all inherited members in the class. Used when initiating page search */
3508function ensureAllInheritedExpanded() {
3509 var toggles = $(".toggle-all", $("#body-content"));
3510 toggles.each(function(i) {
3511 toggleAllInherited(this, true);
3512 });
3513 $("#toggleAllClassInherited").text("[Collapse All]");
3514}
3515
3516
3517/* HANDLE KEY EVENTS
3518 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3519 */
3520var agent = navigator['userAgent'].toLowerCase();
3521var mac = agent.indexOf("macintosh") != -1;
3522
3523$(document).keydown( function(e) {
3524var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3525 if (control && e.which == 70) { // 70 is "F"
3526 ensureAllInheritedExpanded();
3527 }
3528});
Scott Main498d7102013-08-21 15:47:38 -07003529
3530
3531
3532
3533
3534
3535/* On-demand functions */
3536
3537/** Move sample code line numbers out of PRE block and into non-copyable column */
3538function initCodeLineNumbers() {
3539 var numbers = $("#codesample-block a.number");
3540 if (numbers.length) {
3541 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3542 }
3543
3544 $(document).ready(function() {
3545 // select entire line when clicked
3546 $("span.code-line").click(function() {
3547 if (!shifted) {
3548 selectText(this);
3549 }
3550 });
3551 // invoke line link on double click
3552 $(".code-line").dblclick(function() {
3553 document.location.hash = $(this).attr('id');
3554 });
3555 // highlight the line when hovering on the number
3556 $("#codesample-line-numbers a.number").mouseover(function() {
3557 var id = $(this).attr('href');
3558 $(id).css('background','#e7e7e7');
3559 });
3560 $("#codesample-line-numbers a.number").mouseout(function() {
3561 var id = $(this).attr('href');
3562 $(id).css('background','none');
3563 });
3564 });
3565}
3566
3567// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3568var shifted = false;
3569$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3570
3571// courtesy of jasonedelman.com
3572function selectText(element) {
3573 var doc = document
3574 , range, selection
3575 ;
3576 if (doc.body.createTextRange) { //ms
3577 range = doc.body.createTextRange();
3578 range.moveToElementText(element);
3579 range.select();
3580 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003581 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003582 range = doc.createRange();
3583 range.selectNodeContents(element);
3584 selection.removeAllRanges();
3585 selection.addRange(range);
3586 }
Scott Main285f0772013-08-22 23:22:09 +00003587}
Scott Main03aca9a2013-10-31 07:20:55 -07003588
3589
3590
3591
3592/** Display links and other information about samples that match the
3593 group specified by the URL */
3594function showSamples() {
3595 var group = $("#samples").attr('class');
3596 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3597
3598 var $ul = $("<ul>");
3599 $selectedLi = $("#nav li.selected");
3600
3601 $selectedLi.children("ul").children("li").each(function() {
3602 var $li = $("<li>").append($(this).find("a").first().clone());
3603 $ul.append($li);
3604 });
3605
3606 $("#samples").append($ul);
3607
3608}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003609
3610
3611
3612/* ########################################################## */
3613/* ################### RESOURCE CARDS ##################### */
3614/* ########################################################## */
3615
3616/** Handle resource queries, collections, and grids (sections). Requires
3617 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3618
3619(function() {
3620 // Prevent the same resource from being loaded more than once per page.
3621 var addedPageResources = {};
3622
3623 $(document).ready(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003624 // Need to initialize hero carousel before other sections for dedupe
3625 // to work correctly.
3626 $('[data-carousel-query]').dacCarouselQuery();
3627
Dirk Doughertyc3921652014-05-13 16:55:26 -07003628 $('.resource-widget').each(function() {
3629 initResourceWidget(this);
3630 });
3631
3632 /* Pass the line height to ellipsisfade() to adjust the height of the
3633 text container to show the max number of lines possible, without
3634 showing lines that are cut off. This works with the css ellipsis
3635 classes to fade last text line and apply an ellipsis char. */
3636
Dirk Dougherty29e93432015-05-05 18:17:13 -07003637 //card text currently uses 20px line height.
3638 var lineHeight = 20;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003639 $('.card-info .text').ellipsisfade(lineHeight);
3640 });
3641
3642 /*
3643 Three types of resource layouts:
3644 Flow - Uses a fixed row-height flow using float left style.
3645 Carousel - Single card slideshow all same dimension absolute.
3646 Stack - Uses fixed columns and flexible element height.
3647 */
3648 function initResourceWidget(widget) {
3649 var $widget = $(widget);
3650 var isFlow = $widget.hasClass('resource-flow-layout'),
3651 isCarousel = $widget.hasClass('resource-carousel-layout'),
3652 isStack = $widget.hasClass('resource-stack-layout');
3653
Dirk Dougherty29e93432015-05-05 18:17:13 -07003654 // remove illegal col-x class which is not relevant anymore thanks to responsive styles.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003655 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
Dirk Dougherty29e93432015-05-05 18:17:13 -07003656 if (m && !$widget.is('.cols > *')) {
3657 $widget.removeClass('col-' + m[1]);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003658 }
3659
3660 var opts = {
3661 cardSizes: ($widget.data('cardsizes') || '').split(','),
3662 maxResults: parseInt($widget.data('maxresults') || '100', 10),
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003663 initialResults: $widget.data('initialResults'),
Dirk Doughertyc3921652014-05-13 16:55:26 -07003664 itemsPerPage: $widget.data('itemsperpage'),
3665 sortOrder: $widget.data('sortorder'),
3666 query: $widget.data('query'),
3667 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003668 /* Added by LFL 6/6/14 */
3669 resourceStyle: $widget.data('resourcestyle') || 'card',
3670 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003671 };
3672
3673 // run the search for the set of resources to show
3674
3675 var resources = buildResourceList(opts);
3676
3677 if (isFlow) {
3678 drawResourcesFlowWidget($widget, opts, resources);
3679 } else if (isCarousel) {
3680 drawResourcesCarouselWidget($widget, opts, resources);
3681 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003682 /* Looks like this got removed and is not used, so repurposing for the
3683 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003684 Modified by LFL 6/6/14
3685 */
3686 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003687 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003688 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003689 }
3690 }
3691
3692 /* Initializes a Resource Carousel Widget */
3693 function drawResourcesCarouselWidget($widget, opts, resources) {
3694 $widget.empty();
Dirk Dougherty29e93432015-05-05 18:17:13 -07003695 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003696
3697 $widget.addClass('resource-card slideshow-container')
3698 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3699 .append($('<a>').addClass('slideshow-next').text('Next'));
3700
3701 var css = { 'width': $widget.width() + 'px',
3702 'height': $widget.height() + 'px' };
3703
3704 var $ul = $('<ul>');
3705
3706 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003707 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003708 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003709 .decorateResourceCard(resources[i],plusone);
3710
3711 $('<li>').css(css)
3712 .append($card)
3713 .appendTo($ul);
3714 }
3715
3716 $('<div>').addClass('frame')
3717 .append($ul)
3718 .appendTo($widget);
3719
3720 $widget.dacSlideshow({
3721 auto: true,
3722 btnPrev: '.slideshow-prev',
3723 btnNext: '.slideshow-next'
3724 });
3725 };
3726
Robert Lye7eeb402014-06-03 19:35:24 -07003727 /* Initializes a Resource Card Stack Widget (column-based layout)
3728 Modified by LFL 6/6/14
3729 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003730 function drawResourcesStackWidget($widget, opts, resources, sections) {
3731 // Don't empty widget, grab all items inside since they will be the first
3732 // items stacked, followed by the resource query
Dirk Dougherty29e93432015-05-05 18:17:13 -07003733 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003734 var cards = $widget.find('.resource-card').detach().toArray();
3735 var numStacks = opts.numStacks || 1;
3736 var $stacks = [];
3737 var urlString;
3738
3739 for (var i = 0; i < numStacks; ++i) {
3740 $stacks[i] = $('<div>').addClass('resource-card-stack')
3741 .appendTo($widget);
3742 }
3743
3744 var sectionResources = [];
3745
3746 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003747 if (sections) {
3748 for (var i = 0; i < sections.length; ++i) {
3749 if (!sections[i].sections || !sections[i].sections.length) {
3750 // Render it as a resource card
3751 sectionResources.push(
3752 $('<a>')
3753 .addClass('resource-card section-card')
3754 .attr('href', cleanUrl(sections[i].resource.url))
3755 .decorateResourceCard(sections[i].resource,plusone)[0]
3756 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003757
Robert Lye7eeb402014-06-03 19:35:24 -07003758 } else {
3759 cards.push(
3760 $('<div>')
3761 .addClass('resource-card section-card-menu')
3762 .decorateResourceSection(sections[i],plusone)[0]
3763 );
3764 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003765 }
3766 }
3767
3768 cards = cards.concat(sectionResources);
3769
3770 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003771 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003772
Robert Lye7eeb402014-06-03 19:35:24 -07003773 if (opts.resourceStyle.indexOf('related') > -1) {
3774 $card.addClass('related-card');
3775 }
smain@google.com95948b82014-06-16 19:24:25 -07003776
Dirk Doughertyc3921652014-05-13 16:55:26 -07003777 cards.push($card[0]);
3778 }
3779
Robert Lye7eeb402014-06-03 19:35:24 -07003780 if (opts.stackSort != 'false') {
3781 for (var i = 0; i < cards.length; ++i) {
3782 // Find the stack with the shortest height, but give preference to
3783 // left to right order.
3784 var minHeight = $stacks[0].height();
3785 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003786
Robert Lye7eeb402014-06-03 19:35:24 -07003787 for (var j = 1; j < numStacks; ++j) {
3788 var height = $stacks[j].height();
3789 if (height < minHeight - 45) {
3790 minHeight = height;
3791 minIndex = j;
3792 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003793 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003794
Robert Lye7eeb402014-06-03 19:35:24 -07003795 $stacks[minIndex].append($(cards[i]));
3796 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003797 }
3798
3799 };
smain@google.com95948b82014-06-16 19:24:25 -07003800
3801 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003802 Create a resource card using the given resource object and a list of html
3803 configured options. Returns a jquery object containing the element.
3804 */
smain@google.com95948b82014-06-16 19:24:25 -07003805 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003806 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003807
Robert Lye7eeb402014-06-03 19:35:24 -07003808 // The difference here is that generic cards are not entirely clickable
3809 // so its a div instead of an a tag, also the generic one is not given
3810 // the resource-card class so it appears with a transparent background
3811 // and can be styled in whatever way the css setup.
3812 if (opts.resourceStyle == 'generic') {
3813 $el = $('<div>')
3814 .addClass('resource')
3815 .attr('href', cleanUrl(resource.url))
3816 .decorateResource(resource, opts);
3817 } else {
3818 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003819
Robert Lye7eeb402014-06-03 19:35:24 -07003820 $el = $('<a>')
3821 .addClass(cls)
3822 .attr('href', cleanUrl(resource.url))
3823 .decorateResourceCard(resource, plusone);
3824 }
smain@google.com95948b82014-06-16 19:24:25 -07003825
Robert Lye7eeb402014-06-03 19:35:24 -07003826 return $el;
3827 }
Quddus Chong2cb2f682015-09-04 14:45:46 -07003828
Dirk Dougherty29e93432015-05-05 18:17:13 -07003829 function createResponsiveFlowColumn(cardSize) {
3830 var cardWidth = parseInt(cardSize.match(/(\d+)/)[1], 10);
3831 var column = $('<div>').addClass('col-' + (cardWidth / 3) + 'of6');
3832 if (cardWidth < 9) {
3833 column.addClass('col-tablet-1of2');
3834 } else if (cardWidth > 9 && cardWidth < 18) {
3835 column.addClass('col-tablet-1of1');
3836 }
3837 if (cardWidth < 18) {
3838 column.addClass('col-mobile-1of1')
3839 }
3840 return column;
3841 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003842
3843 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3844 function drawResourcesFlowWidget($widget, opts, resources) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003845 $widget.empty().addClass('cols');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003846 var cardSizes = opts.cardSizes || ['6x6'];
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003847 var initialResults = opts.initialResults || resources.length;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003848 var i = 0, j = 0;
Dirk Dougherty29e93432015-05-05 18:17:13 -07003849 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003850 var cardParent = $widget;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003851
3852 while (i < resources.length) {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003853
3854 if (i === initialResults && initialResults < resources.length) {
3855 // Toggle remaining cards
3856 cardParent = $('<div class="dac-toggle-content clearfix">').appendTo($widget);
3857 $widget.addClass('dac-toggle');
3858 $('<div class="col-1of1 dac-section-links dac-text-center">')
3859 .append(
3860 $('<div class="dac-section-link" data-toggle="section">')
3861 .append('<span class="dac-toggle-expand">More<i class="dac-sprite dac-auto-unfold-more"></i></span>')
3862 .append('<span class="dac-toggle-collapse">Less<i class="dac-sprite dac-auto-unfold-less"></i></span>')
3863 )
3864 .appendTo($widget)
3865 }
3866
Dirk Doughertyc3921652014-05-13 16:55:26 -07003867 var cardSize = cardSizes[j++ % cardSizes.length];
3868 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Quddus Chong2cb2f682015-09-04 14:45:46 -07003869
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003870 var column = createResponsiveFlowColumn(cardSize).appendTo(cardParent);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003871
3872 // A stack has a third dimension which is the number of stacked items
3873 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3874 var stackCount = 0;
3875 var $stackDiv = null;
3876
3877 if (isStack) {
3878 // Create a stack container which should have the dimensions defined
3879 // by the product of the items inside.
3880 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
Dirk Dougherty29e93432015-05-05 18:17:13 -07003881 + 'x' + isStack[2] * isStack[3]) .appendTo(column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003882 }
3883
3884 // Build each stack item or just a single item
3885 do {
3886 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003887
Robert Lye7eeb402014-06-03 19:35:24 -07003888 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003889
3890 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003891 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003892
Dirk Doughertyc3921652014-05-13 16:55:26 -07003893 if (isStack) {
3894 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3895 if (++stackCount == parseInt(isStack[3])) {
3896 $card.addClass('resource-card-row-stack-last');
3897 stackCount = 0;
3898 }
3899 } else {
3900 stackCount = 0;
3901 }
3902
Dirk Dougherty29e93432015-05-05 18:17:13 -07003903 $card.appendTo($stackDiv || column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003904
3905 } while (++i < resources.length && stackCount > 0);
3906 }
3907 }
3908
3909 /* Build a site map of resources using a section as a root. */
3910 function buildSectionList(opts) {
3911 if (opts.section && SECTION_BY_ID[opts.section]) {
3912 return SECTION_BY_ID[opts.section].sections || [];
3913 }
3914 return [];
3915 }
3916
3917 function buildResourceList(opts) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003918 return $.queryResources(opts);
3919 }
3920
3921 $.queryResources = function(opts) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003922 var maxResults = opts.maxResults || 100;
3923
3924 var query = opts.query || '';
3925 var expressions = parseResourceQuery(query);
3926 var addedResourceIndices = {};
3927 var results = [];
3928
3929 for (var i = 0; i < expressions.length; i++) {
3930 var clauses = expressions[i];
3931
3932 // build initial set of resources from first clause
3933 var firstClause = clauses[0];
3934 var resources = [];
3935 switch (firstClause.attr) {
3936 case 'type':
3937 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3938 break;
3939 case 'lang':
3940 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3941 break;
3942 case 'tag':
3943 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3944 break;
3945 case 'collection':
3946 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3947 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3948 break;
3949 case 'section':
3950 var urls = SITE_MAP[firstClause.value].sections || [];
3951 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3952 break;
3953 }
3954 // console.log(firstClause.attr + ':' + firstClause.value);
3955 resources = resources || [];
3956
3957 // use additional clauses to filter corpus
3958 if (clauses.length > 1) {
3959 var otherClauses = clauses.slice(1);
3960 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3961 }
3962
3963 // filter out resources already added
3964 if (i > 1) {
3965 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3966 }
3967
3968 // add to list of already added indices
3969 for (var j = 0; j < resources.length; j++) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003970 if (resources[j]) {
3971 addedResourceIndices[resources[j].index] = 1;
3972 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003973 }
3974
3975 // concat to final results list
3976 results = results.concat(resources);
3977 }
3978
3979 if (opts.sortOrder && results.length) {
3980 var attr = opts.sortOrder;
3981
3982 if (opts.sortOrder == 'random') {
3983 var i = results.length, j, temp;
3984 while (--i) {
3985 j = Math.floor(Math.random() * (i + 1));
3986 temp = results[i];
3987 results[i] = results[j];
3988 results[j] = temp;
3989 }
3990 } else {
3991 var desc = attr.charAt(0) == '-';
3992 if (desc) {
3993 attr = attr.substring(1);
3994 }
3995 results = results.sort(function(x,y) {
3996 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3997 });
3998 }
3999 }
4000
4001 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
4002 results = results.slice(0, maxResults);
4003
4004 for (var j = 0; j < results.length; ++j) {
4005 addedPageResources[results[j].index] = 1;
4006 }
4007
4008 return results;
4009 }
4010
4011
4012 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
4013 return function(resource) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004014 return resource && !addedResourceIndices[resource.index];
Dirk Doughertyc3921652014-05-13 16:55:26 -07004015 };
4016 }
4017
4018
4019 function getResourceMatchesClausesFilter(clauses) {
4020 return function(resource) {
4021 return doesResourceMatchClauses(resource, clauses);
4022 };
4023 }
4024
4025
4026 function doesResourceMatchClauses(resource, clauses) {
4027 for (var i = 0; i < clauses.length; i++) {
4028 var map;
4029 switch (clauses[i].attr) {
4030 case 'type':
4031 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
4032 break;
4033 case 'lang':
4034 map = IS_RESOURCE_IN_LANG[clauses[i].value];
4035 break;
4036 case 'tag':
4037 map = IS_RESOURCE_TAGGED[clauses[i].value];
4038 break;
4039 }
4040
4041 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
4042 return clauses[i].negative;
4043 }
4044 }
4045 return true;
4046 }
smain@google.com95948b82014-06-16 19:24:25 -07004047
Robert Lye7eeb402014-06-03 19:35:24 -07004048 function cleanUrl(url)
4049 {
4050 if (url && url.indexOf('//') === -1) {
4051 url = toRoot + url;
4052 }
smain@google.com95948b82014-06-16 19:24:25 -07004053
Robert Lye7eeb402014-06-03 19:35:24 -07004054 return url;
4055 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004056
4057
4058 function parseResourceQuery(query) {
4059 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
4060 var expressions = [];
4061 var expressionStrs = query.split(',') || [];
4062 for (var i = 0; i < expressionStrs.length; i++) {
4063 var expr = expressionStrs[i] || '';
4064
4065 // Break expression into clauses (clause e.g. 'tag:foo')
4066 var clauses = [];
4067 var clauseStrs = expr.split(/(?=[\+\-])/);
4068 for (var j = 0; j < clauseStrs.length; j++) {
4069 var clauseStr = clauseStrs[j] || '';
4070
4071 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
4072 var parts = clauseStr.split(':');
4073 var clause = {};
4074
4075 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
4076 if (clause.attr) {
4077 if (clause.attr.charAt(0) == '+') {
4078 clause.attr = clause.attr.substring(1);
4079 } else if (clause.attr.charAt(0) == '-') {
4080 clause.negative = true;
4081 clause.attr = clause.attr.substring(1);
4082 }
4083 }
4084
4085 if (parts.length > 1) {
4086 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
4087 }
4088
4089 clauses.push(clause);
4090 }
4091
4092 if (!clauses.length) {
4093 continue;
4094 }
4095
4096 expressions.push(clauses);
4097 }
4098
4099 return expressions;
4100 }
4101})();
4102
4103(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07004104
smain@google.com95948b82014-06-16 19:24:25 -07004105 /*
Robert Lye7eeb402014-06-03 19:35:24 -07004106 Utility method for creating dom for the description area of a card.
4107 Used in decorateResourceCard and decorateResource.
4108 */
4109 function buildResourceCardDescription(resource, plusone) {
4110 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07004111
Robert Lye7eeb402014-06-03 19:35:24 -07004112 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07004113
Robert Lye7eeb402014-06-03 19:35:24 -07004114 if (resource.cta) {
4115 $description.append($('<a>').addClass('cta').html(resource.cta));
4116 }
smain@google.com95948b82014-06-16 19:24:25 -07004117
Robert Lye7eeb402014-06-03 19:35:24 -07004118 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07004119 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07004120 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004121
Robert Lye7eeb402014-06-03 19:35:24 -07004122 $description.append($('<div>').addClass('util')
4123 .append($('<div>').addClass('g-plusone')
4124 .attr('data-size', 'small')
4125 .attr('data-align', 'right')
4126 .attr('data-href', plusurl)));
4127 }
smain@google.com95948b82014-06-16 19:24:25 -07004128
Robert Lye7eeb402014-06-03 19:35:24 -07004129 return $description;
4130 }
smain@google.com95948b82014-06-16 19:24:25 -07004131
4132
Dirk Doughertyc3921652014-05-13 16:55:26 -07004133 /* Simple jquery function to create dom for a standard resource card */
4134 $.fn.decorateResourceCard = function(resource,plusone) {
4135 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07004136 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004137 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07004138
Robert Lye7eeb402014-06-03 19:35:24 -07004139 if (imgUrl.indexOf('//') === -1) {
4140 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07004141 }
Robert Lye7eeb402014-06-03 19:35:24 -07004142
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004143 if (resource.type === 'youtube') {
4144 $('<div>').addClass('play-button')
4145 .append($('<i class="dac-sprite dac-play-white">'))
4146 .appendTo(this);
4147 }
4148
Robert Lye7eeb402014-06-03 19:35:24 -07004149 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07004150 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07004151 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07004152 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07004153
Robert Lye7eeb402014-06-03 19:35:24 -07004154 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
4155 .append($('<div>').addClass('section').text(section))
4156 .append($('<div>').addClass('title').html(resource.title))
4157 .append(buildResourceCardDescription(resource, plusone))
4158 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07004159
4160 return this;
4161 };
4162
4163 /* Simple jquery function to create dom for a resource section card (menu) */
4164 $.fn.decorateResourceSection = function(section,plusone) {
4165 var resource = section.resource;
4166 //keep url clean for matching and offline mode handling
4167 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
4168 var $base = $('<a>')
4169 .addClass('card-bg')
4170 .attr('href', resource.url)
4171 .append($('<div>').addClass('card-section-icon')
4172 .append($('<div>').addClass('icon'))
4173 .append($('<div>').addClass('section').html(resource.title)))
4174 .appendTo(this);
4175
4176 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
4177
4178 if (section.sections && section.sections.length) {
4179 // Recurse the section sub-tree to find a resource image.
4180 var stack = [section];
4181
4182 while (stack.length) {
4183 if (stack[0].resource.image) {
4184 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4185 break;
4186 }
4187
4188 if (stack[0].sections) {
4189 stack = stack.concat(stack[0].sections);
4190 }
4191
4192 stack.shift();
4193 }
4194
4195 var $ul = $('<ul>')
4196 .appendTo($cardInfo);
4197
4198 var max = section.sections.length > 3 ? 3 : section.sections.length;
4199
4200 for (var i = 0; i < max; ++i) {
4201
4202 var subResource = section.sections[i];
4203 if (!plusone) {
4204 $('<li>')
4205 .append($('<a>').attr('href', subResource.url)
4206 .append($('<div>').addClass('title').html(subResource.title))
4207 .append($('<div>').addClass('description ellipsis')
4208 .append($('<div>').addClass('text').html(subResource.summary))
4209 .append($('<div>').addClass('util'))))
4210 .appendTo($ul);
4211 } else {
4212 $('<li>')
4213 .append($('<a>').attr('href', subResource.url)
4214 .append($('<div>').addClass('title').html(subResource.title))
4215 .append($('<div>').addClass('description ellipsis')
4216 .append($('<div>').addClass('text').html(subResource.summary))
4217 .append($('<div>').addClass('util')
4218 .append($('<div>').addClass('g-plusone')
4219 .attr('data-size', 'small')
4220 .attr('data-align', 'right')
4221 .attr('data-href', resource.url)))))
4222 .appendTo($ul);
4223 }
4224 }
4225
4226 // Add a more row
4227 if (max < section.sections.length) {
4228 $('<li>')
4229 .append($('<a>').attr('href', resource.url)
4230 .append($('<div>')
4231 .addClass('title')
4232 .text('More')))
4233 .appendTo($ul);
4234 }
4235 } else {
4236 // No sub-resources, just render description?
4237 }
4238
4239 return this;
4240 };
smain@google.com95948b82014-06-16 19:24:25 -07004241
4242
4243
4244
Robert Lye7eeb402014-06-03 19:35:24 -07004245 /* Render other types of resource styles that are not cards. */
4246 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07004247 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004248 'assets/images/resource-card-default-android.jpg';
4249 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004250
Robert Lye7eeb402014-06-03 19:35:24 -07004251 if (imgUrl.indexOf('//') === -1) {
4252 imgUrl = toRoot + imgUrl;
4253 }
smain@google.com95948b82014-06-16 19:24:25 -07004254
Robert Lye7eeb402014-06-03 19:35:24 -07004255 if (linkUrl && linkUrl.indexOf('//') === -1) {
4256 linkUrl = toRoot + linkUrl;
4257 }
4258
4259 $(this).append(
4260 $('<div>').addClass('image')
4261 .css('background-image', 'url(' + imgUrl + ')'),
4262 $('<div>').addClass('info').append(
4263 $('<h4>').addClass('title').html(resource.title),
4264 $('<p>').addClass('summary').html(resource.summary),
4265 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4266 )
4267 );
4268
4269 return this;
4270 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004271})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004272
4273
Dirk Doughertyc3921652014-05-13 16:55:26 -07004274/* Calculate the vertical area remaining */
4275(function($) {
4276 $.fn.ellipsisfade= function(lineHeight) {
4277 this.each(function() {
4278 // get element text
4279 var $this = $(this);
4280 var remainingHeight = $this.parent().parent().height();
4281 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004282 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004283 if ($(this).is(":visible")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004284 var h = $(this).outerHeight(true);
smain@google.comcda1a9a2014-06-19 17:07:46 -07004285 remainingHeight = remainingHeight - h;
4286 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004287 });
4288
4289 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4290 $this.parent().css({'height': adjustedRemainingHeight});
4291 $this.css({'height': "auto"});
4292 });
4293
4294 return this;
4295 };
4296}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004297
4298/*
4299 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004300
Robert Lye7eeb402014-06-03 19:35:24 -07004301 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004302 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004303 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004304
Robert Lye7eeb402014-06-03 19:35:24 -07004305 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004306
Robert Lye7eeb402014-06-03 19:35:24 -07004307 <div class="fullscreen-carousel">
4308 <div class="fullscreen-carousel-content">
4309 <!-- content here -->
4310 </div>
4311 <div class="fullscreen-carousel-content">
4312 <!-- content here -->
4313 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004314
Robert Lye7eeb402014-06-03 19:35:24 -07004315 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004316
Robert Lye7eeb402014-06-03 19:35:24 -07004317 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004318
Robert Lye7eeb402014-06-03 19:35:24 -07004319 Control over how the carousel takes over the screen can mostly be defined in
4320 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004321 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004322 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004323 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004324 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004325
Robert Lye7eeb402014-06-03 19:35:24 -07004326 There is limited functionality for having multiple sections since that request
4327 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4328 scroll between multiple content areas.
4329*/
4330
4331(function() {
4332 $(document).ready(function() {
4333 $('.fullscreen-carousel').each(function() {
4334 initWidget(this);
4335 });
4336 });
4337
4338 function initWidget(widget) {
4339 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004340
Robert Lye7eeb402014-06-03 19:35:24 -07004341 var topOffset = $widget.offset().top;
4342 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4343 var maxHeight = 0;
4344 var minHeight = 0;
4345 var $content = $widget.find('.fullscreen-carousel-content');
4346 var $nextArrow = $widget.find('.next-arrow');
4347 var $prevArrow = $widget.find('.prev-arrow');
4348 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004349
Robert Lye7eeb402014-06-03 19:35:24 -07004350 if ($content.length <= 1) {
4351 $nextArrow.hide();
4352 $prevArrow.hide();
4353 } else {
4354 $nextArrow.click(function() {
4355 var index = ($content.index($curSection) + 1);
4356 $curSection.hide();
4357 $curSection = $($content[index >= $content.length ? 0 : index]);
4358 $curSection.show();
4359 });
smain@google.com95948b82014-06-16 19:24:25 -07004360
Robert Lye7eeb402014-06-03 19:35:24 -07004361 $prevArrow.click(function() {
4362 var index = ($content.index($curSection) - 1);
4363 $curSection.hide();
4364 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4365 $curSection.show();
4366 });
4367 }
4368
4369 // Just hide all content sections except first.
4370 $content.each(function(index) {
4371 if ($(this).height() > minHeight) minHeight = $(this).height();
4372 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4373 });
4374
4375 // Register for changes to window size, and trigger.
4376 $(window).resize(resizeWidget);
4377 resizeWidget();
4378
4379 function resizeWidget() {
4380 var height = $(window).height() - topOffset - padBottom;
4381 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004382 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004383 (maxHeight && height > maxHeight ? maxHeight : height));
4384 }
smain@google.com95948b82014-06-16 19:24:25 -07004385 }
Robert Lye7eeb402014-06-03 19:35:24 -07004386})();
4387
4388
4389
4390
4391
4392/*
4393 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004394
Robert Lye7eeb402014-06-03 19:35:24 -07004395 The following allows tab widgets to be installed via the html below. Each
4396 tab content section should have a data-tab attribute matching one of the
4397 nav items'. Also each tab content section should have a width matching the
4398 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004399
Robert Lye7eeb402014-06-03 19:35:24 -07004400 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004401
Robert Lye7eeb402014-06-03 19:35:24 -07004402 <div class="tab-carousel">
4403 <ul class="tab-nav">
4404 <li><a href="#" data-tab="handsets">Handsets</a>
4405 <li><a href="#" data-tab="wearable">Wearable</a>
4406 <li><a href="#" data-tab="tv">TV</a>
4407 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004408
Robert Lye7eeb402014-06-03 19:35:24 -07004409 <div class="tab-carousel-content">
4410 <div data-tab="handsets">
4411 <!--Full width content here-->
4412 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004413
Robert Lye7eeb402014-06-03 19:35:24 -07004414 <div data-tab="wearable">
4415 <!--Full width content here-->
4416 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004417
Robert Lye7eeb402014-06-03 19:35:24 -07004418 <div data-tab="tv">
4419 <!--Full width content here-->
4420 </div>
4421 </div>
4422 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004423
Robert Lye7eeb402014-06-03 19:35:24 -07004424*/
4425(function() {
4426 $(document).ready(function() {
4427 $('.tab-carousel').each(function() {
4428 initWidget(this);
4429 });
4430 });
4431
4432 function initWidget(widget) {
4433 var $widget = $(widget);
4434 var $nav = $widget.find('.tab-nav');
4435 var $anchors = $nav.find('[data-tab]');
4436 var $li = $nav.find('li');
4437 var $contentContainer = $widget.find('.tab-carousel-content');
4438 var $tabs = $contentContainer.find('[data-tab]');
4439 var $curTab = $($tabs[0]); // Current tab is first tab.
4440 var width = $widget.width();
4441
4442 // Setup nav interactivity.
4443 $anchors.click(function(evt) {
4444 evt.preventDefault();
4445 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004446 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004447 });
smain@google.com95948b82014-06-16 19:24:25 -07004448
Robert Lye7eeb402014-06-03 19:35:24 -07004449 // Add highlight for navigation on first item.
4450 var $highlight = $('<div>').addClass('highlight')
4451 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4452 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004453
Robert Lye7eeb402014-06-03 19:35:24 -07004454 // Store height since we will change contents to absolute.
4455 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004456
Robert Lye7eeb402014-06-03 19:35:24 -07004457 // Absolutely position tabs so they're ready for transition.
4458 $tabs.each(function(index) {
4459 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4460 });
smain@google.com95948b82014-06-16 19:24:25 -07004461
Robert Lye7eeb402014-06-03 19:35:24 -07004462 function transitionWidget($toTab) {
4463 if (!$curTab.is($toTab)) {
4464 var curIndex = $tabs.index($curTab[0]);
4465 var toIndex = $tabs.index($toTab[0]);
4466 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004467
Robert Lye7eeb402014-06-03 19:35:24 -07004468 // Animate content sections.
4469 $toTab.css({left:(width * dir) + 'px'});
4470 $curTab.animate({left:(width * -dir) + 'px'});
4471 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004472
Robert Lye7eeb402014-06-03 19:35:24 -07004473 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004474 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004475 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004476
Robert Lye7eeb402014-06-03 19:35:24 -07004477 // Store new current section.
4478 $curTab = $toTab;
4479 }
4480 }
smain@google.com95948b82014-06-16 19:24:25 -07004481 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08004482})();
Dirk Dougherty29e93432015-05-05 18:17:13 -07004483
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004484/**
4485 * Auto TOC
4486 *
4487 * Upgrades h2s on the page to have a rule and be toggle-able on mobile.
4488 */
4489(function($) {
4490 var upgraded = false;
4491 var h2Titles;
4492
4493 function initWidget() {
4494 // add HRs below all H2s (except for a few other h2 variants)
4495 // Consider doing this with css instead.
4496 h2Titles = $('h2').not('#qv h2, #tb h2, .sidebox h2, #devdoc-nav h2, h2.norule');
4497 h2Titles.css({marginBottom:0}).after('<hr/>');
4498
4499 // Exit early if on older browser.
4500 if (!window.matchMedia) {
4501 return;
4502 }
4503
4504 // Only run logic in mobile layout.
4505 var query = window.matchMedia('(max-width: 719px)');
4506 if (query.matches) {
4507 makeTogglable();
4508 } else {
4509 query.addListener(makeTogglable);
4510 }
4511 }
4512
4513 function makeTogglable() {
4514 // Only run this logic once.
4515 if (upgraded) { return; }
4516 upgraded = true;
4517
4518 // Only make content h2s togglable.
4519 var contentTitles = h2Titles.filter('#jd-content *');
4520
4521 // If there are more than 1
4522 if (contentTitles.size() < 2) {
4523 return;
4524 }
4525
4526 contentTitles.each(function() {
4527 // Find all the relevant nodes.
4528 var $title = $(this);
4529 var $hr = $title.next();
4530 var $contents = $hr.nextUntil('h2, .next-docs');
4531 var $section = $($title)
4532 .add($hr)
4533 .add($title.prev('a[name]'))
4534 .add($contents);
4535 var $anchor = $section.first().prev();
4536 var anchorMethod = 'after';
4537 if ($anchor.length === 0) {
4538 $anchor = $title.parent();
4539 anchorMethod = 'prepend';
4540 }
4541
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004542 // Some h2s are in their own container making it pretty hard to find the end, so skip.
4543 if ($contents.length === 0) {
4544 return;
4545 }
4546
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004547 // Remove from DOM before messing with it. DOM is slow!
4548 $section.detach();
4549
4550 // Add mobile-only expand arrows.
4551 $title.prepend('<span class="dac-visible-mobile-inline-block">' +
4552 '<i class="dac-toggle-expand dac-sprite dac-expand-more-black"></i>' +
4553 '<i class="dac-toggle-collapse dac-sprite dac-expand-less-black"></i>' +
4554 '</span>')
4555 .attr('data-toggle', 'section');
4556
4557 // Wrap in magic markup.
4558 $section = $section.wrapAll('<div class="dac-toggle dac-mobile">').parent();
4559 $contents.wrapAll('<div class="dac-toggle-content"><div>'); // extra div used for max-height calculation.
4560
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004561 // Pre-expand section if requested.
4562 if ($title.hasClass('is-expanded')) {
4563 $section.addClass('is-expanded');
4564 }
4565
4566 // Pre-expand section if targetted by hash.
4567 if (location.hash && $section.find(location.hash).length) {
4568 $section.addClass('is-expanded');
4569 }
4570
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004571 // Add it back to the dom.
4572 $anchor[anchorMethod].call($anchor, $section);
4573 });
4574 }
4575
4576 $(function() {
4577 initWidget();
4578 });
4579})(jQuery);
4580
Dirk Dougherty29e93432015-05-05 18:17:13 -07004581(function($) {
4582 'use strict';
4583
4584 /**
4585 * Toggle Floating Label state.
4586 * @param {HTMLElement} el - The DOM element.
4587 * @param options
4588 * @constructor
4589 */
4590 function FloatingLabel(el, options) {
4591 this.el = $(el);
4592 this.options = $.extend({}, FloatingLabel.DEFAULTS_, options);
4593 this.group = this.el.closest('.dac-form-input-group');
4594 this.input = this.group.find('.dac-form-input');
4595
4596 this.checkValue_ = this.checkValue_.bind(this);
4597 this.checkValue_();
4598
4599 this.input.on('focus', function() {
4600 this.group.addClass('dac-focused');
4601 }.bind(this));
4602 this.input.on('blur', function() {
4603 this.group.removeClass('dac-focused');
4604 this.checkValue_();
4605 }.bind(this));
4606 this.input.on('keyup', this.checkValue_);
4607 }
4608
4609 /**
4610 * The label is moved out of the textbox when it has a value.
4611 */
4612 FloatingLabel.prototype.checkValue_ = function() {
4613 if (this.input.val().length) {
4614 this.group.addClass('dac-has-value');
4615 } else {
4616 this.group.removeClass('dac-has-value');
4617 }
4618 };
4619
4620 /**
4621 * jQuery plugin
4622 * @param {object} options - Override default options.
4623 */
4624 $.fn.dacFloatingLabel = function(options) {
4625 return this.each(function() {
4626 new FloatingLabel(this, options);
4627 });
4628 };
4629
4630 $(document).on('ready.aranja', function() {
4631 $('.dac-form-floatlabel').each(function() {
4632 $(this).dacFloatingLabel($(this).data());
4633 });
4634 });
4635})(jQuery);
4636
4637/* global toRoot, CAROUSEL_OVERRIDE */
4638(function($) {
4639 // Ordering matters
4640 var TAG_MAP = [
4641 {from: 'developerstory', to: 'Android Developer Story'},
4642 {from: 'googleplay', to: 'Google Play'}
4643 ];
4644
4645 function DacCarouselQuery(el) {
4646 this.el = $(el);
4647
4648 var opts = this.el.data();
4649 opts.maxResults = parseInt(opts.maxResults || '100', 10);
4650 opts.query = opts.carouselQuery;
4651 var resources = $.queryResources(opts);
4652
4653 this.el.empty();
4654 $(resources).map(function() {
4655 var resource = $.extend({}, this, CAROUSEL_OVERRIDE[this.url]);
4656 var slide = $('<article class="dac-expand dac-hero">');
4657 var image = cleanUrl(resource.heroImage || resource.image);
4658 var fullBleed = image && !resource.heroColor;
4659
4660 // Configure background
4661 slide.css({
4662 backgroundImage: fullBleed ? 'url(' + image + ')' : '',
4663 backgroundColor: resource.heroColor || ''
4664 });
4665
4666 // Should copy be inverted
4667 slide.toggleClass('dac-invert', resource.heroInvert || fullBleed);
4668 slide.toggleClass('dac-darken', fullBleed);
4669
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004670 // Should be clickable
4671 slide.append($('<a class="dac-hero-carousel-action">').attr('href', cleanUrl(resource.url)));
4672
Dirk Dougherty29e93432015-05-05 18:17:13 -07004673 var cols = $('<div class="cols dac-hero-content">');
4674
4675 // inline image column
4676 var rightCol = $('<div class="col-1of2 col-push-1of2 dac-hero-figure">')
4677 .appendTo(cols);
4678
4679 if (!fullBleed && image) {
4680 rightCol.append($('<img>').attr('src', image));
4681 }
4682
4683 // info column
4684 $('<div class="col-1of2 col-pull-1of2">')
4685 .append($('<div class="dac-hero-tag">').text(formatTag(resource)))
4686 .append($('<h1 class="dac-hero-title">').text(formatTitle(resource)))
4687 .append($('<p class="dac-hero-description">').text(resource.summary))
4688 .append($('<a class="dac-hero-cta">')
4689 .text(formatCTA(resource))
4690 .attr('href', cleanUrl(resource.url))
4691 .prepend($('<span class="dac-sprite dac-auto-chevron">'))
4692 )
4693 .appendTo(cols);
4694
4695 slide.append(cols.wrap('<div class="wrap">').parent());
4696 return slide[0];
4697 }).prependTo(this.el);
4698
4699 // Pagination element.
4700 this.el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>');
4701
4702 this.el.dacCarousel();
4703 }
4704
4705 function cleanUrl(url) {
4706 if (url && url.indexOf('//') === -1) {
4707 url = toRoot + url;
4708 }
4709 return url;
4710 }
4711
4712 function formatTag(resource) {
4713 // Hmm, need a better more scalable solution for this.
4714 for (var i = 0, mapping; mapping = TAG_MAP[i]; i++) {
4715 if (resource.tags.indexOf(mapping.from) > -1) {
4716 return mapping.to;
4717 }
4718 }
4719 return resource.type;
4720 }
4721
4722 function formatTitle(resource) {
4723 return resource.title.replace(/android developer story: /i, '');
4724 }
4725
4726 function formatCTA(resource) {
4727 return resource.type === 'youtube' ? 'Watch the video' : 'Learn more';
4728 }
4729
4730 // jQuery plugin
4731 $.fn.dacCarouselQuery = function() {
4732 return this.each(function() {
4733 var el = $(this);
4734 var data = el.data('dac.carouselQuery');
4735
4736 if (!data) { el.data('dac.carouselQuery', (data = new DacCarouselQuery(el))); }
4737 });
4738 };
4739
4740 // Data API
4741 $(function() {
4742 $('[data-carousel-query]').dacCarouselQuery();
4743 });
4744})(jQuery);
4745
4746(function($) {
4747 /**
4748 * A CSS based carousel, inspired by SequenceJS.
4749 * @param {jQuery} el
4750 * @param {object} options
4751 * @constructor
4752 */
4753 function DacCarousel(el, options) {
4754 this.el = $(el);
4755 this.options = options = $.extend({}, DacCarousel.OPTIONS, this.el.data(), options || {});
4756 this.frames = this.el.find(options.frameSelector);
4757 this.count = this.frames.size();
4758 this.current = options.start;
4759
4760 this.initPagination();
4761 this.initEvents();
4762 this.initFrame();
4763 }
4764
4765 DacCarousel.OPTIONS = {
4766 auto: true,
4767 autoTime: 10000,
4768 autoMinTime: 5000,
4769 btnPrev: '[data-carousel-prev]',
4770 btnNext: '[data-carousel-next]',
4771 frameSelector: 'article',
4772 loop: true,
4773 start: 0,
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004774 swipeThreshold: 160,
Dirk Dougherty29e93432015-05-05 18:17:13 -07004775 pagination: '[data-carousel-pagination]'
4776 };
4777
4778 DacCarousel.prototype.initPagination = function() {
4779 this.pagination = $([]);
4780 if (!this.options.pagination) { return; }
4781
4782 var pagination = $('<ul class="dac-pagination">');
4783 var parent = this.el;
4784 if (typeof this.options.pagination === 'string') { parent = this.el.find(this.options.pagination); }
4785
4786 if (this.count > 1) {
4787 for (var i = 0; i < this.count; i++) {
4788 var li = $('<li class="dac-pagination-item">').text(i);
4789 if (i === this.options.start) { li.addClass('active'); }
4790 li.click(this.go.bind(this, i));
4791
4792 pagination.append(li);
4793 }
4794 this.pagination = pagination.children();
4795 parent.append(pagination);
4796 }
4797 };
4798
4799 DacCarousel.prototype.initEvents = function() {
4800 var that = this;
4801
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004802 this.touch = {
4803 start: {x: 0, y: 0},
4804 end: {x: 0, y: 0}
4805 };
4806
4807 this.el.on('touchstart', this.touchstart_.bind(this));
4808 this.el.on('touchend', this.touchend_.bind(this));
4809 this.el.on('touchmove', this.touchmove_.bind(this));
4810
Dirk Dougherty29e93432015-05-05 18:17:13 -07004811 this.el.hover(function() {
4812 that.pauseRotateTimer();
4813 }, function() {
4814 that.startRotateTimer();
4815 });
4816
4817 $(this.options.btnPrev).click(function(e) {
4818 e.preventDefault();
4819 that.prev();
4820 });
4821
4822 $(this.options.btnNext).click(function(e) {
4823 e.preventDefault();
4824 that.next();
4825 });
4826 };
4827
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004828 DacCarousel.prototype.touchstart_ = function(event) {
4829 var t = event.originalEvent.touches[0];
4830 this.touch.start = {x: t.screenX, y: t.screenY};
4831 };
4832
4833 DacCarousel.prototype.touchend_ = function() {
4834 var deltaX = this.touch.end.x - this.touch.start.x;
4835 var deltaY = Math.abs(this.touch.end.y - this.touch.start.y);
4836 var shouldSwipe = (deltaY < Math.abs(deltaX)) && (Math.abs(deltaX) >= this.options.swipeThreshold);
4837
4838 if (shouldSwipe) {
4839 if (deltaX > 0) {
4840 this.prev();
4841 } else {
4842 this.next();
4843 }
4844 }
4845 };
4846
4847 DacCarousel.prototype.touchmove_ = function(event) {
4848 var t = event.originalEvent.touches[0];
4849 this.touch.end = {x: t.screenX, y: t.screenY};
4850 };
4851
Dirk Dougherty29e93432015-05-05 18:17:13 -07004852 DacCarousel.prototype.initFrame = function() {
4853 this.frames.removeClass('active').eq(this.options.start).addClass('active');
4854 };
4855
4856 DacCarousel.prototype.startRotateTimer = function() {
4857 if (!this.options.auto || this.rotateTimer) { return; }
4858 this.rotateTimer = setTimeout(this.next.bind(this), this.options.autoTime);
4859 };
4860
4861 DacCarousel.prototype.pauseRotateTimer = function() {
4862 clearTimeout(this.rotateTimer);
4863 this.rotateTimer = null;
4864 };
4865
4866 DacCarousel.prototype.prev = function() {
4867 this.go(this.current - 1);
4868 };
4869
4870 DacCarousel.prototype.next = function() {
4871 this.go(this.current + 1);
4872 };
4873
4874 DacCarousel.prototype.go = function(next) {
4875 // Figure out what the next slide is.
4876 while (this.count > 0 && next >= this.count) { next -= this.count; }
4877 while (next < 0) { next += this.count; }
4878
4879 // Cancel if we're already on that slide.
4880 if (next === this.current) { return; }
4881
4882 // Prepare next slide.
4883 this.frames.eq(next).removeClass('out');
4884
4885 // Recalculate styles before starting slide transition.
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004886 this.el.resolveStyles();
4887 // Update pagination
4888 this.pagination.removeClass('active').eq(next).addClass('active');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004889
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004890 // Transition out current frame
4891 this.frames.eq(this.current).toggleClass('active out');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004892
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004893 // Transition in a new frame
4894 this.frames.eq(next).toggleClass('active');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004895
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004896 this.current = next;
Dirk Dougherty29e93432015-05-05 18:17:13 -07004897 };
4898
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004899 // Helper which resolves new styles for an element, so it can start transitioning
4900 // from the new values.
4901 $.fn.resolveStyles = function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004902 /*jshint expr:true*/
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004903 this[0] && this[0].offsetTop;
4904 return this;
4905 };
Dirk Dougherty29e93432015-05-05 18:17:13 -07004906
4907 // jQuery plugin
4908 $.fn.dacCarousel = function() {
4909 this.each(function() {
4910 var $el = $(this);
4911 $el.data('dac-carousel', new DacCarousel(this));
4912 });
4913 return this;
4914 };
4915
4916 // Data API
4917 $(function() {
4918 $('[data-carousel]').dacCarousel();
4919 });
4920})(jQuery);
4921
4922(function($) {
4923 'use strict';
4924
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004925 function Modal(el, options) {
4926 this.el = $(el);
4927 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
4928 this.isOpen = false;
4929
4930 this.el.on('click', function(event) {
4931 if (!$.contains($('.dac-modal-window')[0], event.target)) {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004932 return this.el.trigger('modal-close');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004933 }
4934 }.bind(this));
4935
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004936 this.el.on('modal-open', this.open_.bind(this));
4937 this.el.on('modal-close', this.close_.bind(this));
4938 this.el.on('modal-toggle', this.toggle_.bind(this));
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004939 }
4940
4941 Modal.prototype.toggle_ = function() {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004942 this.el.trigger('modal-' + (this.isOpen ? 'close' : 'open'));
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004943 };
4944
4945 Modal.prototype.close_ = function() {
4946 this.el.removeClass('dac-active');
4947 $('body').removeClass('dac-modal-open');
4948 this.isOpen = false;
Dirk Doughertyc607a4d2016-01-28 08:32:47 -08004949 // When closing the modal for Android Studio downloads, reload the page
4950 // because otherwise we might get stuck with post-download dialog state
4951 if ($("[data-modal='studio_tos']").length) {
4952 location.reload();
4953 }
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004954 };
4955
4956 Modal.prototype.open_ = function() {
4957 this.el.addClass('dac-active');
4958 $('body').addClass('dac-modal-open');
4959 this.isOpen = true;
4960 };
4961
Dirk Dougherty29e93432015-05-05 18:17:13 -07004962 function ToggleModal(el, options) {
4963 this.el = $(el);
4964 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004965 this.modal = this.options.modalToggle ? $('[data-modal="' + this.options.modalToggle + '"]') :
4966 this.el.closest('[data-modal]');
4967
Dirk Dougherty29e93432015-05-05 18:17:13 -07004968 this.el.on('click', this.clickHandler_.bind(this));
4969 }
4970
Dirk Dougherty29e93432015-05-05 18:17:13 -07004971 ToggleModal.prototype.clickHandler_ = function(event) {
4972 event.preventDefault();
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004973 this.modal.trigger('modal-toggle');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004974 };
4975
4976 /**
4977 * jQuery plugin
4978 * @param {object} options - Override default options.
4979 */
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004980 $.fn.dacModal = function(options) {
4981 return this.each(function() {
4982 new Modal(this, options);
4983 });
4984 };
4985
Dirk Dougherty29e93432015-05-05 18:17:13 -07004986 $.fn.dacToggleModal = function(options) {
4987 return this.each(function() {
4988 new ToggleModal(this, options);
4989 });
4990 };
4991
4992 /**
4993 * Data Attribute API
4994 */
4995 $(document).on('ready.aranja', function() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004996 $('[data-modal]').each(function() {
4997 $(this).dacModal($(this).data());
4998 });
4999
5000 $('[data-modal-toggle]').each(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07005001 $(this).dacToggleModal($(this).data());
5002 });
5003 });
5004})(jQuery);
5005
5006(function($) {
5007 'use strict';
5008
5009 /**
5010 * Toggle the visabilty of the mobile navigation.
5011 * @param {HTMLElement} el - The DOM element.
5012 * @param options
5013 * @constructor
5014 */
5015 function ToggleNav(el, options) {
5016 this.el = $(el);
5017 this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
5018 this.options.target = [this.options.navigation];
5019
5020 if (this.options.body) {this.options.target.push('body')}
5021 if (this.options.dimmer) {this.options.target.push(this.options.dimmer)}
5022
5023 this.el.on('click', this.clickHandler_.bind(this));
5024 }
5025
5026 /**
5027 * ToggleNav Default Settings
5028 * @type {{body: boolean, dimmer: string, navigation: string, toggleClass: string}}
5029 * @private
5030 */
5031 ToggleNav.DEFAULTS_ = {
5032 body: true,
5033 dimmer: '.dac-nav-dimmer',
5034 navigation: '[data-dac-nav]',
5035 toggleClass: 'dac-nav-open'
5036 };
5037
5038 /**
5039 * The actual toggle logic.
5040 * @param event
5041 * @private
5042 */
5043 ToggleNav.prototype.clickHandler_ = function(event) {
5044 event.preventDefault();
5045 $(this.options.target.join(', ')).toggleClass(this.options.toggleClass);
5046 };
5047
5048 /**
5049 * jQuery plugin
5050 * @param {object} options - Override default options.
5051 */
5052 $.fn.dacToggleMobileNav = function(options) {
5053 return this.each(function() {
5054 new ToggleNav(this, options);
5055 });
5056 };
5057
5058 /**
5059 * Data Attribute API
5060 */
5061 $(window).on('load.aranja', function() {
5062 $('[data-dac-toggle-nav]').each(function() {
5063 $(this).dacToggleMobileNav($(this).data());
5064 });
5065 });
5066})(jQuery);
5067
5068(function($) {
5069 'use strict';
5070
5071 /**
5072 * Submit the newsletter form to a Google Form.
5073 * @param {HTMLElement} el - The Form DOM element.
5074 * @constructor
5075 */
5076 function NewsletterForm(el) {
5077 this.el = $(el);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005078 this.form = this.el.find('form');
5079 $('<iframe/>').hide()
5080 .attr('name', 'dac-newsletter-iframe')
5081 .attr('src', '')
5082 .insertBefore(this.form);
5083 this.form.on('submit', this.submitHandler_.bind(this));
Dirk Dougherty29e93432015-05-05 18:17:13 -07005084 }
5085
5086 /**
Dirk Doughertycbe032f2015-05-22 11:41:40 -07005087 * Milliseconds until modal has vanished after modal-close is triggered.
5088 * @type {number}
5089 * @private
5090 */
5091 NewsletterForm.CLOSE_DELAY_ = 300;
5092
5093 /**
5094 * Switch view to display form after close.
5095 * @private
5096 */
5097 NewsletterForm.prototype.closeHandler_ = function() {
5098 setTimeout(function() {
5099 this.el.trigger('swap-reset');
5100 }.bind(this), NewsletterForm.CLOSE_DELAY_);
5101 };
5102
5103 /**
5104 * Reset the modal to initial state.
5105 * @private
5106 */
5107 NewsletterForm.prototype.reset_ = function() {
5108 this.form.trigger('reset');
5109 this.el.one('modal-close', this.closeHandler_.bind(this));
5110 };
5111
5112 /**
5113 * Display a success view on submit.
Dirk Dougherty29e93432015-05-05 18:17:13 -07005114 * @private
5115 */
5116 NewsletterForm.prototype.submitHandler_ = function() {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07005117 this.el.one('swap-complete', this.reset_.bind(this));
5118 this.el.trigger('swap-content');
Dirk Dougherty29e93432015-05-05 18:17:13 -07005119 };
5120
5121 /**
5122 * jQuery plugin
5123 * @param {object} options - Override default options.
5124 */
5125 $.fn.dacNewsletterForm = function(options) {
5126 return this.each(function() {
5127 new NewsletterForm(this, options);
5128 });
5129 };
5130
5131 /**
5132 * Data Attribute API
5133 */
5134 $(document).on('ready.aranja', function() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005135 $('[data-newsletter]').each(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07005136 $(this).dacNewsletterForm();
5137 });
5138 });
5139})(jQuery);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005140
5141(function($) {
5142 'use strict';
5143
5144 /**
5145 * Smoothly scroll to location on current page.
5146 * @param el
5147 * @param options
5148 * @constructor
5149 */
5150 function ScrollButton(el, options) {
5151 this.el = $(el);
5152 this.target = $(this.el.attr('href'));
5153 this.options = $.extend({}, ScrollButton.DEFAULTS_, options);
5154
5155 if (typeof this.options.offset === 'string') {
5156 this.options.offset = $(this.options.offset).height();
5157 }
5158
5159 this.el.on('click', this.clickHandler_.bind(this));
5160 }
5161
5162 /**
5163 * Default options
5164 * @type {{duration: number, easing: string, offset: number, scrollContainer: string}}
5165 * @private
5166 */
5167 ScrollButton.DEFAULTS_ = {
5168 duration: 300,
5169 easing: 'swing',
5170 offset: 0,
5171 scrollContainer: 'html, body'
5172 };
5173
5174 /**
5175 * Scroll logic
5176 * @param event
5177 * @private
5178 */
5179 ScrollButton.prototype.clickHandler_ = function(event) {
5180 if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
5181 return;
5182 }
5183
5184 event.preventDefault();
5185
5186 $(this.options.scrollContainer).animate({
5187 scrollTop: this.target.offset().top - this.options.offset
5188 }, this.options);
5189 };
5190
5191 /**
5192 * jQuery plugin
5193 * @param {object} options - Override default options.
5194 */
5195 $.fn.dacScrollButton = function(options) {
5196 return this.each(function() {
5197 new ScrollButton(this, options);
5198 });
5199 };
5200
5201 /**
5202 * Data Attribute API
5203 */
5204 $(document).on('ready.aranja', function() {
5205 $('[data-scroll-button]').each(function() {
5206 $(this).dacScrollButton($(this).data());
5207 });
5208 });
5209})(jQuery);
5210
5211(function($) {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07005212 'use strict';
5213
5214 /**
5215 * A component that swaps two dynamic height views with an animation.
5216 * Listens for the following events:
5217 * * swap-content: triggers SwapContent.swap_()
5218 * * swap-reset: triggers SwapContent.reset()
5219 * @param el
5220 * @param options
5221 * @constructor
5222 */
5223 function SwapContent(el, options) {
5224 this.el = $(el);
5225 this.options = $.extend({}, SwapContent.DEFAULTS_, options);
5226 this.containers = this.el.find(this.options.container);
5227 this.initiallyActive = this.containers.children('.' + this.options.activeClass).eq(0);
5228 this.el.on('swap-content', this.swap.bind(this));
5229 this.el.on('swap-reset', this.reset.bind(this));
5230 }
5231
5232 /**
5233 * SwapContent's default settings.
5234 * @type {{activeClass: string, container: string, transitionSpeed: number}}
5235 * @private
5236 */
5237 SwapContent.DEFAULTS_ = {
5238 activeClass: 'dac-active',
5239 container: '[data-swap-container]',
5240 transitionSpeed: 500
5241 };
5242
5243 /**
5244 * Returns container's visible height.
5245 * @param container
5246 * @returns {number}
5247 */
5248 SwapContent.prototype.currentHeight = function(container) {
5249 return container.children('.' + this.options.activeClass).outerHeight();
5250 };
5251
5252 /**
5253 * Reset to show initial content
5254 */
5255 SwapContent.prototype.reset = function() {
5256 if (!this.initiallyActive.hasClass(this.initiallyActive)) {
5257 this.containers.children().toggleClass(this.options.activeClass);
5258 }
5259 };
5260
5261 /**
5262 * Complete the swap.
5263 */
5264 SwapContent.prototype.complete = function() {
5265 this.containers.height('auto');
5266 this.containers.trigger('swap-complete');
5267 };
5268
5269 /**
5270 * Perform the swap of content.
5271 */
5272 SwapContent.prototype.swap = function() {
5273 console.log(this.containers);
5274 this.containers.each(function(index, container) {
5275 container = $(container);
5276 container.height(this.currentHeight(container)).children().toggleClass(this.options.activeClass);
5277 container.animate({height: this.currentHeight(container)}, this.options.transitionSpeed,
5278 this.complete.bind(this));
5279 }.bind(this));
5280 };
5281
5282 /**
5283 * jQuery plugin
5284 * @param {object} options - Override default options.
5285 */
5286 $.fn.dacSwapContent = function(options) {
5287 return this.each(function() {
5288 new SwapContent(this, options);
5289 });
5290 };
5291
5292 /**
5293 * Data Attribute API
5294 */
5295 $(document).on('ready.aranja', function() {
5296 $('[data-swap]').each(function() {
5297 $(this).dacSwapContent($(this).data());
5298 });
5299 });
5300})(jQuery);
5301
5302(function($) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005303 function Toggle(el) {
5304 $(el).on('click.dac.togglesection', this.toggle);
5305 }
5306
5307 Toggle.prototype.toggle = function() {
5308 var $this = $(this);
5309
5310 var $parent = getParent($this);
5311 var isExpanded = $parent.hasClass('is-expanded');
5312
5313 transitionMaxHeight($parent.find('.dac-toggle-content'), !isExpanded);
5314 $parent.toggleClass('is-expanded');
5315
5316 return false;
5317 };
5318
5319 function getParent($this) {
5320 var selector = $this.attr('data-target');
5321
5322 if (!selector) {
5323 selector = $this.attr('href');
5324 selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '');
5325 }
5326
5327 var $parent = selector && $(selector);
5328
Dirk Doughertycbe032f2015-05-22 11:41:40 -07005329 $parent = $parent && $parent.length ? $parent : $this.closest('.dac-toggle');
5330
5331 return $parent.length ? $parent : $this.parent();
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005332 }
5333
5334 /**
5335 * Runs a transition of max-height along with responsive styles which hide or expand the element.
5336 * @param $el
5337 * @param visible
5338 */
5339 function transitionMaxHeight($el, visible) {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07005340 var contentHeight = $el.prop('scrollHeight');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005341 var targetHeight = visible ? contentHeight : 0;
5342 var duration = $el.transitionDuration();
5343
5344 // If we're hiding, first set the maxHeight we're transitioning from.
5345 if (!visible) {
5346 $el.css('maxHeight', contentHeight + 'px')
5347 .resolveStyles();
5348 }
5349
5350 // Transition to new state
5351 $el.css('maxHeight', targetHeight);
5352
5353 // Reset maxHeight to css value after transition.
5354 setTimeout(function() {
5355 $el.css('maxHeight', '');
5356 }, duration);
5357 }
5358
5359 // Utility to get the transition duration for the element.
5360 $.fn.transitionDuration = function() {
5361 var d = $(this).css('transitionDuration') || '0s';
5362
5363 return +(parseFloat(d) * (/ms/.test(d) ? 1 : 1000)).toFixed(0);
5364 };
5365
5366 // jQuery plugin
5367 $.fn.toggleSection = function(option) {
5368 return this.each(function() {
5369 var $this = $(this);
5370 var data = $this.data('dac.togglesection');
5371 if (!data) {$this.data('dac.togglesection', (data = new Toggle(this)));}
5372 if (typeof option === 'string') {data[option].call($this);}
5373 });
5374 };
5375
5376 // Data api
5377 $(document)
5378 .on('click.toggle', '[data-toggle="section"]', Toggle.prototype.toggle);
5379})(jQuery);
smain@google.comf887aec2016-04-28 16:54:06 -07005380
5381
5382var STUDIO_SURVEY_CLICKED = 'studio-survey-20160429-clicked';
5383
5384function onClickStudioSurvey() {
5385 localStorage.setItem(STUDIO_SURVEY_CLICKED, 'true');
5386 $("#studio-survey-button").fadeOut();
5387}
5388
5389function showStudioSurveyButton() {
5390 if (localStorage.getItem(STUDIO_SURVEY_CLICKED) == null) {
5391 $("#studio-survey-button").show();
5392 }
5393}