blob: 2efe3dc657c5a51aad42e9abc802e3a07f2876fb [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
Dirk Doughertyb87e3002014-11-18 19:34:34 -080024 // show lang dialog if the URL includes /intl/
25 //if (location.pathname.substring(0,6) == "/intl/") {
26 // var lang = location.pathname.split('/')[2];
27 // if (lang != getLangPref()) {
28 // $("#langMessage a.yes").attr("onclick","changeLangPref('" + lang
29 // + "', true); $('#langMessage').hide(); return false;");
30 // $("#langMessage .lang." + lang).show();
31 // $("#langMessage").show();
32 // }
33 //}
Scott Main7e447ed2013-02-19 17:22:37 -080034
Scott Main0e76e7e2013-03-12 10:24:07 -070035 // load json file for JD doc search suggestions
Scott Main719acb42013-12-05 16:05:09 -080036 $.getScript(toRoot + 'jd_lists_unified.js');
Scott Main7e447ed2013-02-19 17:22:37 -080037 // load json file for Android API search suggestions
38 $.getScript(toRoot + 'reference/lists.js');
39 // load json files for Google services API suggestions
Scott Main9f2971d2013-02-26 13:07:41 -080040 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080041 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
42 if(jqxhr.status === 200) {
Scott Main9f2971d2013-02-26 13:07:41 -080043 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080044 if(jqxhr.status === 200) {
45 // combine GCM and GMS data
46 GOOGLE_DATA = GMS_DATA;
47 var start = GOOGLE_DATA.length;
48 for (var i=0; i<GCM_DATA.length; i++) {
49 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
50 link:GCM_DATA[i].link, type:GCM_DATA[i].type});
51 }
52 }
53 });
54 }
55 });
56
Scott Main0e76e7e2013-03-12 10:24:07 -070057 // setup keyboard listener for search shortcut
58 $('body').keyup(function(event) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -070059 if (event.which == 191 && $(event.target).is(':not(:input)')) {
Scott Main0e76e7e2013-03-12 10:24:07 -070060 $('#search_autocomplete').focus();
61 }
62 });
Scott Main015d6162013-01-29 09:01:52 -080063
Scott Maine4d8f1b2012-06-21 18:03:05 -070064 // init the fullscreen toggle click event
65 $('#nav-swap .fullscreen').click(function(){
66 if ($(this).hasClass('disabled')) {
67 toggleFullscreen(true);
68 } else {
69 toggleFullscreen(false);
70 }
71 });
Scott Main3b90aff2013-08-01 18:09:35 -070072
Scott Maine4d8f1b2012-06-21 18:03:05 -070073 // initialize the divs with custom scrollbars
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -070074 if (window.innerWidth >= 720) {
75 $('.scroll-pane').jScrollPane({verticalGutter: 0});
76 }
Scott Maine4d8f1b2012-06-21 18:03:05 -070077
78 // set up the search close button
Dirk Dougherty29e93432015-05-05 18:17:13 -070079 $('#search-close').click(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -070080 $searchInput = $('#search_autocomplete');
81 $searchInput.attr('value', '');
82 $(this).addClass("hide");
83 $("#search-container").removeClass('active');
84 $("#search_autocomplete").blur();
Scott Main0e76e7e2013-03-12 10:24:07 -070085 search_focus_changed($searchInput.get(), false);
86 hideResults();
Scott Maine4d8f1b2012-06-21 18:03:05 -070087 });
88
Scott Main3b90aff2013-08-01 18:09:35 -070089
Scott Maine4d8f1b2012-06-21 18:03:05 -070090 //Set up search
91 $("#search_autocomplete").focus(function() {
92 $("#search-container").addClass('active');
93 })
94 $("#search-container").mouseover(function() {
95 $("#search-container").addClass('active');
96 $("#search_autocomplete").focus();
97 })
98 $("#search-container").mouseout(function() {
99 if ($("#search_autocomplete").is(":focus")) return;
100 if ($("#search_autocomplete").val() == '') {
101 setTimeout(function(){
102 $("#search-container").removeClass('active');
103 $("#search_autocomplete").blur();
104 },250);
105 }
106 })
107 $("#search_autocomplete").blur(function() {
108 if ($("#search_autocomplete").val() == '') {
109 $("#search-container").removeClass('active');
110 }
111 })
112
Scott Main3b90aff2013-08-01 18:09:35 -0700113
Scott Maine4d8f1b2012-06-21 18:03:05 -0700114 // prep nav expandos
115 var pagePath = document.location.pathname;
116 // account for intl docs by removing the intl/*/ path
117 if (pagePath.indexOf("/intl/") == 0) {
118 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
119 }
Scott Mainac2aef52013-02-12 14:15:23 -0800120
Scott Maine4d8f1b2012-06-21 18:03:05 -0700121 if (pagePath.indexOf(SITE_ROOT) == 0) {
122 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
123 pagePath += 'index.html';
124 }
125 }
126
Scott Main01a25452013-02-12 17:32:27 -0800127 // Need a copy of the pagePath before it gets changed in the next block;
128 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
129 var pagePathOriginal = pagePath;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700130 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
131 // If running locally, SITE_ROOT will be a relative path, so account for that by
132 // finding the relative URL to this page. This will allow us to find links on the page
133 // leading back to this page.
134 var pathParts = pagePath.split('/');
135 var relativePagePathParts = [];
136 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
137 for (var i = 0; i < upDirs; i++) {
138 relativePagePathParts.push('..');
139 }
140 for (var i = 0; i < upDirs; i++) {
141 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
142 }
143 relativePagePathParts.push(pathParts[pathParts.length - 1]);
144 pagePath = relativePagePathParts.join('/');
145 } else {
146 // Otherwise the page path is already an absolute URL
147 }
148
Scott Mainac2aef52013-02-12 14:15:23 -0800149 // Highlight the header tabs...
150 // highlight Design tab
Dirk Dougherty29e93432015-05-05 18:17:13 -0700151 var urlSegments = pagePathOriginal.split('/');
152 var navEl = $(".dac-nav-list");
153 var subNavEl = navEl.find(".dac-nav-secondary");
154 var parentNavEl;
Scott Mainac2aef52013-02-12 14:15:23 -0800155
Dirk Dougherty29e93432015-05-05 18:17:13 -0700156 if ($("body").hasClass("design")) {
157 navEl.find("> li.design > a").addClass("selected");
smain@google.com6040ffa2014-06-13 15:06:23 -0700158 // highlight About tabs
159 } else if ($("body").hasClass("about")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700160 if (urlSegments[1] == "about" || urlSegments[1] == "wear" || urlSegments[1] == "tv" || urlSegments[1] == "auto") {
161 navEl.find("> li.home > a").addClass('has-subnav');
162 subNavEl.find("li." + urlSegments[1] + " > a").addClass("selected");
163 } else {
164 navEl.find("> li.home > a").addClass('selected');
smain@google.com6040ffa2014-06-13 15:06:23 -0700165 }
Scott Mainac2aef52013-02-12 14:15:23 -0800166 // highlight Develop tab
167 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700168 parentNavEl = navEl.find("> li.develop > a");
169 parentNavEl.addClass('has-subnav');
170
Scott Mainac2aef52013-02-12 14:15:23 -0800171 // In Develop docs, also highlight appropriate sub-tab
Dirk Dougherty29e93432015-05-05 18:17:13 -0700172 if (urlSegments[1] == "training") {
173 subNavEl.find("li.training > a").addClass("selected");
174 } else if (urlSegments[1] == "guide") {
175 subNavEl.find("li.guide > a").addClass("selected");
176 } else if (urlSegments[1] == "reference") {
Scott Mainac2aef52013-02-12 14:15:23 -0800177 // If the root is reference, but page is also part of Google Services, select Google
178 if ($("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700179 subNavEl.find("li.google > a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800180 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700181 subNavEl.find("li.reference > a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800182 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700183 } else if ((urlSegments[1] == "tools") || (urlSegments[1] == "sdk")) {
184 subNavEl.find("li.tools > a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800185 } else if ($("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700186 subNavEl.find("li.google > a").addClass("selected");
Dirk Dougherty4f7e5152010-09-16 10:43:40 -0700187 } else if ($("body").hasClass("samples")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700188 subNavEl.find("li.samples > a").addClass("selected");
Dirk Doughertya8bbfca2015-05-14 22:58:28 -0700189 } else if ($("body").hasClass("preview")) {
190 subNavEl.find("li.preview > a").addClass("selected");
Dirk Dougherty29e93432015-05-05 18:17:13 -0700191 } else {
192 parentNavEl.removeClass('has-subnav').addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800193 }
Scott Mainac2aef52013-02-12 14:15:23 -0800194 // highlight Distribute tab
195 } else if ($("body").hasClass("distribute")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700196 parentNavEl = navEl.find("> li.distribute > a");
197 parentNavEl.addClass('has-subnav');
Dirk Doughertyc3921652014-05-13 16:55:26 -0700198
Dirk Dougherty29e93432015-05-05 18:17:13 -0700199 if (urlSegments[2] == "users") {
200 subNavEl.find("li.users > a").addClass("selected");
201 } else if (urlSegments[2] == "engage") {
202 subNavEl.find("li.engage > a").addClass("selected");
203 } else if (urlSegments[2] == "monetize") {
204 subNavEl.find("li.monetize > a").addClass("selected");
205 } else if (urlSegments[2] == "analyze") {
206 subNavEl.find("li.analyze > a").addClass("selected");
207 } else if (urlSegments[2] == "tools") {
208 subNavEl.find("li.disttools > a").addClass("selected");
209 } else if (urlSegments[2] == "stories") {
210 subNavEl.find("li.stories > a").addClass("selected");
211 } else if (urlSegments[2] == "essentials") {
212 subNavEl.find("li.essentials > a").addClass("selected");
213 } else if (urlSegments[2] == "googleplay") {
214 subNavEl.find("li.googleplay > a").addClass("selected");
215 } else {
216 parentNavEl.removeClass('has-subnav').addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700217 }
Scott Mainb16376f2014-05-21 20:35:47 -0700218 }
Scott Mainac2aef52013-02-12 14:15:23 -0800219
Scott Mainf6145542013-04-01 16:38:11 -0700220 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
221 // and highlight the sidenav
222 mPagePath = pagePath;
223 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700224 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800225
Scott Mainf6145542013-04-01 16:38:11 -0700226 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700227 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700228 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700229 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800230 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700231
232 // set up prev links
233 var $prevLink = [];
234 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700235
Scott Maine4d8f1b2012-06-21 18:03:05 -0700236 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
237false; // navigate across topic boundaries only in design docs
238 if ($prevListItem.length) {
smain@google.comc91ecb72014-06-23 10:22:23 -0700239 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Scott Main5a1123e2012-09-26 12:51:28 -0700240 // jump to last topic of previous section
241 $prevLink = $prevListItem.find('a:last');
242 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700243 // jump to previous topic in this section
244 $prevLink = $prevListItem.find('a:eq(0)');
245 }
246 } else {
247 // jump to this section's index page (if it exists)
248 var $parentListItem = $selListItem.parents('li');
249 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700250
Scott Maine4d8f1b2012-06-21 18:03:05 -0700251 // except if cross boundaries aren't allowed, and we're at the top of a section already
252 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700253 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700254 && $selListItem.hasClass('nav-section')) {
255 $prevLink = [];
256 }
257 }
258
Scott Maine4d8f1b2012-06-21 18:03:05 -0700259 // set up next links
260 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700261 var startClass = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700262 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700263
Scott Main1a00f7f2013-10-29 11:11:19 -0700264 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700265 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700266 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700267
268 // if there aren't any children, go to the next section (required for About pages)
269 if($nextLink.length == 0) {
270 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700271 } else if ($('.topic-start-link').length) {
272 // as long as there's a child link and there is a "topic start link" (we're on a landing)
273 // then set the landing page "start link" text to be the first doc title
274 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700275 }
Scott Main3b90aff2013-08-01 18:09:35 -0700276
Scott Main5a1123e2012-09-26 12:51:28 -0700277 // If the selected page has a description, then it's a class or article homepage
278 if ($selListItem.find('a[description]').length) {
279 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700280 startClass = true;
281 }
282 } else {
283 // jump to the next topic in this section (if it exists)
284 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700285 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700286 isCrossingBoundary = true;
287 // no more topics in this section, jump to the first topic in the next section
smain@google.comabf34112014-06-23 11:39:02 -0700288 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Scott Main5a1123e2012-09-26 12:51:28 -0700289 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
290 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700291 if ($nextLink.length == 0) {
292 // if that doesn't work, we're at the end of the list, so disable NEXT link
293 $('.next-page-link').attr('href','').addClass("disabled")
294 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700295 // and completely hide the one in the footer
296 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700297 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700298 }
299 }
300 }
Scott Main5a1123e2012-09-26 12:51:28 -0700301
302 if (startClass) {
303 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
304
Scott Main3b90aff2013-08-01 18:09:35 -0700305 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700306 // then we need to add a bottom border to button
307 if (!$("#tb").length) {
308 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700309 }
Scott Main5a1123e2012-09-26 12:51:28 -0700310 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
311 $('.content-footer.next-class').show();
312 $('.next-page-link').attr('href','')
313 .removeClass("hide").addClass("disabled")
314 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700315 // and completely hide the one in the footer
316 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700317 if ($nextLink.length) {
318 $('.next-class-link').attr('href',$nextLink.attr('href'))
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700319 .removeClass("hide")
320 .append(": " + $nextLink.html());
Scott Main1a00f7f2013-10-29 11:11:19 -0700321 $('.next-class-link').find('.new').empty();
322 }
Scott Main5a1123e2012-09-26 12:51:28 -0700323 } else {
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700324 $('.next-page-link').attr('href', $nextLink.attr('href'))
325 .removeClass("hide");
326 // for the footer link, also add the next page title
327 $('.content-footer .next-page-link').append(": " + $nextLink.html());
Scott Main5a1123e2012-09-26 12:51:28 -0700328 }
329
330 if (!startClass && $prevLink.length) {
331 var prevHref = $prevLink.attr('href');
332 if (prevHref == SITE_ROOT + 'index.html') {
333 // Don't show Previous when it leads to the homepage
334 } else {
335 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
336 }
Scott Main3b90aff2013-08-01 18:09:35 -0700337 }
Scott Main5a1123e2012-09-26 12:51:28 -0700338
Scott Maine4d8f1b2012-06-21 18:03:05 -0700339 }
Scott Main3b90aff2013-08-01 18:09:35 -0700340
341
342
Scott Main5a1123e2012-09-26 12:51:28 -0700343 // Set up the course landing pages for Training with class names and descriptions
344 if ($('body.trainingcourse').length) {
345 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700346
347 // create an array for all the class descriptions
348 var $classDescriptions = new Array($classLinks.length);
349 var lang = getLangPref();
350 $classLinks.each(function(index) {
351 var langDescr = $(this).attr(lang + "-description");
352 if (typeof langDescr !== 'undefined' && langDescr !== false) {
353 // if there's a class description in the selected language, use that
354 $classDescriptions[index] = langDescr;
355 } else {
356 // otherwise, use the default english description
357 $classDescriptions[index] = $(this).attr("description");
358 }
359 });
Scott Main3b90aff2013-08-01 18:09:35 -0700360
Scott Main5a1123e2012-09-26 12:51:28 -0700361 var $olClasses = $('<ol class="class-list"></ol>');
362 var $liClass;
Scott Main5a1123e2012-09-26 12:51:28 -0700363 var $h2Title;
364 var $pSummary;
365 var $olLessons;
366 var $liLesson;
367 $classLinks.each(function(index) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700368 $liClass = $('<li class="clearfix"></li>');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700369 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2 class="norule">' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700370 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700371
Scott Main5a1123e2012-09-26 12:51:28 -0700372 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700373
Scott Main5a1123e2012-09-26 12:51:28 -0700374 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700375
Scott Main5a1123e2012-09-26 12:51:28 -0700376 if ($lessons.length) {
Scott Main5a1123e2012-09-26 12:51:28 -0700377 $lessons.each(function(index) {
378 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
379 });
380 } else {
Scott Main5a1123e2012-09-26 12:51:28 -0700381 $pSummary.addClass('article');
382 }
383
Dirk Dougherty29e93432015-05-05 18:17:13 -0700384 $liClass.append($h2Title).append($pSummary).append($olLessons);
Scott Main5a1123e2012-09-26 12:51:28 -0700385 $olClasses.append($liClass);
386 });
387 $('.jd-descr').append($olClasses);
388 }
389
Scott Maine4d8f1b2012-06-21 18:03:05 -0700390 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700391 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700392
Scott Main3b90aff2013-08-01 18:09:35 -0700393
Scott Maine4d8f1b2012-06-21 18:03:05 -0700394 $(".scroll-pane").scroll(function(event) {
395 event.preventDefault();
396 return false;
397 });
398
399 /* Resize nav height when window height changes */
400 $(window).resize(function() {
401 if ($('#side-nav').length == 0) return;
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700402 setNavBarDimensions(); // do this even if sidenav isn't fixed because it could become fixed
Scott Maine4d8f1b2012-06-21 18:03:05 -0700403 // make sidenav behave when resizing the window and side-scolling is a concern
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700404 updateSideNavDimensions();
405 checkSticky();
406 resizeNav(250);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700407 });
408
Scott Maine4d8f1b2012-06-21 18:03:05 -0700409 if ($('#devdoc-nav').length) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700410 setNavBarDimensions();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700411 }
412
413
Scott Maine4d8f1b2012-06-21 18:03:05 -0700414 // Set up play-on-hover <video> tags.
415 $('video.play-on-hover').bind('click', function(){
416 $(this).get(0).load(); // in case the video isn't seekable
417 $(this).get(0).play();
418 });
419
420 // Set up tooltips
421 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700422 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700423 var $target = $(this);
424 var $tooltip = $('<div>')
425 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700426 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700427 .hide()
428 .appendTo('body');
429 $target.removeAttr('title');
430
431 $target.hover(function() {
432 // in
433 var targetRect = $target.offset();
434 targetRect.width = $target.width();
435 targetRect.height = $target.height();
436
437 $tooltip.css({
438 left: targetRect.left,
439 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
440 });
441 $tooltip.addClass('below');
442 $tooltip.show();
443 }, function() {
444 // out
445 $tooltip.hide();
446 });
447 });
448
449 // Set up <h2> deeplinks
450 $('h2').click(function() {
451 var id = $(this).attr('id');
452 if (id) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700453 if (history && history.replaceState) {
454 // Change url without scrolling.
455 history.replaceState({}, '', '#' + id);
456 } else {
457 document.location.hash = id;
458 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700459 }
460 });
461
462 //Loads the +1 button
463 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
464 po.src = 'https://apis.google.com/js/plusone.js';
465 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
466
Scott Maine4d8f1b2012-06-21 18:03:05 -0700467 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700468
Scott Maine4d8f1b2012-06-21 18:03:05 -0700469 if ($(".scroll-pane").length > 1) {
470 // Check if there's a user preference for the panel heights
471 var cookieHeight = readCookie("reference_height");
472 if (cookieHeight) {
473 restoreHeight(cookieHeight);
474 }
475 }
Scott Main3b90aff2013-08-01 18:09:35 -0700476
Scott Main06f3f2c2014-05-30 11:23:00 -0700477 // Resize once loading is finished
Scott Maine4d8f1b2012-06-21 18:03:05 -0700478 resizeNav();
Scott Main06f3f2c2014-05-30 11:23:00 -0700479 // Check if there's an anchor that we need to scroll into view.
480 // A delay is needed, because some browsers do not immediately scroll down to the anchor
481 window.setTimeout(offsetScrollForSticky, 100);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700482
Scott Main015d6162013-01-29 09:01:52 -0800483 /* init the language selector based on user cookie for lang */
484 loadLangPref();
485 changeNavLang(getLangPref());
486
487 /* setup event handlers to ensure the overflow menu is visible while picking lang */
488 $("#language select")
489 .mousedown(function() {
490 $("div.morehover").addClass("hover"); })
491 .blur(function() {
492 $("div.morehover").removeClass("hover"); });
493
494 /* some global variable setup */
495 resizePackagesNav = $("#resize-packages-nav");
496 classesNav = $("#classes-nav");
497 devdocNav = $("#devdoc-nav");
498
499 var cookiePath = "";
500 if (location.href.indexOf("/reference/") != -1) {
501 cookiePath = "reference_";
502 } else if (location.href.indexOf("/guide/") != -1) {
503 cookiePath = "guide_";
504 } else if (location.href.indexOf("/tools/") != -1) {
505 cookiePath = "tools_";
506 } else if (location.href.indexOf("/training/") != -1) {
507 cookiePath = "training_";
508 } else if (location.href.indexOf("/design/") != -1) {
509 cookiePath = "design_";
510 } else if (location.href.indexOf("/distribute/") != -1) {
511 cookiePath = "distribute_";
512 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700513
smain@google.com698fff02014-11-20 20:39:33 -0800514
515 /* setup shadowbox for any videos that want it */
smain@google.comf75ee212014-11-24 09:42:59 -0800516 var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
smain@google.com698fff02014-11-20 20:39:33 -0800517 if ($videoLinks.length) {
518 // if there's at least one, add the shadowbox HTML to the body
519 $('body').prepend(
520'<div id="video-container">'+
521 '<div id="video-frame">'+
522 '<div class="video-close">'+
523 '<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
524 '</div>'+
525 '<div id="youTubePlayer"></div>'+
526 '</div>'+
527'</div>');
528
529 // loads the IFrame Player API code asynchronously.
530 $.getScript("https://www.youtube.com/iframe_api");
531
532 $videoLinks.each(function() {
533 var videoId = $(this).attr('href').split('?v=')[1];
534 $(this).click(function(event) {
535 event.preventDefault();
536 startYouTubePlayer(videoId);
537 });
538 });
smain@google.com698fff02014-11-20 20:39:33 -0800539 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700540});
Scott Main7e447ed2013-02-19 17:22:37 -0800541// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700542
543
smain@google.com698fff02014-11-20 20:39:33 -0800544var youTubePlayer;
545function onYouTubeIframeAPIReady() {
546}
547
smain@google.com3de83c12014-12-12 19:06:52 -0800548/* Returns the height the shadowbox video should be. It's based on the current
549 height of the "video-frame" element, which is 100% height for the window.
550 Then minus the margin so the video isn't actually the full window height. */
551function getVideoHeight() {
552 var frameHeight = $("#video-frame").height();
553 var marginTop = $("#video-frame").css('margin-top').split('px')[0];
554 return frameHeight - (marginTop * 2);
555}
556
smain@google.comd162be52015-02-05 13:27:16 -0800557var mPlayerPaused = false;
558
smain@google.com698fff02014-11-20 20:39:33 -0800559function startYouTubePlayer(videoId) {
smain@google.com3de83c12014-12-12 19:06:52 -0800560 $("#video-container").show();
561 $("#video-frame").show();
smain@google.comd162be52015-02-05 13:27:16 -0800562 mPlayerPaused = false;
smain@google.com3de83c12014-12-12 19:06:52 -0800563
564 // compute the size of the player so it's centered in window
565 var maxWidth = 940; // the width of the web site content
566 var videoAspect = .5625; // based on 1280x720 resolution
567 var maxHeight = maxWidth * videoAspect;
568 var videoHeight = getVideoHeight();
569 var videoWidth = videoHeight / videoAspect;
570 if (videoWidth > maxWidth) {
571 videoWidth = maxWidth;
572 videoHeight = maxHeight;
smain@google.com570c2212014-11-25 19:01:40 -0800573 }
smain@google.com3de83c12014-12-12 19:06:52 -0800574 $("#video-frame").css('width', videoWidth);
575
576 // check if we've already created this player
smain@google.com698fff02014-11-20 20:39:33 -0800577 if (youTubePlayer == null) {
smain@google.com3de83c12014-12-12 19:06:52 -0800578 // check if there's a start time specified
579 var idAndHash = videoId.split("#");
580 var startTime = 0;
581 if (idAndHash.length > 1) {
582 startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0;
583 }
584 // enable localized player
585 var lang = getLangPref();
586 var captionsOn = lang == 'en' ? 0 : 1;
587
smain@google.com698fff02014-11-20 20:39:33 -0800588 youTubePlayer = new YT.Player('youTubePlayer', {
smain@google.com3de83c12014-12-12 19:06:52 -0800589 height: videoHeight,
590 width: videoWidth,
smain@google.com570c2212014-11-25 19:01:40 -0800591 videoId: idAndHash[0],
smain@google.com481f15c2014-12-12 11:57:27 -0800592 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
smain@google.com698fff02014-11-20 20:39:33 -0800593 events: {
smain@google.comf75ee212014-11-24 09:42:59 -0800594 'onReady': onPlayerReady,
595 'onStateChange': onPlayerStateChange
smain@google.com698fff02014-11-20 20:39:33 -0800596 }
597 });
598 } else {
smain@google.com3de83c12014-12-12 19:06:52 -0800599 // reset the size in case the user adjusted the window since last play
600 youTubePlayer.setSize(videoWidth, videoHeight);
smain@google.com94e0b532014-12-16 19:07:08 -0800601 // if a video different from the one already playing was requested, cue it up
smain@google.comd162be52015-02-05 13:27:16 -0800602 if (videoId != youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]) {
smain@google.com94e0b532014-12-16 19:07:08 -0800603 youTubePlayer.cueVideoById(videoId);
604 }
smain@google.com698fff02014-11-20 20:39:33 -0800605 youTubePlayer.playVideo();
606 }
smain@google.com698fff02014-11-20 20:39:33 -0800607}
608
609function onPlayerReady(event) {
610 event.target.playVideo();
smain@google.comd162be52015-02-05 13:27:16 -0800611 mPlayerPaused = false;
smain@google.com698fff02014-11-20 20:39:33 -0800612}
613
614function closeVideo() {
615 try {
smain@google.comf75ee212014-11-24 09:42:59 -0800616 youTubePlayer.pauseVideo();
smain@google.com698fff02014-11-20 20:39:33 -0800617 } catch(e) {
smain@google.com698fff02014-11-20 20:39:33 -0800618 }
smain@google.com3de83c12014-12-12 19:06:52 -0800619 $("#video-container").fadeOut(200);
smain@google.com698fff02014-11-20 20:39:33 -0800620}
621
smain@google.comf75ee212014-11-24 09:42:59 -0800622/* Track youtube playback for analytics */
623function onPlayerStateChange(event) {
624 // Video starts, send the video ID
625 if (event.data == YT.PlayerState.PLAYING) {
smain@google.comd162be52015-02-05 13:27:16 -0800626 if (mPlayerPaused) {
627 ga('send', 'event', 'Videos', 'Resume',
628 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]);
629 } else {
630 // track the start playing event so we know from which page the video was selected
631 ga('send', 'event', 'Videos', 'Start: ' +
632 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
633 'on: ' + document.location.href);
634 }
635 mPlayerPaused = false;
smain@google.comf75ee212014-11-24 09:42:59 -0800636 }
637 // Video paused, send video ID and video elapsed time
638 if (event.data == YT.PlayerState.PAUSED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800639 ga('send', 'event', 'Videos', 'Paused',
smain@google.comd162be52015-02-05 13:27:16 -0800640 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
641 youTubePlayer.getCurrentTime());
642 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800643 }
644 // Video finished, send video ID and video elapsed time
645 if (event.data == YT.PlayerState.ENDED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800646 ga('send', 'event', 'Videos', 'Finished',
smain@google.comd162be52015-02-05 13:27:16 -0800647 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
648 youTubePlayer.getCurrentTime());
649 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800650 }
651}
652
smain@google.com698fff02014-11-20 20:39:33 -0800653
654
Scott Mainad08f072013-08-20 16:49:57 -0700655function initExpandableNavItems(rootTag) {
656 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
657 var section = $(this).closest('li.nav-section');
658 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700659 /* hide me and descendants */
660 section.find('ul').slideUp(250, function() {
661 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700662 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700663 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700664 resizeNav();
665 });
666 } else {
667 /* show me */
668 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700669 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700670 $others.removeClass('expanded').children('ul').slideUp(250);
671
672 // now expand me
673 section.closest('li').addClass('expanded');
674 section.children('ul').slideDown(250, function() {
675 resizeNav();
676 });
677 }
678 });
Scott Mainf0093852013-08-22 11:37:11 -0700679
680 // Stop expand/collapse behavior when clicking on nav section links
681 // (since we're navigating away from the page)
682 // This selector captures the first instance of <a>, but not those with "#" as the href.
683 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
684 window.location.href = $(this).attr('href');
685 return false;
686 });
Scott Mainad08f072013-08-20 16:49:57 -0700687}
688
Dirk Doughertyc3921652014-05-13 16:55:26 -0700689
690/** Create the list of breadcrumb links in the sticky header */
691function buildBreadcrumbs() {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700692 var $breadcrumbUl = $(".dac-header-crumbs");
693 var primaryNavLink = ".dac-nav-list > .dac-nav-item > .dac-nav-link";
694
Dirk Doughertyc3921652014-05-13 16:55:26 -0700695 // Add the secondary horizontal nav item, if provided
Dirk Dougherty29e93432015-05-05 18:17:13 -0700696 var $selectedSecondNav = $(".dac-nav-secondary .dac-nav-link.selected").clone()
697 .attr('class', 'dac-header-crumbs-link');
698
Dirk Doughertyc3921652014-05-13 16:55:26 -0700699 if ($selectedSecondNav.length) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700700 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedSecondNav));
Dirk Doughertyc3921652014-05-13 16:55:26 -0700701 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700702
Dirk Doughertyc3921652014-05-13 16:55:26 -0700703 // Add the primary horizontal nav
Dirk Dougherty29e93432015-05-05 18:17:13 -0700704 var $selectedFirstNav = $(primaryNavLink + ".selected, " + primaryNavLink + ".has-subnav").clone()
705 .attr('class', 'dac-header-crumbs-link');
706
Dirk Doughertyc3921652014-05-13 16:55:26 -0700707 // If there's no header nav item, use the logo link and title from alt text
708 if ($selectedFirstNav.length < 1) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700709 $selectedFirstNav = $('<a class="dac-header-crumbs-link">')
Dirk Doughertyc3921652014-05-13 16:55:26 -0700710 .attr('href', $("div#header .logo a").attr('href'))
711 .text($("div#header .logo img").attr('alt'));
712 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700713 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedFirstNav));
Dirk Doughertyc3921652014-05-13 16:55:26 -0700714}
715
716
717
Scott Maine624b3f2013-09-12 12:56:41 -0700718/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700719function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700720 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
721 if ($("ul#nav li.selected").length) {
722 unHighlightSidenav();
723 }
724 // look for URL in sidenav, including the hash
725 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
726
727 // If the selNavLink is still empty, look for it without the hash
728 if ($selNavLink.length == 0) {
729 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
730 }
731
Scott Mainf6145542013-04-01 16:38:11 -0700732 var $selListItem;
733 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700734 // Find this page's <li> in sidenav and set selected
735 $selListItem = $selNavLink.closest('li');
736 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700737
Scott Mainf6145542013-04-01 16:38:11 -0700738 // Traverse up the tree and expand all parent nav-sections
739 $selNavLink.parents('li.nav-section').each(function() {
740 $(this).addClass('expanded');
741 $(this).children('ul').show();
742 });
743 }
744}
745
Scott Maine624b3f2013-09-12 12:56:41 -0700746function unHighlightSidenav() {
747 $("ul#nav li.selected").removeClass("selected");
748 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
749}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700750
751function toggleFullscreen(enable) {
752 var delay = 20;
753 var enabled = true;
754 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
755 if (enable) {
756 // Currently NOT USING fullscreen; enable fullscreen
757 stylesheet.removeAttr('disabled');
758 $('#nav-swap .fullscreen').removeClass('disabled');
759 $('#devdoc-nav').css({left:''});
760 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
761 enabled = true;
762 } else {
763 // Currently USING fullscreen; disable fullscreen
764 stylesheet.attr('disabled', 'disabled');
765 $('#nav-swap .fullscreen').addClass('disabled');
766 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
767 enabled = false;
768 }
smain@google.com6bdcb982014-11-14 11:53:07 -0800769 writeCookie("fullscreen", enabled, null);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700770 setNavBarDimensions();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700771 resizeNav(delay);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700772 updateSideNavDimensions();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700773 setTimeout(initSidenavHeightResize,delay);
774}
775
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700776// TODO: Refactor into a closure.
777var navBarLeftPos;
778var navBarWidth;
779function setNavBarDimensions() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700780 navBarLeftPos = $('#body-content').offset().left;
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700781 navBarWidth = $('#side-nav').width();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700782}
783
784
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700785function updateSideNavDimensions() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700786 var newLeft = $(window).scrollLeft() - navBarLeftPos;
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700787 $('#devdoc-nav').css({left: -newLeft, width: navBarWidth});
Dirk Dougherty29e93432015-05-05 18:17:13 -0700788 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('padding-left')))});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700789}
Scott Main3b90aff2013-08-01 18:09:35 -0700790
Scott Maine4d8f1b2012-06-21 18:03:05 -0700791// TODO: use $(document).ready instead
792function addLoadEvent(newfun) {
793 var current = window.onload;
794 if (typeof window.onload != 'function') {
795 window.onload = newfun;
796 } else {
797 window.onload = function() {
798 current();
799 newfun();
800 }
801 }
802}
803
804var agent = navigator['userAgent'].toLowerCase();
805// If a mobile phone, set flag and do mobile setup
806if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
807 (agent.indexOf("blackberry") != -1) ||
808 (agent.indexOf("webos") != -1) ||
809 (agent.indexOf("mini") != -1)) { // opera mini browsers
810 isMobile = true;
811}
812
813
Scott Main498d7102013-08-21 15:47:38 -0700814$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700815 $("pre:not(.no-pretty-print)").addClass("prettyprint");
816 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700817});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700818
Scott Maine4d8f1b2012-06-21 18:03:05 -0700819
820
821
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700822/* ######### RESIZE THE SIDENAV ########## */
Scott Maine4d8f1b2012-06-21 18:03:05 -0700823
824function resizeNav(delay) {
825 var $nav = $("#devdoc-nav");
826 var $window = $(window);
827 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700828
Scott Maine4d8f1b2012-06-21 18:03:05 -0700829 // Get the height of entire window and the total header height.
830 // Then figure out based on scroll position whether the header is visible
831 var windowHeight = $window.height();
832 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700833 var headerHeight = $('#header-wrapper').outerHeight();
834 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700835
836 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700837 // Could be either margin or top position, depending on whether the nav is fixed.
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700838 var topMargin = (parseInt($nav.css('top')) || 20) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700839 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700840
Scott Maine4d8f1b2012-06-21 18:03:05 -0700841 // Depending on whether the header is visible, set the side nav's height.
842 if (headerVisible) {
843 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700844 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700845 } else {
846 // Once header is off screen, the nav height is almost full window height
847 navHeight = windowHeight - topMargin;
848 }
Scott Main3b90aff2013-08-01 18:09:35 -0700849
850
851
Scott Maine4d8f1b2012-06-21 18:03:05 -0700852 $scrollPanes = $(".scroll-pane");
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700853 if ($window.width() < 720) {
854 $nav.css('height', '');
855 } else if ($scrollPanes.length > 1) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700856 // subtract the height of the api level widget and nav swapper from the available nav height
857 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700858
Scott Maine4d8f1b2012-06-21 18:03:05 -0700859 $("#swapper").css({height:navHeight + "px"});
860 if ($("#nav-tree").is(":visible")) {
861 $("#nav-tree").css({height:navHeight});
862 }
Scott Main3b90aff2013-08-01 18:09:35 -0700863
864 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700865 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700866
867 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700868 // then the package panel should begin to shrink
869 if (parseInt(classesHeight) <= 0) {
870 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
871 $("#packages-nav").css({height:navHeight - 10});
872 }
Scott Main3b90aff2013-08-01 18:09:35 -0700873
Scott Maine4d8f1b2012-06-21 18:03:05 -0700874 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
875 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700876
877
Scott Maine4d8f1b2012-06-21 18:03:05 -0700878 } else {
879 $nav.height(navHeight);
880 }
Scott Main3b90aff2013-08-01 18:09:35 -0700881
Scott Maine4d8f1b2012-06-21 18:03:05 -0700882 if (delay) {
883 updateFromResize = true;
884 delayedReInitScrollbars(delay);
885 } else {
886 reInitScrollbars();
887 }
Scott Main3b90aff2013-08-01 18:09:35 -0700888
Scott Maine4d8f1b2012-06-21 18:03:05 -0700889}
890
891var updateScrollbars = false;
892var updateFromResize = false;
893
894/* Re-initialize the scrollbars to account for changed nav size.
895 * This method postpones the actual update by a 1/4 second in order to optimize the
896 * scroll performance while the header is still visible, because re-initializing the
897 * scroll panes is an intensive process.
898 */
899function delayedReInitScrollbars(delay) {
900 // If we're scheduled for an update, but have received another resize request
901 // before the scheduled resize has occured, just ignore the new request
902 // (and wait for the scheduled one).
903 if (updateScrollbars && updateFromResize) {
904 updateFromResize = false;
905 return;
906 }
Scott Main3b90aff2013-08-01 18:09:35 -0700907
Scott Maine4d8f1b2012-06-21 18:03:05 -0700908 // We're scheduled for an update and the update request came from this method's setTimeout
909 if (updateScrollbars && !updateFromResize) {
910 reInitScrollbars();
911 updateScrollbars = false;
912 } else {
913 updateScrollbars = true;
914 updateFromResize = false;
915 setTimeout('delayedReInitScrollbars()',delay);
916 }
917}
918
919/* Re-initialize the scrollbars to account for changed nav size. */
920function reInitScrollbars() {
921 var pane = $(".scroll-pane").each(function(){
922 var api = $(this).data('jsp');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700923 if (!api) {return;}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700924 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700925 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700926 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
927}
928
929
930/* Resize the height of the nav panels in the reference,
931 * and save the new size to a cookie */
932function saveNavPanels() {
933 var basePath = getBaseUri(location.pathname);
934 var section = basePath.substring(1,basePath.indexOf("/",1));
smain@google.com6bdcb982014-11-14 11:53:07 -0800935 writeCookie("height", resizePackagesNav.css("height"), section);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700936}
937
938
939
940function restoreHeight(packageHeight) {
941 $("#resize-packages-nav").height(packageHeight);
942 $("#packages-nav").height(packageHeight);
943 // var classesHeight = navHeight - packageHeight;
944 // $("#classes-nav").css({height:classesHeight});
945 // $("#classes-nav .jspContainer").css({height:classesHeight});
946}
947
948
949
950/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
951
952
953
954
955
Scott Main3b90aff2013-08-01 18:09:35 -0700956/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700957 This is called when the page finished loading. */
958function scrollIntoView(nav) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700959 return;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700960 var $nav = $("#"+nav);
961 var element = $nav.jScrollPane({/* ...settings... */});
962 var api = element.data('jsp');
963
964 if ($nav.is(':visible')) {
965 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700966 if ($selected.length == 0) {
967 // If no selected item found, exit
968 return;
969 }
Scott Main52dd2062013-08-15 12:22:28 -0700970 // get the selected item's offset from its container nav by measuring the item's offset
971 // relative to the document then subtract the container nav's offset relative to the document
972 var selectedOffset = $selected.offset().top - $nav.offset().top;
973 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
974 // if it's more than 80% down the nav
975 // scroll the item up by an amount equal to 80% the container nav's height
976 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700977 }
978 }
979}
980
981
982
983
984
985
986/* Show popup dialogs */
987function showDialog(id) {
988 $dialog = $("#"+id);
989 $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>');
990 $dialog.wrapInner('<div/>');
991 $dialog.removeClass("hide");
992}
993
994
995
996
997
998/* ######### COOKIES! ########## */
999
1000function readCookie(cookie) {
1001 var myCookie = cookie_namespace+"_"+cookie+"=";
1002 if (document.cookie) {
1003 var index = document.cookie.indexOf(myCookie);
1004 if (index != -1) {
1005 var valStart = index + myCookie.length;
1006 var valEnd = document.cookie.indexOf(";", valStart);
1007 if (valEnd == -1) {
1008 valEnd = document.cookie.length;
1009 }
1010 var val = document.cookie.substring(valStart, valEnd);
1011 return val;
1012 }
1013 }
1014 return 0;
1015}
1016
smain@google.com6bdcb982014-11-14 11:53:07 -08001017function writeCookie(cookie, val, section) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001018 if (val==undefined) return;
1019 section = section == null ? "_" : "_"+section+"_";
smain@google.com6bdcb982014-11-14 11:53:07 -08001020 var age = 2*365*24*60*60; // set max-age to 2 years
Scott Main3b90aff2013-08-01 18:09:35 -07001021 var cookieValue = cookie_namespace + section + cookie + "=" + val
smain@google.com80e38f42014-11-03 10:47:12 -08001022 + "; max-age=" + age +"; path=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -07001023 document.cookie = cookieValue;
1024}
1025
1026/* ######### END COOKIES! ########## */
1027
1028
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001029var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -07001030var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001031var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -07001032/* Sets the vertical scoll position at which the sticky bar should appear.
1033 This method is called to reset the position when search results appear or hide */
1034function setStickyTop() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001035 stickyTop = $('#header-wrapper').outerHeight() - $('#header > .dac-header-inner').outerHeight();
Dirk Doughertyc3921652014-05-13 16:55:26 -07001036}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001037
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001038/*
Scott Mainb16376f2014-05-21 20:35:47 -07001039 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -07001040 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001041$(window).scroll(function(event) {
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001042 // Exit if the mouse target is a DIV, because that means the event is coming
1043 // from a scrollable div and so there's no need to make adjustments to our layout
1044 if ($(event.target).nodeName == "DIV") {
1045 return;
1046 }
1047
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001048 checkSticky();
1049});
1050
1051function checkSticky() {
1052 setStickyTop();
1053 var $headerEl = $('#header');
1054 // Exit if there's no sidenav
1055 if ($('#side-nav').length == 0) return;
1056
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001057 var top = $(window).scrollTop();
1058 // we set the navbar fixed when the scroll position is beyond the height of the site header...
1059 var shouldBeSticky = top >= stickyTop;
1060 // ... except if the document content is shorter than the sidenav height.
1061 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1062 if ($("#doc-col").height() < $("#side-nav").height()) {
1063 shouldBeSticky = false;
1064 }
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001065 // Nor on mobile
1066 if (window.innerWidth < 720) {
1067 shouldBeSticky = false;
1068 }
Scott Mainf5257812014-05-22 17:26:38 -07001069 // Account for horizontal scroll
1070 var scrollLeft = $(window).scrollLeft();
1071 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1072 if (sticky && (scrollLeft != prevScrollLeft)) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001073 updateSideNavDimensions();
Scott Mainf5257812014-05-22 17:26:38 -07001074 prevScrollLeft = scrollLeft;
1075 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001076
1077 // Don't continue if the header is sufficently far away
1078 // (to avoid intensive resizing that slows scrolling)
1079 if (sticky == shouldBeSticky) {
1080 return;
1081 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001082
1083 // If sticky header visible and position is now near top, hide sticky
1084 if (sticky && !shouldBeSticky) {
1085 sticky = false;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001086 // make the sidenav static again
1087 $('#devdoc-nav')
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001088 .removeClass('fixed')
1089 .css({'width':'auto','margin':''});
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001090 // delay hide the sticky
Dirk Dougherty29e93432015-05-05 18:17:13 -07001091 $headerEl.removeClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001092
1093 // update the sidenaav position for side scrolling
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001094 updateSideNavDimensions();
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001095 } else if (!sticky && shouldBeSticky) {
1096 sticky = true;
Dirk Dougherty29e93432015-05-05 18:17:13 -07001097 $headerEl.addClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001098
1099 // make the sidenav fixed
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001100 $('#devdoc-nav')
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001101 .addClass('fixed');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001102
1103 // update the sidenaav position for side scrolling
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001104 updateSideNavDimensions();
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001105
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001106 }
1107 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001108}
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001109
1110/*
1111 * Manages secion card states and nav resize to conclude loading
1112 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001113(function() {
1114 $(document).ready(function() {
1115
Dirk Doughertyc3921652014-05-13 16:55:26 -07001116 // Stack hover states
1117 $('.section-card-menu').each(function(index, el) {
1118 var height = $(el).height();
1119 $(el).css({height:height+'px', position:'relative'});
1120 var $cardInfo = $(el).find('.card-info');
1121
1122 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1123 });
1124
Dirk Doughertyc3921652014-05-13 16:55:26 -07001125 });
1126
1127})();
1128
Scott Maine4d8f1b2012-06-21 18:03:05 -07001129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
Scott Maind7026f72013-06-17 15:08:49 -07001142/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001143
1144
1145
1146
1147
1148function toggle(obj, slide) {
1149 var ul = $("ul:first", obj);
1150 var li = ul.parent();
1151 if (li.hasClass("closed")) {
1152 if (slide) {
1153 ul.slideDown("fast");
1154 } else {
1155 ul.show();
1156 }
1157 li.removeClass("closed");
1158 li.addClass("open");
1159 $(".toggle-img", li).attr("title", "hide pages");
1160 } else {
1161 ul.slideUp("fast");
1162 li.removeClass("open");
1163 li.addClass("closed");
1164 $(".toggle-img", li).attr("title", "show pages");
1165 }
1166}
1167
1168
Scott Maine4d8f1b2012-06-21 18:03:05 -07001169function buildToggleLists() {
1170 $(".toggle-list").each(
1171 function(i) {
1172 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1173 $(this).addClass("closed");
1174 });
1175}
1176
1177
1178
Scott Maind7026f72013-06-17 15:08:49 -07001179function hideNestedItems(list, toggle) {
1180 $list = $(list);
1181 // hide nested lists
1182 if($list.hasClass('showing')) {
1183 $("li ol", $list).hide('fast');
1184 $list.removeClass('showing');
1185 // show nested lists
1186 } else {
1187 $("li ol", $list).show('fast');
1188 $list.addClass('showing');
1189 }
1190 $(".more,.less",$(toggle)).toggle();
1191}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001192
1193
smain@google.com95948b82014-06-16 19:24:25 -07001194/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1195function setupIdeDocToggle() {
1196 $( "select.ide" ).change(function() {
1197 var selected = $(this).find("option:selected").attr("value");
1198 $(".select-ide").hide();
1199 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001200
smain@google.com95948b82014-06-16 19:24:25 -07001201 $("select.ide").val(selected);
1202 });
1203}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228/* REFERENCE NAV SWAP */
1229
1230
1231function getNavPref() {
1232 var v = readCookie('reference_nav');
1233 if (v != NAV_PREF_TREE) {
1234 v = NAV_PREF_PANELS;
1235 }
1236 return v;
1237}
1238
1239function chooseDefaultNav() {
1240 nav_pref = getNavPref();
1241 if (nav_pref == NAV_PREF_TREE) {
1242 $("#nav-panels").toggle();
1243 $("#panel-link").toggle();
1244 $("#nav-tree").toggle();
1245 $("#tree-link").toggle();
1246 }
1247}
1248
1249function swapNav() {
1250 if (nav_pref == NAV_PREF_TREE) {
1251 nav_pref = NAV_PREF_PANELS;
1252 } else {
1253 nav_pref = NAV_PREF_TREE;
1254 init_default_navtree(toRoot);
1255 }
smain@google.com6bdcb982014-11-14 11:53:07 -08001256 writeCookie("nav", nav_pref, "reference");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001257
1258 $("#nav-panels").toggle();
1259 $("#panel-link").toggle();
1260 $("#nav-tree").toggle();
1261 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001262
Scott Maine4d8f1b2012-06-21 18:03:05 -07001263 resizeNav();
1264
1265 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1266 $("#nav-tree .jspContainer:visible")
1267 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1268 // Another nasty hack to make the scrollbar appear now that we have height
1269 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001270
Scott Maine4d8f1b2012-06-21 18:03:05 -07001271 if ($("#nav-tree").is(':visible')) {
1272 scrollIntoView("nav-tree");
1273 } else {
1274 scrollIntoView("packages-nav");
1275 scrollIntoView("classes-nav");
1276 }
1277}
1278
1279
1280
Scott Mainf5089842012-08-14 16:31:07 -07001281/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001282/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001283/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001284
1285function getBaseUri(uri) {
1286 var intlUrl = (uri.substring(0,6) == "/intl/");
1287 if (intlUrl) {
1288 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1289 base = base.substring(base.indexOf('/')+1, base.length);
1290 //alert("intl, returning base url: /" + base);
1291 return ("/" + base);
1292 } else {
1293 //alert("not intl, returning uri as found.");
1294 return uri;
1295 }
1296}
1297
1298function requestAppendHL(uri) {
1299//append "?hl=<lang> to an outgoing request (such as to blog)
1300 var lang = getLangPref();
1301 if (lang) {
1302 var q = 'hl=' + lang;
1303 uri += '?' + q;
1304 window.location = uri;
1305 return false;
1306 } else {
1307 return true;
1308 }
1309}
1310
1311
Scott Maine4d8f1b2012-06-21 18:03:05 -07001312function changeNavLang(lang) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001313 if (lang === 'en') { return; }
1314
1315 var $links = $('a[' + lang + '-lang]');
1316 $links.each(function(){ // for each link with a translation
Scott Main6eb95f12012-10-02 17:12:23 -07001317 var $link = $(this);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001318 // put the desired language from the attribute as the text
1319 $link.text($link.attr(lang + '-lang'))
Scott Main6eb95f12012-10-02 17:12:23 -07001320 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001321}
1322
Scott Main015d6162013-01-29 09:01:52 -08001323function changeLangPref(lang, submit) {
smain@google.com6bdcb982014-11-14 11:53:07 -08001324 writeCookie("pref_lang", lang, null);
Scott Main015d6162013-01-29 09:01:52 -08001325
1326 // ####### TODO: Remove this condition once we're stable on devsite #######
1327 // This condition is only needed if we still need to support legacy GAE server
1328 if (devsite) {
1329 // Switch language when on Devsite server
1330 if (submit) {
1331 $("#setlang").submit();
1332 }
1333 } else {
1334 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001335 if (submit) {
1336 window.location = getBaseUri(location.pathname);
1337 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001338 }
1339}
1340
1341function loadLangPref() {
1342 var lang = readCookie("pref_lang");
1343 if (lang != 0) {
1344 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1345 }
1346}
1347
1348function getLangPref() {
1349 var lang = $("#language").find(":selected").attr("value");
1350 if (!lang) {
1351 lang = readCookie("pref_lang");
1352 }
1353 return (lang != 0) ? lang : 'en';
1354}
1355
1356/* ########## END LOCALIZATION ############ */
1357
1358
1359
1360
1361
1362
1363/* Used to hide and reveal supplemental content, such as long code samples.
1364 See the companion CSS in android-developer-docs.css */
1365function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001366 var div = $(obj).closest(".toggle-content");
1367 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001368 if (div.hasClass("closed")) { // if it's closed, open it
1369 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001370 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001371 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001372 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001373 + "assets/images/triangle-opened.png");
1374 } else { // if it's open, close it
1375 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001376 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001377 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001378 div.find(".toggle-content").removeClass("open").addClass("closed")
1379 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001380 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001381 + "assets/images/triangle-closed.png");
1382 });
1383 }
1384 return false;
1385}
Scott Mainf5089842012-08-14 16:31:07 -07001386
1387
Scott Maindb3678b2012-10-23 14:13:41 -07001388/* New version of expandable content */
1389function toggleExpandable(link,id) {
1390 if($(id).is(':visible')) {
1391 $(id).slideUp();
1392 $(link).removeClass('expanded');
1393 } else {
1394 $(id).slideDown();
1395 $(link).addClass('expanded');
1396 }
1397}
1398
1399function hideExpandable(ids) {
1400 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001401 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001402}
1403
Scott Mainf5089842012-08-14 16:31:07 -07001404
1405
1406
1407
Scott Main3b90aff2013-08-01 18:09:35 -07001408/*
Scott Mainf5089842012-08-14 16:31:07 -07001409 * Slideshow 1.0
1410 * Used on /index.html and /develop/index.html for carousel
1411 *
1412 * Sample usage:
1413 * HTML -
1414 * <div class="slideshow-container">
1415 * <a href="" class="slideshow-prev">Prev</a>
1416 * <a href="" class="slideshow-next">Next</a>
1417 * <ul>
1418 * <li class="item"><img src="images/marquee1.jpg"></li>
1419 * <li class="item"><img src="images/marquee2.jpg"></li>
1420 * <li class="item"><img src="images/marquee3.jpg"></li>
1421 * <li class="item"><img src="images/marquee4.jpg"></li>
1422 * </ul>
1423 * </div>
1424 *
1425 * <script type="text/javascript">
1426 * $('.slideshow-container').dacSlideshow({
1427 * auto: true,
1428 * btnPrev: '.slideshow-prev',
1429 * btnNext: '.slideshow-next'
1430 * });
1431 * </script>
1432 *
1433 * Options:
1434 * btnPrev: optional identifier for previous button
1435 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001436 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001437 * auto: whether or not to auto-proceed
1438 * speed: animation speed
1439 * autoTime: time between auto-rotation
1440 * easing: easing function for transition
1441 * start: item to select by default
1442 * scroll: direction to scroll in
1443 * pagination: whether or not to include dotted pagination
1444 *
1445 */
1446
1447 (function($) {
1448 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001449
Scott Mainf5089842012-08-14 16:31:07 -07001450 //Options - see above
1451 o = $.extend({
1452 btnPrev: null,
1453 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001454 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001455 auto: true,
1456 speed: 500,
1457 autoTime: 12000,
1458 easing: null,
1459 start: 0,
1460 scroll: 1,
1461 pagination: true
1462
1463 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001464
1465 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001466 return this.each(function() {
1467
1468 var running = false;
1469 var animCss = o.vertical ? "top" : "left";
1470 var sizeCss = o.vertical ? "height" : "width";
1471 var div = $(this);
1472 var ul = $("ul", div);
1473 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001474 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001475 var timer = null;
1476
1477 var li = $("li", ul);
1478 var itemLength = li.size();
1479 var curr = o.start;
1480
1481 li.css({float: o.vertical ? "none" : "left"});
1482 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1483 div.css({position: "relative", "z-index": "2", left: "0px"});
1484
1485 var liSize = o.vertical ? height(li) : width(li);
1486 var ulSize = liSize * itemLength;
1487 var divSize = liSize;
1488
1489 li.css({width: li.width(), height: li.height()});
1490 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1491
1492 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001493
Scott Mainf5089842012-08-14 16:31:07 -07001494 //Pagination
1495 if (o.pagination) {
1496 var pagination = $("<div class='pagination'></div>");
1497 var pag_ul = $("<ul></ul>");
1498 if (tl > 1) {
1499 for (var i=0;i<tl;i++) {
1500 var li = $("<li>"+i+"</li>");
1501 pag_ul.append(li);
1502 if (i==o.start) li.addClass('active');
1503 li.click(function() {
1504 go(parseInt($(this).text()));
1505 })
1506 }
1507 pagination.append(pag_ul);
1508 div.append(pagination);
1509 }
1510 }
Scott Main3b90aff2013-08-01 18:09:35 -07001511
Scott Mainf5089842012-08-14 16:31:07 -07001512 //Previous button
1513 if(o.btnPrev)
1514 $(o.btnPrev).click(function(e) {
1515 e.preventDefault();
1516 return go(curr-o.scroll);
1517 });
1518
1519 //Next button
1520 if(o.btnNext)
1521 $(o.btnNext).click(function(e) {
1522 e.preventDefault();
1523 return go(curr+o.scroll);
1524 });
Scott Maineb410352013-01-14 19:03:40 -08001525
1526 //Pause button
1527 if(o.btnPause)
1528 $(o.btnPause).click(function(e) {
1529 e.preventDefault();
1530 if ($(this).hasClass('paused')) {
1531 startRotateTimer();
1532 } else {
1533 pauseRotateTimer();
1534 }
1535 });
Scott Main3b90aff2013-08-01 18:09:35 -07001536
Scott Mainf5089842012-08-14 16:31:07 -07001537 //Auto rotation
1538 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001539
Scott Mainf5089842012-08-14 16:31:07 -07001540 function startRotateTimer() {
1541 clearInterval(timer);
1542 timer = setInterval(function() {
1543 if (curr == tl-1) {
1544 go(0);
1545 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001546 go(curr+o.scroll);
1547 }
Scott Mainf5089842012-08-14 16:31:07 -07001548 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001549 $(o.btnPause).removeClass('paused');
1550 }
1551
1552 function pauseRotateTimer() {
1553 clearInterval(timer);
1554 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001555 }
1556
1557 //Go to an item
1558 function go(to) {
1559 if(!running) {
1560
1561 if(to<0) {
1562 to = itemLength-1;
1563 } else if (to>itemLength-1) {
1564 to = 0;
1565 }
1566 curr = to;
1567
1568 running = true;
1569
1570 ul.animate(
1571 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1572 function() {
1573 running = false;
1574 }
1575 );
1576
1577 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1578 $( (curr-o.scroll<0 && o.btnPrev)
1579 ||
1580 (curr+o.scroll > itemLength && o.btnNext)
1581 ||
1582 []
1583 ).addClass("disabled");
1584
Scott Main3b90aff2013-08-01 18:09:35 -07001585
Scott Mainf5089842012-08-14 16:31:07 -07001586 var nav_items = $('li', pagination);
1587 nav_items.removeClass('active');
1588 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001589
Scott Mainf5089842012-08-14 16:31:07 -07001590
1591 }
1592 if(o.auto) startRotateTimer();
1593 return false;
1594 };
1595 });
1596 };
1597
1598 function css(el, prop) {
1599 return parseInt($.css(el[0], prop)) || 0;
1600 };
1601 function width(el) {
1602 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1603 };
1604 function height(el) {
1605 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1606 };
1607
1608 })(jQuery);
1609
1610
Scott Main3b90aff2013-08-01 18:09:35 -07001611/*
Scott Mainf5089842012-08-14 16:31:07 -07001612 * dacSlideshow 1.0
1613 * Used on develop/index.html for side-sliding tabs
1614 *
1615 * Sample usage:
1616 * HTML -
1617 * <div class="slideshow-container">
1618 * <a href="" class="slideshow-prev">Prev</a>
1619 * <a href="" class="slideshow-next">Next</a>
1620 * <ul>
1621 * <li class="item"><img src="images/marquee1.jpg"></li>
1622 * <li class="item"><img src="images/marquee2.jpg"></li>
1623 * <li class="item"><img src="images/marquee3.jpg"></li>
1624 * <li class="item"><img src="images/marquee4.jpg"></li>
1625 * </ul>
1626 * </div>
1627 *
1628 * <script type="text/javascript">
1629 * $('.slideshow-container').dacSlideshow({
1630 * auto: true,
1631 * btnPrev: '.slideshow-prev',
1632 * btnNext: '.slideshow-next'
1633 * });
1634 * </script>
1635 *
1636 * Options:
1637 * btnPrev: optional identifier for previous button
1638 * btnNext: optional identifier for next button
1639 * auto: whether or not to auto-proceed
1640 * speed: animation speed
1641 * autoTime: time between auto-rotation
1642 * easing: easing function for transition
1643 * start: item to select by default
1644 * scroll: direction to scroll in
1645 * pagination: whether or not to include dotted pagination
1646 *
1647 */
1648 (function($) {
1649 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001650
Scott Mainf5089842012-08-14 16:31:07 -07001651 //Options - see above
1652 o = $.extend({
1653 speed : 250,
1654 easing: null,
1655 nav_id: null,
1656 frame_id: null
1657 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001658
1659 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001660 return this.each(function() {
1661
1662 var curr = 0;
1663 var running = false;
1664 var animCss = "margin-left";
1665 var sizeCss = "width";
1666 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001667
Scott Mainf5089842012-08-14 16:31:07 -07001668 var nav = $(o.nav_id, div);
1669 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001670 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001671 var frame = div.find(o.frame_id);
1672 var content_width = $(frame).find('ul').width();
1673 //Buttons
1674 $(nav_li).click(function(e) {
1675 go($(nav_li).index($(this)));
1676 })
Scott Main3b90aff2013-08-01 18:09:35 -07001677
Scott Mainf5089842012-08-14 16:31:07 -07001678 //Go to an item
1679 function go(to) {
1680 if(!running) {
1681 curr = to;
1682 running = true;
1683
1684 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1685 function() {
1686 running = false;
1687 }
1688 );
1689
Scott Main3b90aff2013-08-01 18:09:35 -07001690
Scott Mainf5089842012-08-14 16:31:07 -07001691 nav_li.removeClass('active');
1692 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001693
Scott Mainf5089842012-08-14 16:31:07 -07001694
1695 }
1696 return false;
1697 };
1698 });
1699 };
1700
1701 function css(el, prop) {
1702 return parseInt($.css(el[0], prop)) || 0;
1703 };
1704 function width(el) {
1705 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1706 };
1707 function height(el) {
1708 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1709 };
1710
1711 })(jQuery);
1712
1713
1714
1715
1716
1717/* ######################################################## */
1718/* ################ SEARCH SUGGESTIONS ################## */
1719/* ######################################################## */
1720
1721
Scott Main7e447ed2013-02-19 17:22:37 -08001722
Scott Main0e76e7e2013-03-12 10:24:07 -07001723var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1724var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1725
Scott Mainf5089842012-08-14 16:31:07 -07001726var gMatches = new Array();
1727var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001728var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001729var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1730var gListLength = 0;
1731
1732
1733var gGoogleMatches = new Array();
1734var ROW_COUNT_GOOGLE = 15; // max number of results in list
1735var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001736
Scott Main0e76e7e2013-03-12 10:24:07 -07001737var gDocsMatches = new Array();
1738var ROW_COUNT_DOCS = 100; // max number of results in list
1739var gDocsListLength = 0;
1740
Scott Mainde295272013-03-25 15:48:35 -07001741function onSuggestionClick(link) {
1742 // When user clicks a suggested document, track it
smain@google.comed677d72014-12-12 10:19:17 -08001743 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
1744 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07001745}
1746
Scott Mainf5089842012-08-14 16:31:07 -07001747function set_item_selected($li, selected)
1748{
1749 if (selected) {
1750 $li.attr('class','jd-autocomplete jd-selected');
1751 } else {
1752 $li.attr('class','jd-autocomplete');
1753 }
1754}
1755
1756function set_item_values(toroot, $li, match)
1757{
1758 var $link = $('a',$li);
1759 $link.html(match.__hilabel || match.label);
1760 $link.attr('href',toroot + match.link);
1761}
1762
Scott Main719acb42013-12-05 16:05:09 -08001763function set_item_values_jd(toroot, $li, match)
1764{
1765 var $link = $('a',$li);
1766 $link.html(match.title);
1767 $link.attr('href',toroot + match.url);
1768}
1769
Scott Main0e76e7e2013-03-12 10:24:07 -07001770function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001771 var $li = $("<li class='jd-autocomplete'></li>");
1772 $list.append($li);
1773
1774 $li.mousedown(function() {
1775 window.location = this.firstChild.getAttribute("href");
1776 });
1777 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001778 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001779 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001780 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1781 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001782 });
Scott Mainde295272013-03-25 15:48:35 -07001783 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001784 $li.attr('class','show-item');
1785 return $li;
1786}
1787
Scott Mainf5089842012-08-14 16:31:07 -07001788function sync_selection_table(toroot)
1789{
Scott Mainf5089842012-08-14 16:31:07 -07001790 var $li; //list item jquery object
1791 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001792
Scott Main0e76e7e2013-03-12 10:24:07 -07001793 // if there are NO results at all, hide all columns
1794 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1795 $('.suggest-card').hide(300);
1796 return;
1797 }
1798
1799 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001800 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001801 // reveal suggestion list
Scott Main0e76e7e2013-03-12 10:24:07 -07001802 $('.suggest-card.reference').show();
1803 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001804
Scott Main0e76e7e2013-03-12 10:24:07 -07001805 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001806 $(".suggest-card.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001807
Scott Main0e76e7e2013-03-12 10:24:07 -07001808 // ########### ANDROID RESULTS #############
1809 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001810
Scott Main0e76e7e2013-03-12 10:24:07 -07001811 // determine android results to show
1812 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1813 gMatches.length : ROW_COUNT_FRAMEWORK;
1814 for (i=0; i<gListLength; i++) {
1815 var $li = new_suggestion($(".suggest-card.reference ul"));
1816 set_item_values(toroot, $li, gMatches[i]);
1817 set_item_selected($li, i == gSelectedIndex);
1818 }
1819 }
Scott Main7e447ed2013-02-19 17:22:37 -08001820
Scott Main0e76e7e2013-03-12 10:24:07 -07001821 // ########### GOOGLE RESULTS #############
1822 if (gGoogleMatches.length > 0) {
1823 // show header for list
1824 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001825
Scott Main0e76e7e2013-03-12 10:24:07 -07001826 // determine google results to show
1827 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1828 for (i=0; i<gGoogleListLength; i++) {
1829 var $li = new_suggestion($(".suggest-card.reference ul"));
1830 set_item_values(toroot, $li, gGoogleMatches[i]);
1831 set_item_selected($li, i == gSelectedIndex);
1832 }
1833 }
Scott Mainf5089842012-08-14 16:31:07 -07001834 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001835 $('.suggest-card.reference').hide();
Scott Main0e76e7e2013-03-12 10:24:07 -07001836 }
1837
1838 // ########### JD DOC RESULTS #############
1839 if (gDocsMatches.length > 0) {
1840 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001841 $(".suggest-card:not(.reference) li").remove();
Scott Main0e76e7e2013-03-12 10:24:07 -07001842
1843 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001844 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1845 // The order must match the reverse order that each section appears as a card in
1846 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001847 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1848 for (i=0; i<gDocsListLength; i++) {
1849 var sugg = gDocsMatches[i];
1850 var $li;
1851 if (sugg.type == "design") {
1852 $li = new_suggestion($(".suggest-card.design ul"));
1853 } else
1854 if (sugg.type == "distribute") {
1855 $li = new_suggestion($(".suggest-card.distribute ul"));
1856 } else
Scott Main719acb42013-12-05 16:05:09 -08001857 if (sugg.type == "samples") {
1858 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1859 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001860 if (sugg.type == "training") {
1861 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1862 } else
Scott Main719acb42013-12-05 16:05:09 -08001863 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001864 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1865 } else {
1866 continue;
1867 }
1868
Scott Main719acb42013-12-05 16:05:09 -08001869 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001870 set_item_selected($li, i == gSelectedIndex);
1871 }
1872
1873 // add heading and show or hide card
1874 if ($(".suggest-card.design li").length > 0) {
1875 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1876 $(".suggest-card.design").show(300);
1877 } else {
1878 $('.suggest-card.design').hide(300);
1879 }
1880 if ($(".suggest-card.distribute li").length > 0) {
1881 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1882 $(".suggest-card.distribute").show(300);
1883 } else {
1884 $('.suggest-card.distribute').hide(300);
1885 }
1886 if ($(".child-card.guides li").length > 0) {
1887 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1888 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1889 }
1890 if ($(".child-card.training li").length > 0) {
1891 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1892 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1893 }
Scott Main719acb42013-12-05 16:05:09 -08001894 if ($(".child-card.samples li").length > 0) {
1895 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1896 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1897 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001898
1899 if ($(".suggest-card.develop li").length > 0) {
1900 $(".suggest-card.develop").show(300);
1901 } else {
1902 $('.suggest-card.develop').hide(300);
1903 }
1904
1905 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001906 $('.suggest-card:not(.reference)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001907 }
1908}
1909
Scott Main0e76e7e2013-03-12 10:24:07 -07001910/** Called by the search input's onkeydown and onkeyup events.
1911 * Handles navigation with keyboard arrows, Enter key to invoke search,
1912 * otherwise invokes search suggestions on key-up event.
1913 * @param e The JS event
1914 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001915 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001916 * @returns True if the event should bubble up
1917 */
Scott Mainf5089842012-08-14 16:31:07 -07001918function search_changed(e, kd, toroot)
1919{
Scott Main719acb42013-12-05 16:05:09 -08001920 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001921 var search = document.getElementById("search_autocomplete");
1922 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001923 // get the ul hosting the currently selected item
1924 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1925 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1926 var $selectedUl = $columns[gSelectedColumn];
1927
Scott Mainf5089842012-08-14 16:31:07 -07001928 // show/hide the close button
1929 if (text != '') {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001930 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07001931 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001932 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07001933 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001934 // 27 = esc
1935 if (e.keyCode == 27) {
1936 // close all search results
Dirk Dougherty29e93432015-05-05 18:17:13 -07001937 if (kd) $('#search-close').trigger('click');
Scott Main0e76e7e2013-03-12 10:24:07 -07001938 return true;
1939 }
Scott Mainf5089842012-08-14 16:31:07 -07001940 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001941 else if (e.keyCode == 13) {
1942 if (gSelectedIndex < 0) {
1943 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001944 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1945 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001946 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001947 return true;
1948 } else {
1949 // otherwise, results are already showing, so allow ajax to auto refresh the results
1950 // and ignore this Enter press to avoid the reload.
1951 return false;
1952 }
1953 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001954 // click the link corresponding to selected item
1955 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001956 return false;
1957 }
1958 }
Scott Mainb16376f2014-05-21 20:35:47 -07001959 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001960 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001961 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001962 if ((sticky ) && (search.value != "")) {
1963 $('body,html').animate({scrollTop:0}, '500', 'swing');
1964 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001965 return true;
1966 }
1967 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001968 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001969 // if the next item is a header, skip it
1970 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001971 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001972 }
1973 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001974 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001975 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001976 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1977 // If user reaches top, reset selected column
1978 if (gSelectedIndex < 0) {
1979 gSelectedColumn = -1;
1980 }
Scott Mainf5089842012-08-14 16:31:07 -07001981 }
1982 return false;
1983 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001984 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001985 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001986 // if the next item is a header, skip it
1987 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001988 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08001989 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001990 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
1991 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
1992 $('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');
Scott Mainf5089842012-08-14 16:31:07 -07001995 }
1996 return false;
1997 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001998 // Consider left/right arrow navigation
1999 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
2000 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
2001 // 37 LEFT ARROW
2002 // go left only if current column is not left-most column (last column)
2003 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
2004 $('li', $selectedUl).removeClass('jd-selected');
2005 gSelectedColumn++;
2006 $selectedUl = $columns[gSelectedColumn];
2007 // keep or reset the selected item to last item as appropriate
2008 gSelectedIndex = gSelectedIndex >
2009 $("li", $selectedUl).length-1 ?
2010 $("li", $selectedUl).length-1 : gSelectedIndex;
2011 // if the corresponding item is a header, move down
2012 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2013 gSelectedIndex++;
2014 }
Scott Main3b90aff2013-08-01 18:09:35 -07002015 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002016 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2017 return false;
2018 }
2019 // 39 RIGHT ARROW
2020 // go right only if current column is not the right-most column (first column)
2021 else if (e.keyCode == 39 && gSelectedColumn > 0) {
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 }
2038
Scott Main719acb42013-12-05 16:05:09 -08002039 // if key-up event and not arrow down/up/left/right,
2040 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07002041 else if (!kd && (e.keyCode != 40)
2042 && (e.keyCode != 38)
2043 && (e.keyCode != 37)
2044 && (e.keyCode != 39)) {
2045 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07002046 gMatches = new Array();
2047 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002048 gGoogleMatches = new Array();
2049 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07002050 gDocsMatches = new Array();
2051 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002052
2053 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07002054 for (var i=0; i<DATA.length; i++) {
2055 var s = DATA[i];
2056 if (text.length != 0 &&
2057 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2058 gMatches[matchedCount] = s;
2059 matchedCount++;
2060 }
2061 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002062 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07002063 for (var i=0; i<gMatches.length; i++) {
2064 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08002065 }
2066
2067
2068 // Search for Google matches
2069 for (var i=0; i<GOOGLE_DATA.length; i++) {
2070 var s = GOOGLE_DATA[i];
2071 if (text.length != 0 &&
2072 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2073 gGoogleMatches[matchedCountGoogle] = s;
2074 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07002075 }
2076 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002077 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08002078 for (var i=0; i<gGoogleMatches.length; i++) {
2079 var s = gGoogleMatches[i];
2080 }
2081
Scott Mainf5089842012-08-14 16:31:07 -07002082 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07002083
2084
2085
Scott Main719acb42013-12-05 16:05:09 -08002086 // Search for matching JD docs
Dirk Dougherty9b7f8f22014-11-01 17:08:56 -07002087 if (text.length >= 2) {
Scott Main719acb42013-12-05 16:05:09 -08002088 // Regex to match only the beginning of a word
2089 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
2090
2091
2092 // Search for Training classes
2093 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002094 // current search comparison, with counters for tag and title,
2095 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08002096 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07002097 s.matched_tag = 0;
2098 s.matched_title = 0;
2099 var matched = false;
2100
2101 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08002102 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002103 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08002104 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002105 matched = true;
2106 s.matched_tag = j + 1; // add 1 to index position
2107 }
2108 }
Scott Main719acb42013-12-05 16:05:09 -08002109 // Don't consider doc title for lessons (only for class landing pages),
2110 // unless the lesson has a tag that already matches
2111 if ((s.lang == currentLang) &&
2112 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002113 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08002114 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002115 matched = true;
2116 s.matched_title = 1;
2117 }
2118 }
2119 if (matched) {
2120 gDocsMatches[matchedCountDocs] = s;
2121 matchedCountDocs++;
2122 }
2123 }
Scott Main719acb42013-12-05 16:05:09 -08002124
2125
2126 // Search for API Guides
2127 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2128 // current search comparison, with counters for tag and title,
2129 // used later to improve ranking
2130 var s = GUIDE_RESOURCES[i];
2131 s.matched_tag = 0;
2132 s.matched_title = 0;
2133 var matched = false;
2134
2135 // Check if query matches any tags; work backwards toward 1 to assist ranking
2136 for (var j = s.keywords.length - 1; j >= 0; j--) {
2137 // it matches a tag
2138 if (s.keywords[j].toLowerCase().match(textRegex)) {
2139 matched = true;
2140 s.matched_tag = j + 1; // add 1 to index position
2141 }
2142 }
2143 // Check if query matches the doc title, but only for current language
2144 if (s.lang == currentLang) {
2145 // if query matches the doc title
2146 if (s.title.toLowerCase().match(textRegex)) {
2147 matched = true;
2148 s.matched_title = 1;
2149 }
2150 }
2151 if (matched) {
2152 gDocsMatches[matchedCountDocs] = s;
2153 matchedCountDocs++;
2154 }
2155 }
2156
2157
2158 // Search for Tools Guides
2159 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2160 // current search comparison, with counters for tag and title,
2161 // used later to improve ranking
2162 var s = TOOLS_RESOURCES[i];
2163 s.matched_tag = 0;
2164 s.matched_title = 0;
2165 var matched = false;
2166
2167 // Check if query matches any tags; work backwards toward 1 to assist ranking
2168 for (var j = s.keywords.length - 1; j >= 0; j--) {
2169 // it matches a tag
2170 if (s.keywords[j].toLowerCase().match(textRegex)) {
2171 matched = true;
2172 s.matched_tag = j + 1; // add 1 to index position
2173 }
2174 }
2175 // Check if query matches the doc title, but only for current language
2176 if (s.lang == currentLang) {
2177 // if query matches the doc title
2178 if (s.title.toLowerCase().match(textRegex)) {
2179 matched = true;
2180 s.matched_title = 1;
2181 }
2182 }
2183 if (matched) {
2184 gDocsMatches[matchedCountDocs] = s;
2185 matchedCountDocs++;
2186 }
2187 }
2188
2189
2190 // Search for About docs
2191 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2192 // current search comparison, with counters for tag and title,
2193 // used later to improve ranking
2194 var s = ABOUT_RESOURCES[i];
2195 s.matched_tag = 0;
2196 s.matched_title = 0;
2197 var matched = false;
2198
2199 // Check if query matches any tags; work backwards toward 1 to assist ranking
2200 for (var j = s.keywords.length - 1; j >= 0; j--) {
2201 // it matches a tag
2202 if (s.keywords[j].toLowerCase().match(textRegex)) {
2203 matched = true;
2204 s.matched_tag = j + 1; // add 1 to index position
2205 }
2206 }
2207 // Check if query matches the doc title, but only for current language
2208 if (s.lang == currentLang) {
2209 // if query matches the doc title
2210 if (s.title.toLowerCase().match(textRegex)) {
2211 matched = true;
2212 s.matched_title = 1;
2213 }
2214 }
2215 if (matched) {
2216 gDocsMatches[matchedCountDocs] = s;
2217 matchedCountDocs++;
2218 }
2219 }
2220
2221
2222 // Search for Design guides
2223 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2224 // current search comparison, with counters for tag and title,
2225 // used later to improve ranking
2226 var s = DESIGN_RESOURCES[i];
2227 s.matched_tag = 0;
2228 s.matched_title = 0;
2229 var matched = false;
2230
2231 // Check if query matches any tags; work backwards toward 1 to assist ranking
2232 for (var j = s.keywords.length - 1; j >= 0; j--) {
2233 // it matches a tag
2234 if (s.keywords[j].toLowerCase().match(textRegex)) {
2235 matched = true;
2236 s.matched_tag = j + 1; // add 1 to index position
2237 }
2238 }
2239 // Check if query matches the doc title, but only for current language
2240 if (s.lang == currentLang) {
2241 // if query matches the doc title
2242 if (s.title.toLowerCase().match(textRegex)) {
2243 matched = true;
2244 s.matched_title = 1;
2245 }
2246 }
2247 if (matched) {
2248 gDocsMatches[matchedCountDocs] = s;
2249 matchedCountDocs++;
2250 }
2251 }
2252
2253
2254 // Search for Distribute guides
2255 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2256 // current search comparison, with counters for tag and title,
2257 // used later to improve ranking
2258 var s = DISTRIBUTE_RESOURCES[i];
2259 s.matched_tag = 0;
2260 s.matched_title = 0;
2261 var matched = false;
2262
2263 // Check if query matches any tags; work backwards toward 1 to assist ranking
2264 for (var j = s.keywords.length - 1; j >= 0; j--) {
2265 // it matches a tag
2266 if (s.keywords[j].toLowerCase().match(textRegex)) {
2267 matched = true;
2268 s.matched_tag = j + 1; // add 1 to index position
2269 }
2270 }
2271 // Check if query matches the doc title, but only for current language
2272 if (s.lang == currentLang) {
2273 // if query matches the doc title
2274 if (s.title.toLowerCase().match(textRegex)) {
2275 matched = true;
2276 s.matched_title = 1;
2277 }
2278 }
2279 if (matched) {
2280 gDocsMatches[matchedCountDocs] = s;
2281 matchedCountDocs++;
2282 }
2283 }
2284
2285
2286 // Search for Google guides
2287 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2288 // current search comparison, with counters for tag and title,
2289 // used later to improve ranking
2290 var s = GOOGLE_RESOURCES[i];
2291 s.matched_tag = 0;
2292 s.matched_title = 0;
2293 var matched = false;
2294
2295 // Check if query matches any tags; work backwards toward 1 to assist ranking
2296 for (var j = s.keywords.length - 1; j >= 0; j--) {
2297 // it matches a tag
2298 if (s.keywords[j].toLowerCase().match(textRegex)) {
2299 matched = true;
2300 s.matched_tag = j + 1; // add 1 to index position
2301 }
2302 }
2303 // Check if query matches the doc title, but only for current language
2304 if (s.lang == currentLang) {
2305 // if query matches the doc title
2306 if (s.title.toLowerCase().match(textRegex)) {
2307 matched = true;
2308 s.matched_title = 1;
2309 }
2310 }
2311 if (matched) {
2312 gDocsMatches[matchedCountDocs] = s;
2313 matchedCountDocs++;
2314 }
2315 }
2316
2317
2318 // Search for Samples
2319 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2320 // current search comparison, with counters for tag and title,
2321 // used later to improve ranking
2322 var s = SAMPLES_RESOURCES[i];
2323 s.matched_tag = 0;
2324 s.matched_title = 0;
2325 var matched = false;
2326 // Check if query matches any tags; work backwards toward 1 to assist ranking
2327 for (var j = s.keywords.length - 1; j >= 0; j--) {
2328 // it matches a tag
2329 if (s.keywords[j].toLowerCase().match(textRegex)) {
2330 matched = true;
2331 s.matched_tag = j + 1; // add 1 to index position
2332 }
2333 }
2334 // Check if query matches the doc title, but only for current language
2335 if (s.lang == currentLang) {
2336 // if query matches the doc title.t
2337 if (s.title.toLowerCase().match(textRegex)) {
2338 matched = true;
2339 s.matched_title = 1;
2340 }
2341 }
2342 if (matched) {
2343 gDocsMatches[matchedCountDocs] = s;
2344 matchedCountDocs++;
2345 }
2346 }
2347
Joe Fernandeza9d796a2015-05-05 22:07:42 -07002348 // Search for Preview Guides
2349 for (var i=0; i<PREVIEW_RESOURCES.length; i++) {
2350 // current search comparison, with counters for tag and title,
2351 // used later to improve ranking
2352 var s = PREVIEW_RESOURCES[i];
2353 s.matched_tag = 0;
2354 s.matched_title = 0;
2355 var matched = false;
2356
2357 // Check if query matches any tags; work backwards toward 1 to assist ranking
2358 for (var j = s.keywords.length - 1; j >= 0; j--) {
2359 // it matches a tag
2360 if (s.keywords[j].toLowerCase().match(textRegex)) {
2361 matched = true;
2362 s.matched_tag = j + 1; // add 1 to index position
2363 }
2364 }
2365 // Check if query matches the doc title, but only for current language
2366 if (s.lang == currentLang) {
2367 // if query matches the doc title
2368 if (s.title.toLowerCase().match(textRegex)) {
2369 matched = true;
2370 s.matched_title = 1;
2371 }
2372 }
2373 if (matched) {
2374 gDocsMatches[matchedCountDocs] = s;
2375 matchedCountDocs++;
2376 }
2377 }
2378
Scott Main719acb42013-12-05 16:05:09 -08002379 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002380 rank_autocomplete_doc_results(text, gDocsMatches);
2381 }
2382
2383 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002384 sync_selection_table(toroot);
2385 return true; // allow the event to bubble up to the search api
2386 }
2387}
2388
Scott Main0e76e7e2013-03-12 10:24:07 -07002389/* Order the jd doc result list based on match quality */
2390function rank_autocomplete_doc_results(query, matches) {
2391 query = query || '';
2392 if (!matches || !matches.length)
2393 return;
2394
2395 var _resultScoreFn = function(match) {
2396 var score = 1.0;
2397
2398 // if the query matched a tag
2399 if (match.matched_tag > 0) {
2400 // multiply score by factor relative to position in tags list (max of 3)
2401 score *= 3 / match.matched_tag;
2402
2403 // if it also matched the title
2404 if (match.matched_title > 0) {
2405 score *= 2;
2406 }
2407 } else if (match.matched_title > 0) {
2408 score *= 3;
2409 }
2410
2411 return score;
2412 };
2413
2414 for (var i=0; i<matches.length; i++) {
2415 matches[i].__resultScore = _resultScoreFn(matches[i]);
2416 }
2417
2418 matches.sort(function(a,b){
2419 var n = b.__resultScore - a.__resultScore;
2420 if (n == 0) // lexicographical sort if scores are the same
2421 n = (a.label < b.label) ? -1 : 1;
2422 return n;
2423 });
2424}
2425
Scott Main7e447ed2013-02-19 17:22:37 -08002426/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002427function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002428 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002429 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002430 return;
2431
2432 // helper function that gets the last occurence index of the given regex
2433 // in the given string, or -1 if not found
2434 var _lastSearch = function(s, re) {
2435 if (s == '')
2436 return -1;
2437 var l = -1;
2438 var tmp;
2439 while ((tmp = s.search(re)) >= 0) {
2440 if (l < 0) l = 0;
2441 l += tmp;
2442 s = s.substr(tmp + 1);
2443 }
2444 return l;
2445 };
2446
2447 // helper function that counts the occurrences of a given character in
2448 // a given string
2449 var _countChar = function(s, c) {
2450 var n = 0;
2451 for (var i=0; i<s.length; i++)
2452 if (s.charAt(i) == c) ++n;
2453 return n;
2454 };
2455
2456 var queryLower = query.toLowerCase();
2457 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2458 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2459 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2460
2461 var _resultScoreFn = function(result) {
2462 // scores are calculated based on exact and prefix matches,
2463 // and then number of path separators (dots) from the last
2464 // match (i.e. favoring classes and deep package names)
2465 var score = 1.0;
2466 var labelLower = result.label.toLowerCase();
2467 var t;
2468 t = _lastSearch(labelLower, partExactAlnumRE);
2469 if (t >= 0) {
2470 // exact part match
2471 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2472 score *= 200 / (partsAfter + 1);
2473 } else {
2474 t = _lastSearch(labelLower, partPrefixAlnumRE);
2475 if (t >= 0) {
2476 // part prefix match
2477 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2478 score *= 20 / (partsAfter + 1);
2479 }
2480 }
2481
2482 return score;
2483 };
2484
Scott Main7e447ed2013-02-19 17:22:37 -08002485 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002486 // if the API is deprecated, default score is 0; otherwise, perform scoring
2487 if (matches[i].deprecated == "true") {
2488 matches[i].__resultScore = 0;
2489 } else {
2490 matches[i].__resultScore = _resultScoreFn(matches[i]);
2491 }
Scott Mainf5089842012-08-14 16:31:07 -07002492 }
2493
Scott Main7e447ed2013-02-19 17:22:37 -08002494 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002495 var n = b.__resultScore - a.__resultScore;
2496 if (n == 0) // lexicographical sort if scores are the same
2497 n = (a.label < b.label) ? -1 : 1;
2498 return n;
2499 });
2500}
2501
Scott Main7e447ed2013-02-19 17:22:37 -08002502/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002503function highlight_autocomplete_result_labels(query) {
2504 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002505 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002506 return;
2507
2508 var queryLower = query.toLowerCase();
2509 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2510 var queryRE = new RegExp(
2511 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2512 for (var i=0; i<gMatches.length; i++) {
2513 gMatches[i].__hilabel = gMatches[i].label.replace(
2514 queryRE, '<b>$1</b>');
2515 }
Scott Main7e447ed2013-02-19 17:22:37 -08002516 for (var i=0; i<gGoogleMatches.length; i++) {
2517 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2518 queryRE, '<b>$1</b>');
2519 }
Scott Mainf5089842012-08-14 16:31:07 -07002520}
2521
2522function search_focus_changed(obj, focused)
2523{
Scott Main3b90aff2013-08-01 18:09:35 -07002524 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002525 if(obj.value == ""){
Dirk Dougherty29e93432015-05-05 18:17:13 -07002526 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002527 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002528 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002529 }
2530}
2531
2532function submit_search() {
2533 var query = document.getElementById('search_autocomplete').value;
2534 location.hash = 'q=' + query;
2535 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002536 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002537 return false;
2538}
2539
2540
2541function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002542 $("#searchResults").slideUp('fast', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002543 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002544 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002545
Scott Mainf5089842012-08-14 16:31:07 -07002546 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002547
Scott Mainf5089842012-08-14 16:31:07 -07002548 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2549 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002550
2551 // forcefully regain key-up event control (previously jacked by search api)
2552 $("#search_autocomplete").keyup(function(event) {
2553 return search_changed(event, false, toRoot);
2554 });
2555
Scott Mainf5089842012-08-14 16:31:07 -07002556 return false;
2557}
2558
2559
2560
2561/* ########################################################## */
2562/* ################ CUSTOM SEARCH ENGINE ################## */
2563/* ########################################################## */
2564
Scott Mainf5089842012-08-14 16:31:07 -07002565var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002566google.load('search', '1', {"callback" : function() {
2567 searchControl = new google.search.SearchControl();
2568 } });
Scott Mainf5089842012-08-14 16:31:07 -07002569
2570function loadSearchResults() {
2571 document.getElementById("search_autocomplete").style.color = "#000";
2572
Scott Mainf5089842012-08-14 16:31:07 -07002573 searchControl = new google.search.SearchControl();
2574
2575 // use our existing search form and use tabs when multiple searchers are used
2576 drawOptions = new google.search.DrawOptions();
2577 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2578 drawOptions.setInput(document.getElementById("search_autocomplete"));
2579
2580 // configure search result options
2581 searchOptions = new google.search.SearcherOptions();
2582 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2583
2584 // configure each of the searchers, for each tab
2585 devSiteSearcher = new google.search.WebSearch();
2586 devSiteSearcher.setUserDefinedLabel("All");
2587 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2588
2589 designSearcher = new google.search.WebSearch();
2590 designSearcher.setUserDefinedLabel("Design");
2591 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2592
2593 trainingSearcher = new google.search.WebSearch();
2594 trainingSearcher.setUserDefinedLabel("Training");
2595 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2596
2597 guidesSearcher = new google.search.WebSearch();
2598 guidesSearcher.setUserDefinedLabel("Guides");
2599 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2600
2601 referenceSearcher = new google.search.WebSearch();
2602 referenceSearcher.setUserDefinedLabel("Reference");
2603 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2604
Scott Maindf08ada2012-12-03 08:54:37 -08002605 googleSearcher = new google.search.WebSearch();
2606 googleSearcher.setUserDefinedLabel("Google Services");
2607 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2608
Scott Mainf5089842012-08-14 16:31:07 -07002609 blogSearcher = new google.search.WebSearch();
2610 blogSearcher.setUserDefinedLabel("Blog");
2611 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2612
2613 // add each searcher to the search control
2614 searchControl.addSearcher(devSiteSearcher, searchOptions);
2615 searchControl.addSearcher(designSearcher, searchOptions);
2616 searchControl.addSearcher(trainingSearcher, searchOptions);
2617 searchControl.addSearcher(guidesSearcher, searchOptions);
2618 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002619 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002620 searchControl.addSearcher(blogSearcher, searchOptions);
2621
2622 // configure result options
2623 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2624 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2625 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2626 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2627
2628 // upon ajax search, refresh the url and search title
2629 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2630 updateResultTitle(query);
2631 var query = document.getElementById('search_autocomplete').value;
2632 location.hash = 'q=' + query;
2633 });
2634
Scott Mainde295272013-03-25 15:48:35 -07002635 // once search results load, set up click listeners
2636 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2637 addResultClickListeners();
2638 });
2639
Scott Mainf5089842012-08-14 16:31:07 -07002640 // draw the search results box
2641 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2642
2643 // get query and execute the search
2644 searchControl.execute(decodeURI(getQuery(location.hash)));
2645
2646 document.getElementById("search_autocomplete").focus();
2647 addTabListeners();
2648}
2649// End of loadSearchResults
2650
2651
2652google.setOnLoadCallback(function(){
2653 if (location.hash.indexOf("q=") == -1) {
2654 // if there's no query in the url, don't search and make sure results are hidden
2655 $('#searchResults').hide();
2656 return;
2657 } else {
2658 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002659 $('#searchResults').slideDown('slow', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002660 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002661 loadSearchResults();
2662 }
2663}, true);
2664
smain@google.com9a818f52014-10-03 09:25:59 -07002665/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2666 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002667function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002668 // Ignore if there's no search bar (some special pages have no header)
2669 if ($("#search-container").length < 1) return;
2670
smain@google.com3b77ab52014-06-17 11:57:27 -07002671 var hash = escape(location.hash.substr(1));
2672 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002673 // Sanity check that there's an element with that ID on the page
2674 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002675 // If the position of the target element is near the top of the page (<20px, where we expect it
2676 // to be because we need to move it down 60px to become in view), then move it down 60px
2677 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2678 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002679 }
2680 }
2681}
2682
Scott Mainf5089842012-08-14 16:31:07 -07002683// when an event on the browser history occurs (back, forward, load) requery hash and do search
2684$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002685 // Ignore if there's no search bar (some special pages have no header)
2686 if ($("#search-container").length < 1) return;
2687
Dirk Doughertyc3921652014-05-13 16:55:26 -07002688 // If the hash isn't a search query or there's an error in the query,
2689 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002690 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2691 // If the results pane is open, close it.
2692 if (!$("#searchResults").is(":hidden")) {
2693 hideResults();
2694 }
Scott Mainb16376f2014-05-21 20:35:47 -07002695 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002696 return;
2697 }
2698
2699 // Otherwise, we have a search to do
2700 var query = decodeURI(getQuery(location.hash));
2701 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002702 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002703 $("#search_autocomplete").focus();
Dirk Dougherty29e93432015-05-05 18:17:13 -07002704 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002705
2706 updateResultTitle(query);
2707});
2708
2709function updateResultTitle(query) {
2710 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2711}
2712
2713// forcefully regain key-up event control (previously jacked by search api)
2714$("#search_autocomplete").keyup(function(event) {
2715 return search_changed(event, false, toRoot);
2716});
2717
2718// add event listeners to each tab so we can track the browser history
2719function addTabListeners() {
2720 var tabHeaders = $(".gsc-tabHeader");
2721 for (var i = 0; i < tabHeaders.length; i++) {
2722 $(tabHeaders[i]).attr("id",i).click(function() {
2723 /*
2724 // make a copy of the page numbers for the search left pane
2725 setTimeout(function() {
2726 // remove any residual page numbers
2727 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002728 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002729 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002730 // and because we're going to remove it (previous line),
2731 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002732 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2733 .clone().appendTo('#searchResults .gsc-tabsArea');
2734 }, 200);
2735 */
2736 });
2737 }
2738 setTimeout(function(){$(tabHeaders[0]).click()},200);
2739}
2740
Scott Mainde295272013-03-25 15:48:35 -07002741// add analytics tracking events to each result link
2742function addResultClickListeners() {
2743 $("#searchResults a.gs-title").each(function(index, link) {
2744 // When user clicks enter for Google search results, track it
2745 $(link).click(function() {
smain@google.comed677d72014-12-12 10:19:17 -08002746 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'),
2747 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07002748 });
2749 });
2750}
2751
Scott Mainf5089842012-08-14 16:31:07 -07002752
2753function getQuery(hash) {
2754 var queryParts = hash.split('=');
2755 return queryParts[1];
2756}
2757
2758/* returns the given string with all HTML brackets converted to entities
2759 TODO: move this to the site's JS library */
2760function escapeHTML(string) {
2761 return string.replace(/</g,"&lt;")
2762 .replace(/>/g,"&gt;");
2763}
2764
2765
2766
2767
2768
2769
2770
2771/* ######################################################## */
2772/* ################# JAVADOC REFERENCE ################### */
2773/* ######################################################## */
2774
Scott Main65511c02012-09-07 15:51:32 -07002775/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002776if (location.pathname.indexOf("/reference") == 0) {
2777 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2778 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2779 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002780 $(document).ready(function() {
2781 // init available apis based on user pref
2782 changeApiLevel();
2783 initSidenavHeightResize()
2784 });
2785 }
Scott Main65511c02012-09-07 15:51:32 -07002786}
Scott Mainf5089842012-08-14 16:31:07 -07002787
2788var API_LEVEL_COOKIE = "api_level";
2789var minLevel = 1;
2790var maxLevel = 1;
2791
2792/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002793
Scott Mainf5089842012-08-14 16:31:07 -07002794 function initSidenavHeightResize() {
2795 // Change the drag bar size to nicely fit the scrollbar positions
2796 var $dragBar = $(".ui-resizable-s");
2797 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002798
2799 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002800 containment: "#nav-panels",
2801 handles: "s",
2802 alsoResize: "#packages-nav",
2803 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2804 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2805 });
Scott Main3b90aff2013-08-01 18:09:35 -07002806
Scott Mainf5089842012-08-14 16:31:07 -07002807 }
Scott Main3b90aff2013-08-01 18:09:35 -07002808
Scott Mainf5089842012-08-14 16:31:07 -07002809function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002810 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002811 $('#devdoc-nav').css({
2812 'width' : $('#side-nav').css('width'),
2813 'margin' : $('#side-nav').css('margin')
2814 });
2815 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002816
Scott Mainf5089842012-08-14 16:31:07 -07002817 initSidenavHeightResize();
2818}
2819
2820function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002821 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002822 $('#devdoc-nav').css({
2823 'width' : $('#side-nav').css('width'),
2824 'margin' : $('#side-nav').css('margin')
2825 });
2826 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002827
Scott Mainf5089842012-08-14 16:31:07 -07002828 initSidenavHeightResize();
2829}
2830
2831function buildApiLevelSelector() {
2832 maxLevel = SINCE_DATA.length;
2833 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2834 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2835
2836 minLevel = parseInt($("#doc-api-level").attr("class"));
2837 // Handle provisional api levels; the provisional level will always be the highest possible level
2838 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2839 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2840 if (isNaN(minLevel) && minLevel.length) {
2841 minLevel = maxLevel;
2842 }
2843 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2844 for (var i = maxLevel-1; i >= 0; i--) {
2845 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2846 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2847 select.append(option);
2848 }
2849
2850 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2851 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2852 selectedLevelItem.setAttribute('selected',true);
2853}
2854
2855function changeApiLevel() {
2856 maxLevel = SINCE_DATA.length;
2857 var selectedLevel = maxLevel;
2858
2859 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2860 toggleVisisbleApis(selectedLevel, "body");
2861
smain@google.com6bdcb982014-11-14 11:53:07 -08002862 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002863
2864 if (selectedLevel < minLevel) {
2865 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002866 $("#naMessage").show().html("<div><p><strong>This " + thing
2867 + " requires API level " + minLevel + " or higher.</strong></p>"
2868 + "<p>This document is hidden because your selected API level for the documentation is "
2869 + selectedLevel + ". You can change the documentation API level with the selector "
2870 + "above the left navigation.</p>"
2871 + "<p>For more information about specifying the API level your app requires, "
2872 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2873 + ">Supporting Different Platform Versions</a>.</p>"
2874 + "<input type='button' value='OK, make this page visible' "
2875 + "title='Change the API level to " + minLevel + "' "
2876 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2877 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002878 } else {
2879 $("#naMessage").hide();
2880 }
2881}
2882
2883function toggleVisisbleApis(selectedLevel, context) {
2884 var apis = $(".api",context);
2885 apis.each(function(i) {
2886 var obj = $(this);
2887 var className = obj.attr("class");
2888 var apiLevelIndex = className.lastIndexOf("-")+1;
2889 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2890 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2891 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2892 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2893 return;
2894 }
2895 apiLevel = parseInt(apiLevel);
2896
2897 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2898 var selectedLevelNum = parseInt(selectedLevel)
2899 var apiLevelNum = parseInt(apiLevel);
2900 if (isNaN(apiLevelNum)) {
2901 apiLevelNum = maxLevel;
2902 }
2903
2904 // Grey things out that aren't available and give a tooltip title
2905 if (apiLevelNum > selectedLevelNum) {
2906 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002907 + apiLevel + "\" or higher. To reveal, change the target API level "
2908 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002909 }
Scott Mainf5089842012-08-14 16:31:07 -07002910 else obj.removeClass("absent").removeAttr("title");
2911 });
2912}
2913
2914
2915
2916
2917/* ################# SIDENAV TREE VIEW ################### */
2918
2919function new_node(me, mom, text, link, children_data, api_level)
2920{
2921 var node = new Object();
2922 node.children = Array();
2923 node.children_data = children_data;
2924 node.depth = mom.depth + 1;
2925
2926 node.li = document.createElement("li");
2927 mom.get_children_ul().appendChild(node.li);
2928
2929 node.label_div = document.createElement("div");
2930 node.label_div.className = "label";
2931 if (api_level != null) {
2932 $(node.label_div).addClass("api");
2933 $(node.label_div).addClass("api-level-"+api_level);
2934 }
2935 node.li.appendChild(node.label_div);
2936
2937 if (children_data != null) {
2938 node.expand_toggle = document.createElement("a");
2939 node.expand_toggle.href = "javascript:void(0)";
2940 node.expand_toggle.onclick = function() {
2941 if (node.expanded) {
2942 $(node.get_children_ul()).slideUp("fast");
2943 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2944 node.expanded = false;
2945 } else {
2946 expand_node(me, node);
2947 }
2948 };
2949 node.label_div.appendChild(node.expand_toggle);
2950
2951 node.plus_img = document.createElement("img");
2952 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2953 node.plus_img.className = "plus";
2954 node.plus_img.width = "8";
2955 node.plus_img.border = "0";
2956 node.expand_toggle.appendChild(node.plus_img);
2957
2958 node.expanded = false;
2959 }
2960
2961 var a = document.createElement("a");
2962 node.label_div.appendChild(a);
2963 node.label = document.createTextNode(text);
2964 a.appendChild(node.label);
2965 if (link) {
2966 a.href = me.toroot + link;
2967 } else {
2968 if (children_data != null) {
2969 a.className = "nolink";
2970 a.href = "javascript:void(0)";
2971 a.onclick = node.expand_toggle.onclick;
2972 // This next line shouldn't be necessary. I'll buy a beer for the first
2973 // person who figures out how to remove this line and have the link
2974 // toggle shut on the first try. --joeo@android.com
2975 node.expanded = false;
2976 }
2977 }
Scott Main3b90aff2013-08-01 18:09:35 -07002978
Scott Mainf5089842012-08-14 16:31:07 -07002979
2980 node.children_ul = null;
2981 node.get_children_ul = function() {
2982 if (!node.children_ul) {
2983 node.children_ul = document.createElement("ul");
2984 node.children_ul.className = "children_ul";
2985 node.children_ul.style.display = "none";
2986 node.li.appendChild(node.children_ul);
2987 }
2988 return node.children_ul;
2989 };
2990
2991 return node;
2992}
2993
Robert Lyd2dd6e52012-11-29 21:28:48 -08002994
2995
2996
Scott Mainf5089842012-08-14 16:31:07 -07002997function expand_node(me, node)
2998{
2999 if (node.children_data && !node.expanded) {
3000 if (node.children_visited) {
3001 $(node.get_children_ul()).slideDown("fast");
3002 } else {
3003 get_node(me, node);
3004 if ($(node.label_div).hasClass("absent")) {
3005 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07003006 }
Scott Mainf5089842012-08-14 16:31:07 -07003007 $(node.get_children_ul()).slideDown("fast");
3008 }
3009 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
3010 node.expanded = true;
3011
3012 // perform api level toggling because new nodes are new to the DOM
3013 var selectedLevel = $("#apiLevelSelector option:selected").val();
3014 toggleVisisbleApis(selectedLevel, "#side-nav");
3015 }
3016}
3017
3018function get_node(me, mom)
3019{
3020 mom.children_visited = true;
3021 for (var i in mom.children_data) {
3022 var node_data = mom.children_data[i];
3023 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
3024 node_data[2], node_data[3]);
3025 }
3026}
3027
3028function this_page_relative(toroot)
3029{
3030 var full = document.location.pathname;
3031 var file = "";
3032 if (toroot.substr(0, 1) == "/") {
3033 if (full.substr(0, toroot.length) == toroot) {
3034 return full.substr(toroot.length);
3035 } else {
3036 // the file isn't under toroot. Fail.
3037 return null;
3038 }
3039 } else {
3040 if (toroot != "./") {
3041 toroot = "./" + toroot;
3042 }
3043 do {
3044 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3045 var pos = full.lastIndexOf("/");
3046 file = full.substr(pos) + file;
3047 full = full.substr(0, pos);
3048 toroot = toroot.substr(0, toroot.length-3);
3049 }
3050 } while (toroot != "" && toroot != "/");
3051 return file.substr(1);
3052 }
3053}
3054
3055function find_page(url, data)
3056{
3057 var nodes = data;
3058 var result = null;
3059 for (var i in nodes) {
3060 var d = nodes[i];
3061 if (d[1] == url) {
3062 return new Array(i);
3063 }
3064 else if (d[2] != null) {
3065 result = find_page(url, d[2]);
3066 if (result != null) {
3067 return (new Array(i).concat(result));
3068 }
3069 }
3070 }
3071 return null;
3072}
3073
Scott Mainf5089842012-08-14 16:31:07 -07003074function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07003075 // load json file for navtree data
3076 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3077 // when the file is loaded, initialize the tree
3078 if(jqxhr.status === 200) {
3079 init_navtree("tree-list", toroot, NAVTREE_DATA);
3080 }
3081 });
Scott Main3b90aff2013-08-01 18:09:35 -07003082
Scott Mainf5089842012-08-14 16:31:07 -07003083 // perform api level toggling because because the whole tree is new to the DOM
3084 var selectedLevel = $("#apiLevelSelector option:selected").val();
3085 toggleVisisbleApis(selectedLevel, "#side-nav");
3086}
3087
3088function init_navtree(navtree_id, toroot, root_nodes)
3089{
3090 var me = new Object();
3091 me.toroot = toroot;
3092 me.node = new Object();
3093
3094 me.node.li = document.getElementById(navtree_id);
3095 me.node.children_data = root_nodes;
3096 me.node.children = new Array();
3097 me.node.children_ul = document.createElement("ul");
3098 me.node.get_children_ul = function() { return me.node.children_ul; };
3099 //me.node.children_ul.className = "children_ul";
3100 me.node.li.appendChild(me.node.children_ul);
3101 me.node.depth = 0;
3102
3103 get_node(me, me.node);
3104
3105 me.this_page = this_page_relative(toroot);
3106 me.breadcrumbs = find_page(me.this_page, root_nodes);
3107 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3108 var mom = me.node;
3109 for (var i in me.breadcrumbs) {
3110 var j = me.breadcrumbs[i];
3111 mom = mom.children[j];
3112 expand_node(me, mom);
3113 }
3114 mom.label_div.className = mom.label_div.className + " selected";
3115 addLoadEvent(function() {
3116 scrollIntoView("nav-tree");
3117 });
3118 }
3119}
3120
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003121
3122
3123
3124
3125
3126
3127
Robert Lyd2dd6e52012-11-29 21:28:48 -08003128/* TODO: eliminate redundancy with non-google functions */
3129function init_google_navtree(navtree_id, toroot, root_nodes)
3130{
3131 var me = new Object();
3132 me.toroot = toroot;
3133 me.node = new Object();
3134
3135 me.node.li = document.getElementById(navtree_id);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003136 if (!me.node.li) {
3137 return;
3138 }
3139
Robert Lyd2dd6e52012-11-29 21:28:48 -08003140 me.node.children_data = root_nodes;
3141 me.node.children = new Array();
3142 me.node.children_ul = document.createElement("ul");
3143 me.node.get_children_ul = function() { return me.node.children_ul; };
3144 //me.node.children_ul.className = "children_ul";
3145 me.node.li.appendChild(me.node.children_ul);
3146 me.node.depth = 0;
3147
3148 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003149}
3150
3151function new_google_node(me, mom, text, link, children_data, api_level)
3152{
3153 var node = new Object();
3154 var child;
3155 node.children = Array();
3156 node.children_data = children_data;
3157 node.depth = mom.depth + 1;
3158 node.get_children_ul = function() {
3159 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003160 node.children_ul = document.createElement("ul");
3161 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003162 node.li.appendChild(node.children_ul);
3163 }
3164 return node.children_ul;
3165 };
3166 node.li = document.createElement("li");
3167
3168 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003169
3170
Robert Lyd2dd6e52012-11-29 21:28:48 -08003171 if(link) {
3172 child = document.createElement("a");
3173
3174 }
3175 else {
3176 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003177 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003178
3179 }
3180 if (children_data != null) {
3181 node.li.className="nav-section";
3182 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003183 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003184 node.li.appendChild(node.label_div);
3185 get_google_node(me, node);
3186 node.label_div.appendChild(child);
3187 }
3188 else {
3189 node.li.appendChild(child);
3190 }
3191 if(link) {
3192 child.href = me.toroot + link;
3193 }
3194 node.label = document.createTextNode(text);
3195 child.appendChild(node.label);
3196
3197 node.children_ul = null;
3198
3199 return node;
3200}
3201
3202function get_google_node(me, mom)
3203{
3204 mom.children_visited = true;
3205 var linkText;
3206 for (var i in mom.children_data) {
3207 var node_data = mom.children_data[i];
3208 linkText = node_data[0];
3209
3210 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3211 linkText = linkText.substr(19, linkText.length);
3212 }
3213 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3214 node_data[2], node_data[3]);
3215 }
3216}
Scott Mainad08f072013-08-20 16:49:57 -07003217
3218
3219
3220
3221
3222
3223/****** NEW version of script to build google and sample navs dynamically ******/
3224// TODO: update Google reference docs to tolerate this new implementation
3225
Scott Maine624b3f2013-09-12 12:56:41 -07003226var NODE_NAME = 0;
3227var NODE_HREF = 1;
3228var NODE_GROUP = 2;
3229var NODE_TAGS = 3;
3230var NODE_CHILDREN = 4;
3231
Scott Mainad08f072013-08-20 16:49:57 -07003232function init_google_navtree2(navtree_id, data)
3233{
3234 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003235 for (var i in data) {
3236 var node_data = data[i];
3237 $containerUl.append(new_google_node2(node_data));
3238 }
3239
Scott Main70557ee2013-10-30 14:47:40 -07003240 // Make all third-generation list items 'sticky' to prevent them from collapsing
3241 $containerUl.find('li li li.nav-section').addClass('sticky');
3242
Scott Mainad08f072013-08-20 16:49:57 -07003243 initExpandableNavItems("#"+navtree_id);
3244}
3245
3246function new_google_node2(node_data)
3247{
Scott Maine624b3f2013-09-12 12:56:41 -07003248 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003249 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3250 linkText = linkText.substr(19, linkText.length);
3251 }
3252 var $li = $('<li>');
3253 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003254 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003255 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3256 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003257 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003258 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3259 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003260 }
3261 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003262 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003263 $li.addClass("nav-section");
3264 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003265 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003266
Scott Maine624b3f2013-09-12 12:56:41 -07003267 for (var i in node_data[NODE_CHILDREN]) {
3268 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003269 $childUl.append(new_google_node2(child_node_data));
3270 }
3271 $li.append($childUl);
3272 }
3273 $li.prepend($a);
3274
3275 return $li;
3276}
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
Robert Lyd2dd6e52012-11-29 21:28:48 -08003288function showGoogleRefTree() {
3289 init_default_google_navtree(toRoot);
3290 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003291}
3292
3293function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003294 // load json file for navtree data
3295 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3296 // when the file is loaded, initialize the tree
3297 if(jqxhr.status === 200) {
3298 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3299 highlightSidenav();
3300 resizeNav();
3301 }
3302 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003303}
3304
3305function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003306 // load json file for navtree data
3307 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3308 // when the file is loaded, initialize the tree
3309 if(jqxhr.status === 200) {
3310 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3311 highlightSidenav();
3312 resizeNav();
3313 }
3314 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003315}
3316
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003317function showSamplesRefTree() {
3318 init_default_samples_navtree(toRoot);
3319}
3320
3321function init_default_samples_navtree(toroot) {
3322 // load json file for navtree data
3323 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3324 // when the file is loaded, initialize the tree
3325 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003326 // hack to remove the "about the samples" link then put it back in
3327 // after we nuke the list to remove the dummy static list of samples
3328 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3329 $("#nav.samples-nav").empty();
3330 $("#nav.samples-nav").append($firstLi);
3331
Scott Mainad08f072013-08-20 16:49:57 -07003332 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003333 highlightSidenav();
3334 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003335 if ($("#jd-content #samples").length) {
3336 showSamples();
3337 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003338 }
3339 });
3340}
3341
Scott Mainf5089842012-08-14 16:31:07 -07003342/* TOGGLE INHERITED MEMBERS */
3343
3344/* Toggle an inherited class (arrow toggle)
3345 * @param linkObj The link that was clicked.
3346 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3347 * 'null' to simply toggle.
3348 */
3349function toggleInherited(linkObj, expand) {
3350 var base = linkObj.getAttribute("id");
3351 var list = document.getElementById(base + "-list");
3352 var summary = document.getElementById(base + "-summary");
3353 var trigger = document.getElementById(base + "-trigger");
3354 var a = $(linkObj);
3355 if ( (expand == null && a.hasClass("closed")) || expand ) {
3356 list.style.display = "none";
3357 summary.style.display = "block";
3358 trigger.src = toRoot + "assets/images/triangle-opened.png";
3359 a.removeClass("closed");
3360 a.addClass("opened");
3361 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3362 list.style.display = "block";
3363 summary.style.display = "none";
3364 trigger.src = toRoot + "assets/images/triangle-closed.png";
3365 a.removeClass("opened");
3366 a.addClass("closed");
3367 }
3368 return false;
3369}
3370
3371/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3372 * @param linkObj The link that was clicked.
3373 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3374 * 'null' to simply toggle.
3375 */
3376function toggleAllInherited(linkObj, expand) {
3377 var a = $(linkObj);
3378 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3379 var expandos = $(".jd-expando-trigger", table);
3380 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3381 expandos.each(function(i) {
3382 toggleInherited(this, true);
3383 });
3384 a.text("[Collapse]");
3385 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3386 expandos.each(function(i) {
3387 toggleInherited(this, false);
3388 });
3389 a.text("[Expand]");
3390 }
3391 return false;
3392}
3393
3394/* Toggle all inherited members in the class (link in the class title)
3395 */
3396function toggleAllClassInherited() {
3397 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3398 var toggles = $(".toggle-all", $("#body-content"));
3399 if (a.text() == "[Expand All]") {
3400 toggles.each(function(i) {
3401 toggleAllInherited(this, true);
3402 });
3403 a.text("[Collapse All]");
3404 } else {
3405 toggles.each(function(i) {
3406 toggleAllInherited(this, false);
3407 });
3408 a.text("[Expand All]");
3409 }
3410 return false;
3411}
3412
3413/* Expand all inherited members in the class. Used when initiating page search */
3414function ensureAllInheritedExpanded() {
3415 var toggles = $(".toggle-all", $("#body-content"));
3416 toggles.each(function(i) {
3417 toggleAllInherited(this, true);
3418 });
3419 $("#toggleAllClassInherited").text("[Collapse All]");
3420}
3421
3422
3423/* HANDLE KEY EVENTS
3424 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3425 */
3426var agent = navigator['userAgent'].toLowerCase();
3427var mac = agent.indexOf("macintosh") != -1;
3428
3429$(document).keydown( function(e) {
3430var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3431 if (control && e.which == 70) { // 70 is "F"
3432 ensureAllInheritedExpanded();
3433 }
3434});
Scott Main498d7102013-08-21 15:47:38 -07003435
3436
3437
3438
3439
3440
3441/* On-demand functions */
3442
3443/** Move sample code line numbers out of PRE block and into non-copyable column */
3444function initCodeLineNumbers() {
3445 var numbers = $("#codesample-block a.number");
3446 if (numbers.length) {
3447 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3448 }
3449
3450 $(document).ready(function() {
3451 // select entire line when clicked
3452 $("span.code-line").click(function() {
3453 if (!shifted) {
3454 selectText(this);
3455 }
3456 });
3457 // invoke line link on double click
3458 $(".code-line").dblclick(function() {
3459 document.location.hash = $(this).attr('id');
3460 });
3461 // highlight the line when hovering on the number
3462 $("#codesample-line-numbers a.number").mouseover(function() {
3463 var id = $(this).attr('href');
3464 $(id).css('background','#e7e7e7');
3465 });
3466 $("#codesample-line-numbers a.number").mouseout(function() {
3467 var id = $(this).attr('href');
3468 $(id).css('background','none');
3469 });
3470 });
3471}
3472
3473// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3474var shifted = false;
3475$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3476
3477// courtesy of jasonedelman.com
3478function selectText(element) {
3479 var doc = document
3480 , range, selection
3481 ;
3482 if (doc.body.createTextRange) { //ms
3483 range = doc.body.createTextRange();
3484 range.moveToElementText(element);
3485 range.select();
3486 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003487 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003488 range = doc.createRange();
3489 range.selectNodeContents(element);
3490 selection.removeAllRanges();
3491 selection.addRange(range);
3492 }
Scott Main285f0772013-08-22 23:22:09 +00003493}
Scott Main03aca9a2013-10-31 07:20:55 -07003494
3495
3496
3497
3498/** Display links and other information about samples that match the
3499 group specified by the URL */
3500function showSamples() {
3501 var group = $("#samples").attr('class');
3502 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3503
3504 var $ul = $("<ul>");
3505 $selectedLi = $("#nav li.selected");
3506
3507 $selectedLi.children("ul").children("li").each(function() {
3508 var $li = $("<li>").append($(this).find("a").first().clone());
3509 $ul.append($li);
3510 });
3511
3512 $("#samples").append($ul);
3513
3514}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003515
3516
3517
3518/* ########################################################## */
3519/* ################### RESOURCE CARDS ##################### */
3520/* ########################################################## */
3521
3522/** Handle resource queries, collections, and grids (sections). Requires
3523 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3524
3525(function() {
3526 // Prevent the same resource from being loaded more than once per page.
3527 var addedPageResources = {};
3528
3529 $(document).ready(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003530 // Need to initialize hero carousel before other sections for dedupe
3531 // to work correctly.
3532 $('[data-carousel-query]').dacCarouselQuery();
3533
Dirk Doughertyc3921652014-05-13 16:55:26 -07003534 $('.resource-widget').each(function() {
3535 initResourceWidget(this);
3536 });
3537
3538 /* Pass the line height to ellipsisfade() to adjust the height of the
3539 text container to show the max number of lines possible, without
3540 showing lines that are cut off. This works with the css ellipsis
3541 classes to fade last text line and apply an ellipsis char. */
3542
Dirk Dougherty29e93432015-05-05 18:17:13 -07003543 //card text currently uses 20px line height.
3544 var lineHeight = 20;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003545 $('.card-info .text').ellipsisfade(lineHeight);
3546 });
3547
3548 /*
3549 Three types of resource layouts:
3550 Flow - Uses a fixed row-height flow using float left style.
3551 Carousel - Single card slideshow all same dimension absolute.
3552 Stack - Uses fixed columns and flexible element height.
3553 */
3554 function initResourceWidget(widget) {
3555 var $widget = $(widget);
3556 var isFlow = $widget.hasClass('resource-flow-layout'),
3557 isCarousel = $widget.hasClass('resource-carousel-layout'),
3558 isStack = $widget.hasClass('resource-stack-layout');
3559
Dirk Dougherty29e93432015-05-05 18:17:13 -07003560 // remove illegal col-x class which is not relevant anymore thanks to responsive styles.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003561 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
Dirk Dougherty29e93432015-05-05 18:17:13 -07003562 if (m && !$widget.is('.cols > *')) {
3563 $widget.removeClass('col-' + m[1]);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003564 }
3565
3566 var opts = {
3567 cardSizes: ($widget.data('cardsizes') || '').split(','),
3568 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3569 itemsPerPage: $widget.data('itemsperpage'),
3570 sortOrder: $widget.data('sortorder'),
3571 query: $widget.data('query'),
3572 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003573 /* Added by LFL 6/6/14 */
3574 resourceStyle: $widget.data('resourcestyle') || 'card',
3575 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003576 };
3577
3578 // run the search for the set of resources to show
3579
3580 var resources = buildResourceList(opts);
3581
3582 if (isFlow) {
3583 drawResourcesFlowWidget($widget, opts, resources);
3584 } else if (isCarousel) {
3585 drawResourcesCarouselWidget($widget, opts, resources);
3586 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003587 /* Looks like this got removed and is not used, so repurposing for the
3588 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003589 Modified by LFL 6/6/14
3590 */
3591 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003592 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003593 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003594 }
3595 }
3596
3597 /* Initializes a Resource Carousel Widget */
3598 function drawResourcesCarouselWidget($widget, opts, resources) {
3599 $widget.empty();
Dirk Dougherty29e93432015-05-05 18:17:13 -07003600 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003601
3602 $widget.addClass('resource-card slideshow-container')
3603 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3604 .append($('<a>').addClass('slideshow-next').text('Next'));
3605
3606 var css = { 'width': $widget.width() + 'px',
3607 'height': $widget.height() + 'px' };
3608
3609 var $ul = $('<ul>');
3610
3611 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003612 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003613 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003614 .decorateResourceCard(resources[i],plusone);
3615
3616 $('<li>').css(css)
3617 .append($card)
3618 .appendTo($ul);
3619 }
3620
3621 $('<div>').addClass('frame')
3622 .append($ul)
3623 .appendTo($widget);
3624
3625 $widget.dacSlideshow({
3626 auto: true,
3627 btnPrev: '.slideshow-prev',
3628 btnNext: '.slideshow-next'
3629 });
3630 };
3631
Robert Lye7eeb402014-06-03 19:35:24 -07003632 /* Initializes a Resource Card Stack Widget (column-based layout)
3633 Modified by LFL 6/6/14
3634 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003635 function drawResourcesStackWidget($widget, opts, resources, sections) {
3636 // Don't empty widget, grab all items inside since they will be the first
3637 // items stacked, followed by the resource query
Dirk Dougherty29e93432015-05-05 18:17:13 -07003638 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003639 var cards = $widget.find('.resource-card').detach().toArray();
3640 var numStacks = opts.numStacks || 1;
3641 var $stacks = [];
3642 var urlString;
3643
3644 for (var i = 0; i < numStacks; ++i) {
3645 $stacks[i] = $('<div>').addClass('resource-card-stack')
3646 .appendTo($widget);
3647 }
3648
3649 var sectionResources = [];
3650
3651 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003652 if (sections) {
3653 for (var i = 0; i < sections.length; ++i) {
3654 if (!sections[i].sections || !sections[i].sections.length) {
3655 // Render it as a resource card
3656 sectionResources.push(
3657 $('<a>')
3658 .addClass('resource-card section-card')
3659 .attr('href', cleanUrl(sections[i].resource.url))
3660 .decorateResourceCard(sections[i].resource,plusone)[0]
3661 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003662
Robert Lye7eeb402014-06-03 19:35:24 -07003663 } else {
3664 cards.push(
3665 $('<div>')
3666 .addClass('resource-card section-card-menu')
3667 .decorateResourceSection(sections[i],plusone)[0]
3668 );
3669 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003670 }
3671 }
3672
3673 cards = cards.concat(sectionResources);
3674
3675 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003676 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003677
Robert Lye7eeb402014-06-03 19:35:24 -07003678 if (opts.resourceStyle.indexOf('related') > -1) {
3679 $card.addClass('related-card');
3680 }
smain@google.com95948b82014-06-16 19:24:25 -07003681
Dirk Doughertyc3921652014-05-13 16:55:26 -07003682 cards.push($card[0]);
3683 }
3684
Robert Lye7eeb402014-06-03 19:35:24 -07003685 if (opts.stackSort != 'false') {
3686 for (var i = 0; i < cards.length; ++i) {
3687 // Find the stack with the shortest height, but give preference to
3688 // left to right order.
3689 var minHeight = $stacks[0].height();
3690 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003691
Robert Lye7eeb402014-06-03 19:35:24 -07003692 for (var j = 1; j < numStacks; ++j) {
3693 var height = $stacks[j].height();
3694 if (height < minHeight - 45) {
3695 minHeight = height;
3696 minIndex = j;
3697 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003698 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003699
Robert Lye7eeb402014-06-03 19:35:24 -07003700 $stacks[minIndex].append($(cards[i]));
3701 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003702 }
3703
3704 };
smain@google.com95948b82014-06-16 19:24:25 -07003705
3706 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003707 Create a resource card using the given resource object and a list of html
3708 configured options. Returns a jquery object containing the element.
3709 */
smain@google.com95948b82014-06-16 19:24:25 -07003710 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003711 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003712
Robert Lye7eeb402014-06-03 19:35:24 -07003713 // The difference here is that generic cards are not entirely clickable
3714 // so its a div instead of an a tag, also the generic one is not given
3715 // the resource-card class so it appears with a transparent background
3716 // and can be styled in whatever way the css setup.
3717 if (opts.resourceStyle == 'generic') {
3718 $el = $('<div>')
3719 .addClass('resource')
3720 .attr('href', cleanUrl(resource.url))
3721 .decorateResource(resource, opts);
3722 } else {
3723 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003724
Robert Lye7eeb402014-06-03 19:35:24 -07003725 $el = $('<a>')
3726 .addClass(cls)
3727 .attr('href', cleanUrl(resource.url))
3728 .decorateResourceCard(resource, plusone);
3729 }
smain@google.com95948b82014-06-16 19:24:25 -07003730
Robert Lye7eeb402014-06-03 19:35:24 -07003731 return $el;
3732 }
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003733
Dirk Dougherty29e93432015-05-05 18:17:13 -07003734 function createResponsiveFlowColumn(cardSize) {
3735 var cardWidth = parseInt(cardSize.match(/(\d+)/)[1], 10);
3736 var column = $('<div>').addClass('col-' + (cardWidth / 3) + 'of6');
3737 if (cardWidth < 9) {
3738 column.addClass('col-tablet-1of2');
3739 } else if (cardWidth > 9 && cardWidth < 18) {
3740 column.addClass('col-tablet-1of1');
3741 }
3742 if (cardWidth < 18) {
3743 column.addClass('col-mobile-1of1')
3744 }
3745 return column;
3746 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003747
3748 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3749 function drawResourcesFlowWidget($widget, opts, resources) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003750 $widget.empty().addClass('cols');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003751 var cardSizes = opts.cardSizes || ['6x6'];
3752 var i = 0, j = 0;
Dirk Dougherty29e93432015-05-05 18:17:13 -07003753 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003754
3755 while (i < resources.length) {
3756 var cardSize = cardSizes[j++ % cardSizes.length];
3757 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003758
Dirk Dougherty29e93432015-05-05 18:17:13 -07003759 var column = createResponsiveFlowColumn(cardSize).appendTo($widget);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003760
3761 // A stack has a third dimension which is the number of stacked items
3762 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3763 var stackCount = 0;
3764 var $stackDiv = null;
3765
3766 if (isStack) {
3767 // Create a stack container which should have the dimensions defined
3768 // by the product of the items inside.
3769 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
Dirk Dougherty29e93432015-05-05 18:17:13 -07003770 + 'x' + isStack[2] * isStack[3]) .appendTo(column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003771 }
3772
3773 // Build each stack item or just a single item
3774 do {
3775 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003776
Robert Lye7eeb402014-06-03 19:35:24 -07003777 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003778
3779 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003780 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003781
Dirk Doughertyc3921652014-05-13 16:55:26 -07003782 if (isStack) {
3783 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3784 if (++stackCount == parseInt(isStack[3])) {
3785 $card.addClass('resource-card-row-stack-last');
3786 stackCount = 0;
3787 }
3788 } else {
3789 stackCount = 0;
3790 }
3791
Dirk Dougherty29e93432015-05-05 18:17:13 -07003792 $card.appendTo($stackDiv || column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003793
3794 } while (++i < resources.length && stackCount > 0);
3795 }
3796 }
3797
3798 /* Build a site map of resources using a section as a root. */
3799 function buildSectionList(opts) {
3800 if (opts.section && SECTION_BY_ID[opts.section]) {
3801 return SECTION_BY_ID[opts.section].sections || [];
3802 }
3803 return [];
3804 }
3805
3806 function buildResourceList(opts) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003807 return $.queryResources(opts);
3808 }
3809
3810 $.queryResources = function(opts) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003811 var maxResults = opts.maxResults || 100;
3812
3813 var query = opts.query || '';
3814 var expressions = parseResourceQuery(query);
3815 var addedResourceIndices = {};
3816 var results = [];
3817
3818 for (var i = 0; i < expressions.length; i++) {
3819 var clauses = expressions[i];
3820
3821 // build initial set of resources from first clause
3822 var firstClause = clauses[0];
3823 var resources = [];
3824 switch (firstClause.attr) {
3825 case 'type':
3826 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3827 break;
3828 case 'lang':
3829 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3830 break;
3831 case 'tag':
3832 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3833 break;
3834 case 'collection':
3835 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3836 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3837 break;
3838 case 'section':
3839 var urls = SITE_MAP[firstClause.value].sections || [];
3840 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3841 break;
3842 }
3843 // console.log(firstClause.attr + ':' + firstClause.value);
3844 resources = resources || [];
3845
3846 // use additional clauses to filter corpus
3847 if (clauses.length > 1) {
3848 var otherClauses = clauses.slice(1);
3849 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3850 }
3851
3852 // filter out resources already added
3853 if (i > 1) {
3854 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3855 }
3856
3857 // add to list of already added indices
3858 for (var j = 0; j < resources.length; j++) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003859 if (resources[j]) {
3860 addedResourceIndices[resources[j].index] = 1;
3861 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003862 }
3863
3864 // concat to final results list
3865 results = results.concat(resources);
3866 }
3867
3868 if (opts.sortOrder && results.length) {
3869 var attr = opts.sortOrder;
3870
3871 if (opts.sortOrder == 'random') {
3872 var i = results.length, j, temp;
3873 while (--i) {
3874 j = Math.floor(Math.random() * (i + 1));
3875 temp = results[i];
3876 results[i] = results[j];
3877 results[j] = temp;
3878 }
3879 } else {
3880 var desc = attr.charAt(0) == '-';
3881 if (desc) {
3882 attr = attr.substring(1);
3883 }
3884 results = results.sort(function(x,y) {
3885 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3886 });
3887 }
3888 }
3889
3890 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3891 results = results.slice(0, maxResults);
3892
3893 for (var j = 0; j < results.length; ++j) {
3894 addedPageResources[results[j].index] = 1;
3895 }
3896
3897 return results;
3898 }
3899
3900
3901 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3902 return function(resource) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003903 return resource && !addedResourceIndices[resource.index];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003904 };
3905 }
3906
3907
3908 function getResourceMatchesClausesFilter(clauses) {
3909 return function(resource) {
3910 return doesResourceMatchClauses(resource, clauses);
3911 };
3912 }
3913
3914
3915 function doesResourceMatchClauses(resource, clauses) {
3916 for (var i = 0; i < clauses.length; i++) {
3917 var map;
3918 switch (clauses[i].attr) {
3919 case 'type':
3920 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3921 break;
3922 case 'lang':
3923 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3924 break;
3925 case 'tag':
3926 map = IS_RESOURCE_TAGGED[clauses[i].value];
3927 break;
3928 }
3929
3930 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3931 return clauses[i].negative;
3932 }
3933 }
3934 return true;
3935 }
smain@google.com95948b82014-06-16 19:24:25 -07003936
Robert Lye7eeb402014-06-03 19:35:24 -07003937 function cleanUrl(url)
3938 {
3939 if (url && url.indexOf('//') === -1) {
3940 url = toRoot + url;
3941 }
smain@google.com95948b82014-06-16 19:24:25 -07003942
Robert Lye7eeb402014-06-03 19:35:24 -07003943 return url;
3944 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003945
3946
3947 function parseResourceQuery(query) {
3948 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3949 var expressions = [];
3950 var expressionStrs = query.split(',') || [];
3951 for (var i = 0; i < expressionStrs.length; i++) {
3952 var expr = expressionStrs[i] || '';
3953
3954 // Break expression into clauses (clause e.g. 'tag:foo')
3955 var clauses = [];
3956 var clauseStrs = expr.split(/(?=[\+\-])/);
3957 for (var j = 0; j < clauseStrs.length; j++) {
3958 var clauseStr = clauseStrs[j] || '';
3959
3960 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3961 var parts = clauseStr.split(':');
3962 var clause = {};
3963
3964 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3965 if (clause.attr) {
3966 if (clause.attr.charAt(0) == '+') {
3967 clause.attr = clause.attr.substring(1);
3968 } else if (clause.attr.charAt(0) == '-') {
3969 clause.negative = true;
3970 clause.attr = clause.attr.substring(1);
3971 }
3972 }
3973
3974 if (parts.length > 1) {
3975 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3976 }
3977
3978 clauses.push(clause);
3979 }
3980
3981 if (!clauses.length) {
3982 continue;
3983 }
3984
3985 expressions.push(clauses);
3986 }
3987
3988 return expressions;
3989 }
3990})();
3991
3992(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07003993
smain@google.com95948b82014-06-16 19:24:25 -07003994 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003995 Utility method for creating dom for the description area of a card.
3996 Used in decorateResourceCard and decorateResource.
3997 */
3998 function buildResourceCardDescription(resource, plusone) {
3999 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07004000
Robert Lye7eeb402014-06-03 19:35:24 -07004001 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07004002
Robert Lye7eeb402014-06-03 19:35:24 -07004003 if (resource.cta) {
4004 $description.append($('<a>').addClass('cta').html(resource.cta));
4005 }
smain@google.com95948b82014-06-16 19:24:25 -07004006
Robert Lye7eeb402014-06-03 19:35:24 -07004007 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07004008 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07004009 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004010
Robert Lye7eeb402014-06-03 19:35:24 -07004011 $description.append($('<div>').addClass('util')
4012 .append($('<div>').addClass('g-plusone')
4013 .attr('data-size', 'small')
4014 .attr('data-align', 'right')
4015 .attr('data-href', plusurl)));
4016 }
smain@google.com95948b82014-06-16 19:24:25 -07004017
Robert Lye7eeb402014-06-03 19:35:24 -07004018 return $description;
4019 }
smain@google.com95948b82014-06-16 19:24:25 -07004020
4021
Dirk Doughertyc3921652014-05-13 16:55:26 -07004022 /* Simple jquery function to create dom for a standard resource card */
4023 $.fn.decorateResourceCard = function(resource,plusone) {
4024 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07004025 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004026 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07004027
Robert Lye7eeb402014-06-03 19:35:24 -07004028 if (imgUrl.indexOf('//') === -1) {
4029 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07004030 }
Robert Lye7eeb402014-06-03 19:35:24 -07004031
4032 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07004033 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07004034 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07004035 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07004036
Robert Lye7eeb402014-06-03 19:35:24 -07004037 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
4038 .append($('<div>').addClass('section').text(section))
4039 .append($('<div>').addClass('title').html(resource.title))
4040 .append(buildResourceCardDescription(resource, plusone))
4041 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07004042
4043 return this;
4044 };
4045
4046 /* Simple jquery function to create dom for a resource section card (menu) */
4047 $.fn.decorateResourceSection = function(section,plusone) {
4048 var resource = section.resource;
4049 //keep url clean for matching and offline mode handling
4050 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
4051 var $base = $('<a>')
4052 .addClass('card-bg')
4053 .attr('href', resource.url)
4054 .append($('<div>').addClass('card-section-icon')
4055 .append($('<div>').addClass('icon'))
4056 .append($('<div>').addClass('section').html(resource.title)))
4057 .appendTo(this);
4058
4059 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
4060
4061 if (section.sections && section.sections.length) {
4062 // Recurse the section sub-tree to find a resource image.
4063 var stack = [section];
4064
4065 while (stack.length) {
4066 if (stack[0].resource.image) {
4067 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4068 break;
4069 }
4070
4071 if (stack[0].sections) {
4072 stack = stack.concat(stack[0].sections);
4073 }
4074
4075 stack.shift();
4076 }
4077
4078 var $ul = $('<ul>')
4079 .appendTo($cardInfo);
4080
4081 var max = section.sections.length > 3 ? 3 : section.sections.length;
4082
4083 for (var i = 0; i < max; ++i) {
4084
4085 var subResource = section.sections[i];
4086 if (!plusone) {
4087 $('<li>')
4088 .append($('<a>').attr('href', subResource.url)
4089 .append($('<div>').addClass('title').html(subResource.title))
4090 .append($('<div>').addClass('description ellipsis')
4091 .append($('<div>').addClass('text').html(subResource.summary))
4092 .append($('<div>').addClass('util'))))
4093 .appendTo($ul);
4094 } else {
4095 $('<li>')
4096 .append($('<a>').attr('href', subResource.url)
4097 .append($('<div>').addClass('title').html(subResource.title))
4098 .append($('<div>').addClass('description ellipsis')
4099 .append($('<div>').addClass('text').html(subResource.summary))
4100 .append($('<div>').addClass('util')
4101 .append($('<div>').addClass('g-plusone')
4102 .attr('data-size', 'small')
4103 .attr('data-align', 'right')
4104 .attr('data-href', resource.url)))))
4105 .appendTo($ul);
4106 }
4107 }
4108
4109 // Add a more row
4110 if (max < section.sections.length) {
4111 $('<li>')
4112 .append($('<a>').attr('href', resource.url)
4113 .append($('<div>')
4114 .addClass('title')
4115 .text('More')))
4116 .appendTo($ul);
4117 }
4118 } else {
4119 // No sub-resources, just render description?
4120 }
4121
4122 return this;
4123 };
smain@google.com95948b82014-06-16 19:24:25 -07004124
4125
4126
4127
Robert Lye7eeb402014-06-03 19:35:24 -07004128 /* Render other types of resource styles that are not cards. */
4129 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07004130 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004131 'assets/images/resource-card-default-android.jpg';
4132 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004133
Robert Lye7eeb402014-06-03 19:35:24 -07004134 if (imgUrl.indexOf('//') === -1) {
4135 imgUrl = toRoot + imgUrl;
4136 }
smain@google.com95948b82014-06-16 19:24:25 -07004137
Robert Lye7eeb402014-06-03 19:35:24 -07004138 if (linkUrl && linkUrl.indexOf('//') === -1) {
4139 linkUrl = toRoot + linkUrl;
4140 }
4141
4142 $(this).append(
4143 $('<div>').addClass('image')
4144 .css('background-image', 'url(' + imgUrl + ')'),
4145 $('<div>').addClass('info').append(
4146 $('<h4>').addClass('title').html(resource.title),
4147 $('<p>').addClass('summary').html(resource.summary),
4148 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4149 )
4150 );
4151
4152 return this;
4153 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004154})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004155
4156
Dirk Doughertyc3921652014-05-13 16:55:26 -07004157/* Calculate the vertical area remaining */
4158(function($) {
4159 $.fn.ellipsisfade= function(lineHeight) {
4160 this.each(function() {
4161 // get element text
4162 var $this = $(this);
4163 var remainingHeight = $this.parent().parent().height();
4164 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004165 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004166 if ($(this).is(":visible")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004167 var h = $(this).outerHeight(true);
smain@google.comcda1a9a2014-06-19 17:07:46 -07004168 remainingHeight = remainingHeight - h;
4169 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004170 });
4171
4172 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4173 $this.parent().css({'height': adjustedRemainingHeight});
4174 $this.css({'height': "auto"});
4175 });
4176
4177 return this;
4178 };
4179}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004180
4181/*
4182 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004183
Robert Lye7eeb402014-06-03 19:35:24 -07004184 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004185 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004186 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004187
Robert Lye7eeb402014-06-03 19:35:24 -07004188 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004189
Robert Lye7eeb402014-06-03 19:35:24 -07004190 <div class="fullscreen-carousel">
4191 <div class="fullscreen-carousel-content">
4192 <!-- content here -->
4193 </div>
4194 <div class="fullscreen-carousel-content">
4195 <!-- content here -->
4196 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004197
Robert Lye7eeb402014-06-03 19:35:24 -07004198 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004199
Robert Lye7eeb402014-06-03 19:35:24 -07004200 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004201
Robert Lye7eeb402014-06-03 19:35:24 -07004202 Control over how the carousel takes over the screen can mostly be defined in
4203 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004204 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004205 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004206 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004207 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004208
Robert Lye7eeb402014-06-03 19:35:24 -07004209 There is limited functionality for having multiple sections since that request
4210 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4211 scroll between multiple content areas.
4212*/
4213
4214(function() {
4215 $(document).ready(function() {
4216 $('.fullscreen-carousel').each(function() {
4217 initWidget(this);
4218 });
4219 });
4220
4221 function initWidget(widget) {
4222 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004223
Robert Lye7eeb402014-06-03 19:35:24 -07004224 var topOffset = $widget.offset().top;
4225 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4226 var maxHeight = 0;
4227 var minHeight = 0;
4228 var $content = $widget.find('.fullscreen-carousel-content');
4229 var $nextArrow = $widget.find('.next-arrow');
4230 var $prevArrow = $widget.find('.prev-arrow');
4231 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004232
Robert Lye7eeb402014-06-03 19:35:24 -07004233 if ($content.length <= 1) {
4234 $nextArrow.hide();
4235 $prevArrow.hide();
4236 } else {
4237 $nextArrow.click(function() {
4238 var index = ($content.index($curSection) + 1);
4239 $curSection.hide();
4240 $curSection = $($content[index >= $content.length ? 0 : index]);
4241 $curSection.show();
4242 });
smain@google.com95948b82014-06-16 19:24:25 -07004243
Robert Lye7eeb402014-06-03 19:35:24 -07004244 $prevArrow.click(function() {
4245 var index = ($content.index($curSection) - 1);
4246 $curSection.hide();
4247 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4248 $curSection.show();
4249 });
4250 }
4251
4252 // Just hide all content sections except first.
4253 $content.each(function(index) {
4254 if ($(this).height() > minHeight) minHeight = $(this).height();
4255 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4256 });
4257
4258 // Register for changes to window size, and trigger.
4259 $(window).resize(resizeWidget);
4260 resizeWidget();
4261
4262 function resizeWidget() {
4263 var height = $(window).height() - topOffset - padBottom;
4264 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004265 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004266 (maxHeight && height > maxHeight ? maxHeight : height));
4267 }
smain@google.com95948b82014-06-16 19:24:25 -07004268 }
Robert Lye7eeb402014-06-03 19:35:24 -07004269})();
4270
4271
4272
4273
4274
4275/*
4276 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004277
Robert Lye7eeb402014-06-03 19:35:24 -07004278 The following allows tab widgets to be installed via the html below. Each
4279 tab content section should have a data-tab attribute matching one of the
4280 nav items'. Also each tab content section should have a width matching the
4281 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004282
Robert Lye7eeb402014-06-03 19:35:24 -07004283 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004284
Robert Lye7eeb402014-06-03 19:35:24 -07004285 <div class="tab-carousel">
4286 <ul class="tab-nav">
4287 <li><a href="#" data-tab="handsets">Handsets</a>
4288 <li><a href="#" data-tab="wearable">Wearable</a>
4289 <li><a href="#" data-tab="tv">TV</a>
4290 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004291
Robert Lye7eeb402014-06-03 19:35:24 -07004292 <div class="tab-carousel-content">
4293 <div data-tab="handsets">
4294 <!--Full width content here-->
4295 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004296
Robert Lye7eeb402014-06-03 19:35:24 -07004297 <div data-tab="wearable">
4298 <!--Full width content here-->
4299 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004300
Robert Lye7eeb402014-06-03 19:35:24 -07004301 <div data-tab="tv">
4302 <!--Full width content here-->
4303 </div>
4304 </div>
4305 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004306
Robert Lye7eeb402014-06-03 19:35:24 -07004307*/
4308(function() {
4309 $(document).ready(function() {
4310 $('.tab-carousel').each(function() {
4311 initWidget(this);
4312 });
4313 });
4314
4315 function initWidget(widget) {
4316 var $widget = $(widget);
4317 var $nav = $widget.find('.tab-nav');
4318 var $anchors = $nav.find('[data-tab]');
4319 var $li = $nav.find('li');
4320 var $contentContainer = $widget.find('.tab-carousel-content');
4321 var $tabs = $contentContainer.find('[data-tab]');
4322 var $curTab = $($tabs[0]); // Current tab is first tab.
4323 var width = $widget.width();
4324
4325 // Setup nav interactivity.
4326 $anchors.click(function(evt) {
4327 evt.preventDefault();
4328 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004329 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004330 });
smain@google.com95948b82014-06-16 19:24:25 -07004331
Robert Lye7eeb402014-06-03 19:35:24 -07004332 // Add highlight for navigation on first item.
4333 var $highlight = $('<div>').addClass('highlight')
4334 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4335 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004336
Robert Lye7eeb402014-06-03 19:35:24 -07004337 // Store height since we will change contents to absolute.
4338 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004339
Robert Lye7eeb402014-06-03 19:35:24 -07004340 // Absolutely position tabs so they're ready for transition.
4341 $tabs.each(function(index) {
4342 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4343 });
smain@google.com95948b82014-06-16 19:24:25 -07004344
Robert Lye7eeb402014-06-03 19:35:24 -07004345 function transitionWidget($toTab) {
4346 if (!$curTab.is($toTab)) {
4347 var curIndex = $tabs.index($curTab[0]);
4348 var toIndex = $tabs.index($toTab[0]);
4349 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004350
Robert Lye7eeb402014-06-03 19:35:24 -07004351 // Animate content sections.
4352 $toTab.css({left:(width * dir) + 'px'});
4353 $curTab.animate({left:(width * -dir) + 'px'});
4354 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004355
Robert Lye7eeb402014-06-03 19:35:24 -07004356 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004357 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004358 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004359
Robert Lye7eeb402014-06-03 19:35:24 -07004360 // Store new current section.
4361 $curTab = $toTab;
4362 }
4363 }
smain@google.com95948b82014-06-16 19:24:25 -07004364 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08004365})();
Dirk Dougherty29e93432015-05-05 18:17:13 -07004366
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004367/**
4368 * Auto TOC
4369 *
4370 * Upgrades h2s on the page to have a rule and be toggle-able on mobile.
4371 */
4372(function($) {
4373 var upgraded = false;
4374 var h2Titles;
4375
4376 function initWidget() {
4377 // add HRs below all H2s (except for a few other h2 variants)
4378 // Consider doing this with css instead.
4379 h2Titles = $('h2').not('#qv h2, #tb h2, .sidebox h2, #devdoc-nav h2, h2.norule');
4380 h2Titles.css({marginBottom:0}).after('<hr/>');
4381
4382 // Exit early if on older browser.
4383 if (!window.matchMedia) {
4384 return;
4385 }
4386
4387 // Only run logic in mobile layout.
4388 var query = window.matchMedia('(max-width: 719px)');
4389 if (query.matches) {
4390 makeTogglable();
4391 } else {
4392 query.addListener(makeTogglable);
4393 }
4394 }
4395
4396 function makeTogglable() {
4397 // Only run this logic once.
4398 if (upgraded) { return; }
4399 upgraded = true;
4400
4401 // Only make content h2s togglable.
4402 var contentTitles = h2Titles.filter('#jd-content *');
4403
4404 // If there are more than 1
4405 if (contentTitles.size() < 2) {
4406 return;
4407 }
4408
4409 contentTitles.each(function() {
4410 // Find all the relevant nodes.
4411 var $title = $(this);
4412 var $hr = $title.next();
4413 var $contents = $hr.nextUntil('h2, .next-docs');
4414 var $section = $($title)
4415 .add($hr)
4416 .add($title.prev('a[name]'))
4417 .add($contents);
4418 var $anchor = $section.first().prev();
4419 var anchorMethod = 'after';
4420 if ($anchor.length === 0) {
4421 $anchor = $title.parent();
4422 anchorMethod = 'prepend';
4423 }
4424
4425 // Remove from DOM before messing with it. DOM is slow!
4426 $section.detach();
4427
4428 // Add mobile-only expand arrows.
4429 $title.prepend('<span class="dac-visible-mobile-inline-block">' +
4430 '<i class="dac-toggle-expand dac-sprite dac-expand-more-black"></i>' +
4431 '<i class="dac-toggle-collapse dac-sprite dac-expand-less-black"></i>' +
4432 '</span>')
4433 .attr('data-toggle', 'section');
4434
4435 // Wrap in magic markup.
4436 $section = $section.wrapAll('<div class="dac-toggle dac-mobile">').parent();
4437 $contents.wrapAll('<div class="dac-toggle-content"><div>'); // extra div used for max-height calculation.
4438
4439 // Add it back to the dom.
4440 $anchor[anchorMethod].call($anchor, $section);
4441 });
4442 }
4443
4444 $(function() {
4445 initWidget();
4446 });
4447})(jQuery);
4448
Dirk Dougherty29e93432015-05-05 18:17:13 -07004449(function($) {
4450 'use strict';
4451
4452 /**
4453 * Toggle Floating Label state.
4454 * @param {HTMLElement} el - The DOM element.
4455 * @param options
4456 * @constructor
4457 */
4458 function FloatingLabel(el, options) {
4459 this.el = $(el);
4460 this.options = $.extend({}, FloatingLabel.DEFAULTS_, options);
4461 this.group = this.el.closest('.dac-form-input-group');
4462 this.input = this.group.find('.dac-form-input');
4463
4464 this.checkValue_ = this.checkValue_.bind(this);
4465 this.checkValue_();
4466
4467 this.input.on('focus', function() {
4468 this.group.addClass('dac-focused');
4469 }.bind(this));
4470 this.input.on('blur', function() {
4471 this.group.removeClass('dac-focused');
4472 this.checkValue_();
4473 }.bind(this));
4474 this.input.on('keyup', this.checkValue_);
4475 }
4476
4477 /**
4478 * The label is moved out of the textbox when it has a value.
4479 */
4480 FloatingLabel.prototype.checkValue_ = function() {
4481 if (this.input.val().length) {
4482 this.group.addClass('dac-has-value');
4483 } else {
4484 this.group.removeClass('dac-has-value');
4485 }
4486 };
4487
4488 /**
4489 * jQuery plugin
4490 * @param {object} options - Override default options.
4491 */
4492 $.fn.dacFloatingLabel = function(options) {
4493 return this.each(function() {
4494 new FloatingLabel(this, options);
4495 });
4496 };
4497
4498 $(document).on('ready.aranja', function() {
4499 $('.dac-form-floatlabel').each(function() {
4500 $(this).dacFloatingLabel($(this).data());
4501 });
4502 });
4503})(jQuery);
4504
4505/* global toRoot, CAROUSEL_OVERRIDE */
4506(function($) {
4507 // Ordering matters
4508 var TAG_MAP = [
4509 {from: 'developerstory', to: 'Android Developer Story'},
4510 {from: 'googleplay', to: 'Google Play'}
4511 ];
4512
4513 function DacCarouselQuery(el) {
4514 this.el = $(el);
4515
4516 var opts = this.el.data();
4517 opts.maxResults = parseInt(opts.maxResults || '100', 10);
4518 opts.query = opts.carouselQuery;
4519 var resources = $.queryResources(opts);
4520
4521 this.el.empty();
4522 $(resources).map(function() {
4523 var resource = $.extend({}, this, CAROUSEL_OVERRIDE[this.url]);
4524 var slide = $('<article class="dac-expand dac-hero">');
4525 var image = cleanUrl(resource.heroImage || resource.image);
4526 var fullBleed = image && !resource.heroColor;
4527
4528 // Configure background
4529 slide.css({
4530 backgroundImage: fullBleed ? 'url(' + image + ')' : '',
4531 backgroundColor: resource.heroColor || ''
4532 });
4533
4534 // Should copy be inverted
4535 slide.toggleClass('dac-invert', resource.heroInvert || fullBleed);
4536 slide.toggleClass('dac-darken', fullBleed);
4537
4538 var cols = $('<div class="cols dac-hero-content">');
4539
4540 // inline image column
4541 var rightCol = $('<div class="col-1of2 col-push-1of2 dac-hero-figure">')
4542 .appendTo(cols);
4543
4544 if (!fullBleed && image) {
4545 rightCol.append($('<img>').attr('src', image));
4546 }
4547
4548 // info column
4549 $('<div class="col-1of2 col-pull-1of2">')
4550 .append($('<div class="dac-hero-tag">').text(formatTag(resource)))
4551 .append($('<h1 class="dac-hero-title">').text(formatTitle(resource)))
4552 .append($('<p class="dac-hero-description">').text(resource.summary))
4553 .append($('<a class="dac-hero-cta">')
4554 .text(formatCTA(resource))
4555 .attr('href', cleanUrl(resource.url))
4556 .prepend($('<span class="dac-sprite dac-auto-chevron">'))
4557 )
4558 .appendTo(cols);
4559
4560 slide.append(cols.wrap('<div class="wrap">').parent());
4561 return slide[0];
4562 }).prependTo(this.el);
4563
4564 // Pagination element.
4565 this.el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>');
4566
4567 this.el.dacCarousel();
4568 }
4569
4570 function cleanUrl(url) {
4571 if (url && url.indexOf('//') === -1) {
4572 url = toRoot + url;
4573 }
4574 return url;
4575 }
4576
4577 function formatTag(resource) {
4578 // Hmm, need a better more scalable solution for this.
4579 for (var i = 0, mapping; mapping = TAG_MAP[i]; i++) {
4580 if (resource.tags.indexOf(mapping.from) > -1) {
4581 return mapping.to;
4582 }
4583 }
4584 return resource.type;
4585 }
4586
4587 function formatTitle(resource) {
4588 return resource.title.replace(/android developer story: /i, '');
4589 }
4590
4591 function formatCTA(resource) {
4592 return resource.type === 'youtube' ? 'Watch the video' : 'Learn more';
4593 }
4594
4595 // jQuery plugin
4596 $.fn.dacCarouselQuery = function() {
4597 return this.each(function() {
4598 var el = $(this);
4599 var data = el.data('dac.carouselQuery');
4600
4601 if (!data) { el.data('dac.carouselQuery', (data = new DacCarouselQuery(el))); }
4602 });
4603 };
4604
4605 // Data API
4606 $(function() {
4607 $('[data-carousel-query]').dacCarouselQuery();
4608 });
4609})(jQuery);
4610
4611(function($) {
4612 /**
4613 * A CSS based carousel, inspired by SequenceJS.
4614 * @param {jQuery} el
4615 * @param {object} options
4616 * @constructor
4617 */
4618 function DacCarousel(el, options) {
4619 this.el = $(el);
4620 this.options = options = $.extend({}, DacCarousel.OPTIONS, this.el.data(), options || {});
4621 this.frames = this.el.find(options.frameSelector);
4622 this.count = this.frames.size();
4623 this.current = options.start;
4624
4625 this.initPagination();
4626 this.initEvents();
4627 this.initFrame();
4628 }
4629
4630 DacCarousel.OPTIONS = {
4631 auto: true,
4632 autoTime: 10000,
4633 autoMinTime: 5000,
4634 btnPrev: '[data-carousel-prev]',
4635 btnNext: '[data-carousel-next]',
4636 frameSelector: 'article',
4637 loop: true,
4638 start: 0,
4639 pagination: '[data-carousel-pagination]'
4640 };
4641
4642 DacCarousel.prototype.initPagination = function() {
4643 this.pagination = $([]);
4644 if (!this.options.pagination) { return; }
4645
4646 var pagination = $('<ul class="dac-pagination">');
4647 var parent = this.el;
4648 if (typeof this.options.pagination === 'string') { parent = this.el.find(this.options.pagination); }
4649
4650 if (this.count > 1) {
4651 for (var i = 0; i < this.count; i++) {
4652 var li = $('<li class="dac-pagination-item">').text(i);
4653 if (i === this.options.start) { li.addClass('active'); }
4654 li.click(this.go.bind(this, i));
4655
4656 pagination.append(li);
4657 }
4658 this.pagination = pagination.children();
4659 parent.append(pagination);
4660 }
4661 };
4662
4663 DacCarousel.prototype.initEvents = function() {
4664 var that = this;
4665
4666 this.el.hover(function() {
4667 that.pauseRotateTimer();
4668 }, function() {
4669 that.startRotateTimer();
4670 });
4671
4672 $(this.options.btnPrev).click(function(e) {
4673 e.preventDefault();
4674 that.prev();
4675 });
4676
4677 $(this.options.btnNext).click(function(e) {
4678 e.preventDefault();
4679 that.next();
4680 });
4681 };
4682
4683 DacCarousel.prototype.initFrame = function() {
4684 this.frames.removeClass('active').eq(this.options.start).addClass('active');
4685 };
4686
4687 DacCarousel.prototype.startRotateTimer = function() {
4688 if (!this.options.auto || this.rotateTimer) { return; }
4689 this.rotateTimer = setTimeout(this.next.bind(this), this.options.autoTime);
4690 };
4691
4692 DacCarousel.prototype.pauseRotateTimer = function() {
4693 clearTimeout(this.rotateTimer);
4694 this.rotateTimer = null;
4695 };
4696
4697 DacCarousel.prototype.prev = function() {
4698 this.go(this.current - 1);
4699 };
4700
4701 DacCarousel.prototype.next = function() {
4702 this.go(this.current + 1);
4703 };
4704
4705 DacCarousel.prototype.go = function(next) {
4706 // Figure out what the next slide is.
4707 while (this.count > 0 && next >= this.count) { next -= this.count; }
4708 while (next < 0) { next += this.count; }
4709
4710 // Cancel if we're already on that slide.
4711 if (next === this.current) { return; }
4712
4713 // Prepare next slide.
4714 this.frames.eq(next).removeClass('out');
4715
4716 // Recalculate styles before starting slide transition.
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004717 this.el.resolveStyles();
4718 // Update pagination
4719 this.pagination.removeClass('active').eq(next).addClass('active');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004720
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004721 // Transition out current frame
4722 this.frames.eq(this.current).toggleClass('active out');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004723
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004724 // Transition in a new frame
4725 this.frames.eq(next).toggleClass('active');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004726
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004727 this.current = next;
Dirk Dougherty29e93432015-05-05 18:17:13 -07004728 };
4729
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004730 // Helper which resolves new styles for an element, so it can start transitioning
4731 // from the new values.
4732 $.fn.resolveStyles = function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004733 /*jshint expr:true*/
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004734 this[0] && this[0].offsetTop;
4735 return this;
4736 };
Dirk Dougherty29e93432015-05-05 18:17:13 -07004737
4738 // jQuery plugin
4739 $.fn.dacCarousel = function() {
4740 this.each(function() {
4741 var $el = $(this);
4742 $el.data('dac-carousel', new DacCarousel(this));
4743 });
4744 return this;
4745 };
4746
4747 // Data API
4748 $(function() {
4749 $('[data-carousel]').dacCarousel();
4750 });
4751})(jQuery);
4752
4753(function($) {
4754 'use strict';
4755
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004756 function Modal(el, options) {
4757 this.el = $(el);
4758 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
4759 this.isOpen = false;
4760
4761 this.el.on('click', function(event) {
4762 if (!$.contains($('.dac-modal-window')[0], event.target)) {
4763 return this.close_();
4764 }
4765 }.bind(this));
4766
4767 this.el.on('open', this.open_.bind(this));
4768 this.el.on('close', this.close_.bind(this));
4769 this.el.on('toggle', this.toggle_.bind(this));
4770 }
4771
4772 Modal.prototype.toggle_ = function() {
4773 if (this.isOpen) {
4774 this.close_();
4775 } else {
4776 this.open_();
4777 }
4778 };
4779
4780 Modal.prototype.close_ = function() {
4781 this.el.removeClass('dac-active');
4782 $('body').removeClass('dac-modal-open');
4783 this.isOpen = false;
4784 };
4785
4786 Modal.prototype.open_ = function() {
4787 this.el.addClass('dac-active');
4788 $('body').addClass('dac-modal-open');
4789 this.isOpen = true;
4790 };
4791
Dirk Dougherty29e93432015-05-05 18:17:13 -07004792 function ToggleModal(el, options) {
4793 this.el = $(el);
4794 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004795 this.modal = this.options.modalToggle ? $('[data-modal="' + this.options.modalToggle + '"]') :
4796 this.el.closest('[data-modal]');
4797
Dirk Dougherty29e93432015-05-05 18:17:13 -07004798 this.el.on('click', this.clickHandler_.bind(this));
4799 }
4800
Dirk Dougherty29e93432015-05-05 18:17:13 -07004801 ToggleModal.prototype.clickHandler_ = function(event) {
4802 event.preventDefault();
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004803 this.modal.trigger('toggle');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004804 };
4805
4806 /**
4807 * jQuery plugin
4808 * @param {object} options - Override default options.
4809 */
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004810 $.fn.dacModal = function(options) {
4811 return this.each(function() {
4812 new Modal(this, options);
4813 });
4814 };
4815
Dirk Dougherty29e93432015-05-05 18:17:13 -07004816 $.fn.dacToggleModal = function(options) {
4817 return this.each(function() {
4818 new ToggleModal(this, options);
4819 });
4820 };
4821
4822 /**
4823 * Data Attribute API
4824 */
4825 $(document).on('ready.aranja', function() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004826 $('[data-modal]').each(function() {
4827 $(this).dacModal($(this).data());
4828 });
4829
4830 $('[data-modal-toggle]').each(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004831 $(this).dacToggleModal($(this).data());
4832 });
4833 });
4834})(jQuery);
4835
4836(function($) {
4837 'use strict';
4838
4839 /**
4840 * Toggle the visabilty of the mobile navigation.
4841 * @param {HTMLElement} el - The DOM element.
4842 * @param options
4843 * @constructor
4844 */
4845 function ToggleNav(el, options) {
4846 this.el = $(el);
4847 this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
4848 this.options.target = [this.options.navigation];
4849
4850 if (this.options.body) {this.options.target.push('body')}
4851 if (this.options.dimmer) {this.options.target.push(this.options.dimmer)}
4852
4853 this.el.on('click', this.clickHandler_.bind(this));
4854 }
4855
4856 /**
4857 * ToggleNav Default Settings
4858 * @type {{body: boolean, dimmer: string, navigation: string, toggleClass: string}}
4859 * @private
4860 */
4861 ToggleNav.DEFAULTS_ = {
4862 body: true,
4863 dimmer: '.dac-nav-dimmer',
4864 navigation: '[data-dac-nav]',
4865 toggleClass: 'dac-nav-open'
4866 };
4867
4868 /**
4869 * The actual toggle logic.
4870 * @param event
4871 * @private
4872 */
4873 ToggleNav.prototype.clickHandler_ = function(event) {
4874 event.preventDefault();
4875 $(this.options.target.join(', ')).toggleClass(this.options.toggleClass);
4876 };
4877
4878 /**
4879 * jQuery plugin
4880 * @param {object} options - Override default options.
4881 */
4882 $.fn.dacToggleMobileNav = function(options) {
4883 return this.each(function() {
4884 new ToggleNav(this, options);
4885 });
4886 };
4887
4888 /**
4889 * Data Attribute API
4890 */
4891 $(window).on('load.aranja', function() {
4892 $('[data-dac-toggle-nav]').each(function() {
4893 $(this).dacToggleMobileNav($(this).data());
4894 });
4895 });
4896})(jQuery);
4897
4898(function($) {
4899 'use strict';
4900
4901 /**
4902 * Submit the newsletter form to a Google Form.
4903 * @param {HTMLElement} el - The Form DOM element.
4904 * @constructor
4905 */
4906 function NewsletterForm(el) {
4907 this.el = $(el);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004908 this.form = this.el.find('form');
4909 $('<iframe/>').hide()
4910 .attr('name', 'dac-newsletter-iframe')
4911 .attr('src', '')
4912 .insertBefore(this.form);
4913 this.form.on('submit', this.submitHandler_.bind(this));
Dirk Dougherty29e93432015-05-05 18:17:13 -07004914 }
4915
4916 /**
4917 * Close the modal when the form is sent.
4918 * @private
4919 */
4920 NewsletterForm.prototype.submitHandler_ = function() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004921 this.form.trigger('reset');
4922 this.el.trigger('close');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004923 };
4924
4925 /**
4926 * jQuery plugin
4927 * @param {object} options - Override default options.
4928 */
4929 $.fn.dacNewsletterForm = function(options) {
4930 return this.each(function() {
4931 new NewsletterForm(this, options);
4932 });
4933 };
4934
4935 /**
4936 * Data Attribute API
4937 */
4938 $(document).on('ready.aranja', function() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004939 $('[data-newsletter]').each(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004940 $(this).dacNewsletterForm();
4941 });
4942 });
4943})(jQuery);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004944
4945(function($) {
4946 'use strict';
4947
4948 /**
4949 * Smoothly scroll to location on current page.
4950 * @param el
4951 * @param options
4952 * @constructor
4953 */
4954 function ScrollButton(el, options) {
4955 this.el = $(el);
4956 this.target = $(this.el.attr('href'));
4957 this.options = $.extend({}, ScrollButton.DEFAULTS_, options);
4958
4959 if (typeof this.options.offset === 'string') {
4960 this.options.offset = $(this.options.offset).height();
4961 }
4962
4963 this.el.on('click', this.clickHandler_.bind(this));
4964 }
4965
4966 /**
4967 * Default options
4968 * @type {{duration: number, easing: string, offset: number, scrollContainer: string}}
4969 * @private
4970 */
4971 ScrollButton.DEFAULTS_ = {
4972 duration: 300,
4973 easing: 'swing',
4974 offset: 0,
4975 scrollContainer: 'html, body'
4976 };
4977
4978 /**
4979 * Scroll logic
4980 * @param event
4981 * @private
4982 */
4983 ScrollButton.prototype.clickHandler_ = function(event) {
4984 if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
4985 return;
4986 }
4987
4988 event.preventDefault();
4989
4990 $(this.options.scrollContainer).animate({
4991 scrollTop: this.target.offset().top - this.options.offset
4992 }, this.options);
4993 };
4994
4995 /**
4996 * jQuery plugin
4997 * @param {object} options - Override default options.
4998 */
4999 $.fn.dacScrollButton = function(options) {
5000 return this.each(function() {
5001 new ScrollButton(this, options);
5002 });
5003 };
5004
5005 /**
5006 * Data Attribute API
5007 */
5008 $(document).on('ready.aranja', function() {
5009 $('[data-scroll-button]').each(function() {
5010 $(this).dacScrollButton($(this).data());
5011 });
5012 });
5013})(jQuery);
5014
5015(function($) {
5016 function Toggle(el) {
5017 $(el).on('click.dac.togglesection', this.toggle);
5018 }
5019
5020 Toggle.prototype.toggle = function() {
5021 var $this = $(this);
5022
5023 var $parent = getParent($this);
5024 var isExpanded = $parent.hasClass('is-expanded');
5025
5026 transitionMaxHeight($parent.find('.dac-toggle-content'), !isExpanded);
5027 $parent.toggleClass('is-expanded');
5028
5029 return false;
5030 };
5031
5032 function getParent($this) {
5033 var selector = $this.attr('data-target');
5034
5035 if (!selector) {
5036 selector = $this.attr('href');
5037 selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '');
5038 }
5039
5040 var $parent = selector && $(selector);
5041
5042 return $parent && $parent.length ? $parent : $this.parent();
5043 }
5044
5045 /**
5046 * Runs a transition of max-height along with responsive styles which hide or expand the element.
5047 * @param $el
5048 * @param visible
5049 */
5050 function transitionMaxHeight($el, visible) {
5051 // Only supports 1 child
5052 var contentHeight = $el.children().outerHeight();
5053 var targetHeight = visible ? contentHeight : 0;
5054 var duration = $el.transitionDuration();
5055
5056 // If we're hiding, first set the maxHeight we're transitioning from.
5057 if (!visible) {
5058 $el.css('maxHeight', contentHeight + 'px')
5059 .resolveStyles();
5060 }
5061
5062 // Transition to new state
5063 $el.css('maxHeight', targetHeight);
5064
5065 // Reset maxHeight to css value after transition.
5066 setTimeout(function() {
5067 $el.css('maxHeight', '');
5068 }, duration);
5069 }
5070
5071 // Utility to get the transition duration for the element.
5072 $.fn.transitionDuration = function() {
5073 var d = $(this).css('transitionDuration') || '0s';
5074
5075 return +(parseFloat(d) * (/ms/.test(d) ? 1 : 1000)).toFixed(0);
5076 };
5077
5078 // jQuery plugin
5079 $.fn.toggleSection = function(option) {
5080 return this.each(function() {
5081 var $this = $(this);
5082 var data = $this.data('dac.togglesection');
5083 if (!data) {$this.data('dac.togglesection', (data = new Toggle(this)));}
5084 if (typeof option === 'string') {data[option].call($this);}
5085 });
5086 };
5087
5088 // Data api
5089 $(document)
5090 .on('click.toggle', '[data-toggle="section"]', Toggle.prototype.toggle);
5091})(jQuery);