blob: 60bbaf9f360ce7c3f2510c614e1577bfe5e56265 [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");
189 } else {
190 parentNavEl.removeClass('has-subnav').addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800191 }
Scott Mainac2aef52013-02-12 14:15:23 -0800192 // highlight Distribute tab
193 } else if ($("body").hasClass("distribute")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700194 parentNavEl = navEl.find("> li.distribute > a");
195 parentNavEl.addClass('has-subnav');
Dirk Doughertyc3921652014-05-13 16:55:26 -0700196
Dirk Dougherty29e93432015-05-05 18:17:13 -0700197 if (urlSegments[2] == "users") {
198 subNavEl.find("li.users > a").addClass("selected");
199 } else if (urlSegments[2] == "engage") {
200 subNavEl.find("li.engage > a").addClass("selected");
201 } else if (urlSegments[2] == "monetize") {
202 subNavEl.find("li.monetize > a").addClass("selected");
203 } else if (urlSegments[2] == "analyze") {
204 subNavEl.find("li.analyze > a").addClass("selected");
205 } else if (urlSegments[2] == "tools") {
206 subNavEl.find("li.disttools > a").addClass("selected");
207 } else if (urlSegments[2] == "stories") {
208 subNavEl.find("li.stories > a").addClass("selected");
209 } else if (urlSegments[2] == "essentials") {
210 subNavEl.find("li.essentials > a").addClass("selected");
211 } else if (urlSegments[2] == "googleplay") {
212 subNavEl.find("li.googleplay > a").addClass("selected");
213 } else {
214 parentNavEl.removeClass('has-subnav').addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700215 }
Scott Mainb16376f2014-05-21 20:35:47 -0700216 }
Scott Mainac2aef52013-02-12 14:15:23 -0800217
Scott Mainf6145542013-04-01 16:38:11 -0700218 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
219 // and highlight the sidenav
220 mPagePath = pagePath;
221 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700222 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800223
Scott Mainf6145542013-04-01 16:38:11 -0700224 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700225 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700226 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700227 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800228 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700229
230 // set up prev links
231 var $prevLink = [];
232 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700233
Scott Maine4d8f1b2012-06-21 18:03:05 -0700234 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
235false; // navigate across topic boundaries only in design docs
236 if ($prevListItem.length) {
smain@google.comc91ecb72014-06-23 10:22:23 -0700237 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Scott Main5a1123e2012-09-26 12:51:28 -0700238 // jump to last topic of previous section
239 $prevLink = $prevListItem.find('a:last');
240 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700241 // jump to previous topic in this section
242 $prevLink = $prevListItem.find('a:eq(0)');
243 }
244 } else {
245 // jump to this section's index page (if it exists)
246 var $parentListItem = $selListItem.parents('li');
247 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700248
Scott Maine4d8f1b2012-06-21 18:03:05 -0700249 // except if cross boundaries aren't allowed, and we're at the top of a section already
250 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700251 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700252 && $selListItem.hasClass('nav-section')) {
253 $prevLink = [];
254 }
255 }
256
Scott Maine4d8f1b2012-06-21 18:03:05 -0700257 // set up next links
258 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700259 var startClass = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700260 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700261
Scott Main1a00f7f2013-10-29 11:11:19 -0700262 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700263 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700264 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700265
266 // if there aren't any children, go to the next section (required for About pages)
267 if($nextLink.length == 0) {
268 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700269 } else if ($('.topic-start-link').length) {
270 // as long as there's a child link and there is a "topic start link" (we're on a landing)
271 // then set the landing page "start link" text to be the first doc title
272 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700273 }
Scott Main3b90aff2013-08-01 18:09:35 -0700274
Scott Main5a1123e2012-09-26 12:51:28 -0700275 // If the selected page has a description, then it's a class or article homepage
276 if ($selListItem.find('a[description]').length) {
277 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700278 startClass = true;
279 }
280 } else {
281 // jump to the next topic in this section (if it exists)
282 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700283 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700284 isCrossingBoundary = true;
285 // no more topics in this section, jump to the first topic in the next section
smain@google.comabf34112014-06-23 11:39:02 -0700286 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Scott Main5a1123e2012-09-26 12:51:28 -0700287 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
288 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700289 if ($nextLink.length == 0) {
290 // if that doesn't work, we're at the end of the list, so disable NEXT link
291 $('.next-page-link').attr('href','').addClass("disabled")
292 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700293 // and completely hide the one in the footer
294 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700295 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700296 }
297 }
298 }
Scott Main5a1123e2012-09-26 12:51:28 -0700299
300 if (startClass) {
301 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
302
Scott Main3b90aff2013-08-01 18:09:35 -0700303 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700304 // then we need to add a bottom border to button
305 if (!$("#tb").length) {
306 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700307 }
Scott Main5a1123e2012-09-26 12:51:28 -0700308 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
309 $('.content-footer.next-class').show();
310 $('.next-page-link').attr('href','')
311 .removeClass("hide").addClass("disabled")
312 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700313 // and completely hide the one in the footer
314 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700315 if ($nextLink.length) {
316 $('.next-class-link').attr('href',$nextLink.attr('href'))
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700317 .removeClass("hide")
318 .append(": " + $nextLink.html());
Scott Main1a00f7f2013-10-29 11:11:19 -0700319 $('.next-class-link').find('.new').empty();
320 }
Scott Main5a1123e2012-09-26 12:51:28 -0700321 } else {
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700322 $('.next-page-link').attr('href', $nextLink.attr('href'))
323 .removeClass("hide");
324 // for the footer link, also add the next page title
325 $('.content-footer .next-page-link').append(": " + $nextLink.html());
Scott Main5a1123e2012-09-26 12:51:28 -0700326 }
327
328 if (!startClass && $prevLink.length) {
329 var prevHref = $prevLink.attr('href');
330 if (prevHref == SITE_ROOT + 'index.html') {
331 // Don't show Previous when it leads to the homepage
332 } else {
333 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
334 }
Scott Main3b90aff2013-08-01 18:09:35 -0700335 }
Scott Main5a1123e2012-09-26 12:51:28 -0700336
Scott Maine4d8f1b2012-06-21 18:03:05 -0700337 }
Scott Main3b90aff2013-08-01 18:09:35 -0700338
339
340
Scott Main5a1123e2012-09-26 12:51:28 -0700341 // Set up the course landing pages for Training with class names and descriptions
342 if ($('body.trainingcourse').length) {
343 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700344
345 // create an array for all the class descriptions
346 var $classDescriptions = new Array($classLinks.length);
347 var lang = getLangPref();
348 $classLinks.each(function(index) {
349 var langDescr = $(this).attr(lang + "-description");
350 if (typeof langDescr !== 'undefined' && langDescr !== false) {
351 // if there's a class description in the selected language, use that
352 $classDescriptions[index] = langDescr;
353 } else {
354 // otherwise, use the default english description
355 $classDescriptions[index] = $(this).attr("description");
356 }
357 });
Scott Main3b90aff2013-08-01 18:09:35 -0700358
Scott Main5a1123e2012-09-26 12:51:28 -0700359 var $olClasses = $('<ol class="class-list"></ol>');
360 var $liClass;
Scott Main5a1123e2012-09-26 12:51:28 -0700361 var $h2Title;
362 var $pSummary;
363 var $olLessons;
364 var $liLesson;
365 $classLinks.each(function(index) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700366 $liClass = $('<li class="clearfix"></li>');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700367 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2 class="norule">' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700368 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700369
Scott Main5a1123e2012-09-26 12:51:28 -0700370 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700371
Scott Main5a1123e2012-09-26 12:51:28 -0700372 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700373
Scott Main5a1123e2012-09-26 12:51:28 -0700374 if ($lessons.length) {
Scott Main5a1123e2012-09-26 12:51:28 -0700375 $lessons.each(function(index) {
376 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
377 });
378 } else {
Scott Main5a1123e2012-09-26 12:51:28 -0700379 $pSummary.addClass('article');
380 }
381
Dirk Dougherty29e93432015-05-05 18:17:13 -0700382 $liClass.append($h2Title).append($pSummary).append($olLessons);
Scott Main5a1123e2012-09-26 12:51:28 -0700383 $olClasses.append($liClass);
384 });
385 $('.jd-descr').append($olClasses);
386 }
387
Scott Maine4d8f1b2012-06-21 18:03:05 -0700388 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700389 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700390
Scott Main3b90aff2013-08-01 18:09:35 -0700391
Scott Maine4d8f1b2012-06-21 18:03:05 -0700392 $(".scroll-pane").scroll(function(event) {
393 event.preventDefault();
394 return false;
395 });
396
397 /* Resize nav height when window height changes */
398 $(window).resize(function() {
399 if ($('#side-nav').length == 0) return;
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700400 setNavBarDimensions(); // do this even if sidenav isn't fixed because it could become fixed
Scott Maine4d8f1b2012-06-21 18:03:05 -0700401 // make sidenav behave when resizing the window and side-scolling is a concern
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700402 updateSideNavDimensions();
403 checkSticky();
404 resizeNav(250);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700405 });
406
Scott Maine4d8f1b2012-06-21 18:03:05 -0700407 if ($('#devdoc-nav').length) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700408 setNavBarDimensions();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700409 }
410
411
Scott Maine4d8f1b2012-06-21 18:03:05 -0700412 // Set up play-on-hover <video> tags.
413 $('video.play-on-hover').bind('click', function(){
414 $(this).get(0).load(); // in case the video isn't seekable
415 $(this).get(0).play();
416 });
417
418 // Set up tooltips
419 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700420 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700421 var $target = $(this);
422 var $tooltip = $('<div>')
423 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700424 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700425 .hide()
426 .appendTo('body');
427 $target.removeAttr('title');
428
429 $target.hover(function() {
430 // in
431 var targetRect = $target.offset();
432 targetRect.width = $target.width();
433 targetRect.height = $target.height();
434
435 $tooltip.css({
436 left: targetRect.left,
437 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
438 });
439 $tooltip.addClass('below');
440 $tooltip.show();
441 }, function() {
442 // out
443 $tooltip.hide();
444 });
445 });
446
447 // Set up <h2> deeplinks
448 $('h2').click(function() {
449 var id = $(this).attr('id');
450 if (id) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700451 if (history && history.replaceState) {
452 // Change url without scrolling.
453 history.replaceState({}, '', '#' + id);
454 } else {
455 document.location.hash = id;
456 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700457 }
458 });
459
460 //Loads the +1 button
461 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
462 po.src = 'https://apis.google.com/js/plusone.js';
463 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
464
Scott Maine4d8f1b2012-06-21 18:03:05 -0700465 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700466
Scott Maine4d8f1b2012-06-21 18:03:05 -0700467 if ($(".scroll-pane").length > 1) {
468 // Check if there's a user preference for the panel heights
469 var cookieHeight = readCookie("reference_height");
470 if (cookieHeight) {
471 restoreHeight(cookieHeight);
472 }
473 }
Scott Main3b90aff2013-08-01 18:09:35 -0700474
Scott Main06f3f2c2014-05-30 11:23:00 -0700475 // Resize once loading is finished
Scott Maine4d8f1b2012-06-21 18:03:05 -0700476 resizeNav();
Scott Main06f3f2c2014-05-30 11:23:00 -0700477 // Check if there's an anchor that we need to scroll into view.
478 // A delay is needed, because some browsers do not immediately scroll down to the anchor
479 window.setTimeout(offsetScrollForSticky, 100);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700480
Scott Main015d6162013-01-29 09:01:52 -0800481 /* init the language selector based on user cookie for lang */
482 loadLangPref();
483 changeNavLang(getLangPref());
484
485 /* setup event handlers to ensure the overflow menu is visible while picking lang */
486 $("#language select")
487 .mousedown(function() {
488 $("div.morehover").addClass("hover"); })
489 .blur(function() {
490 $("div.morehover").removeClass("hover"); });
491
492 /* some global variable setup */
493 resizePackagesNav = $("#resize-packages-nav");
494 classesNav = $("#classes-nav");
495 devdocNav = $("#devdoc-nav");
496
497 var cookiePath = "";
498 if (location.href.indexOf("/reference/") != -1) {
499 cookiePath = "reference_";
500 } else if (location.href.indexOf("/guide/") != -1) {
501 cookiePath = "guide_";
502 } else if (location.href.indexOf("/tools/") != -1) {
503 cookiePath = "tools_";
504 } else if (location.href.indexOf("/training/") != -1) {
505 cookiePath = "training_";
506 } else if (location.href.indexOf("/design/") != -1) {
507 cookiePath = "design_";
508 } else if (location.href.indexOf("/distribute/") != -1) {
509 cookiePath = "distribute_";
510 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700511
smain@google.com698fff02014-11-20 20:39:33 -0800512
513 /* setup shadowbox for any videos that want it */
smain@google.comf75ee212014-11-24 09:42:59 -0800514 var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
smain@google.com698fff02014-11-20 20:39:33 -0800515 if ($videoLinks.length) {
516 // if there's at least one, add the shadowbox HTML to the body
517 $('body').prepend(
518'<div id="video-container">'+
519 '<div id="video-frame">'+
520 '<div class="video-close">'+
521 '<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
522 '</div>'+
523 '<div id="youTubePlayer"></div>'+
524 '</div>'+
525'</div>');
526
527 // loads the IFrame Player API code asynchronously.
528 $.getScript("https://www.youtube.com/iframe_api");
529
530 $videoLinks.each(function() {
531 var videoId = $(this).attr('href').split('?v=')[1];
532 $(this).click(function(event) {
533 event.preventDefault();
534 startYouTubePlayer(videoId);
535 });
536 });
smain@google.com698fff02014-11-20 20:39:33 -0800537 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700538});
Scott Main7e447ed2013-02-19 17:22:37 -0800539// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700540
541
smain@google.com698fff02014-11-20 20:39:33 -0800542var youTubePlayer;
543function onYouTubeIframeAPIReady() {
544}
545
smain@google.com3de83c12014-12-12 19:06:52 -0800546/* Returns the height the shadowbox video should be. It's based on the current
547 height of the "video-frame" element, which is 100% height for the window.
548 Then minus the margin so the video isn't actually the full window height. */
549function getVideoHeight() {
550 var frameHeight = $("#video-frame").height();
551 var marginTop = $("#video-frame").css('margin-top').split('px')[0];
552 return frameHeight - (marginTop * 2);
553}
554
smain@google.comd162be52015-02-05 13:27:16 -0800555var mPlayerPaused = false;
556
smain@google.com698fff02014-11-20 20:39:33 -0800557function startYouTubePlayer(videoId) {
smain@google.com3de83c12014-12-12 19:06:52 -0800558 $("#video-container").show();
559 $("#video-frame").show();
smain@google.comd162be52015-02-05 13:27:16 -0800560 mPlayerPaused = false;
smain@google.com3de83c12014-12-12 19:06:52 -0800561
562 // compute the size of the player so it's centered in window
563 var maxWidth = 940; // the width of the web site content
564 var videoAspect = .5625; // based on 1280x720 resolution
565 var maxHeight = maxWidth * videoAspect;
566 var videoHeight = getVideoHeight();
567 var videoWidth = videoHeight / videoAspect;
568 if (videoWidth > maxWidth) {
569 videoWidth = maxWidth;
570 videoHeight = maxHeight;
smain@google.com570c2212014-11-25 19:01:40 -0800571 }
smain@google.com3de83c12014-12-12 19:06:52 -0800572 $("#video-frame").css('width', videoWidth);
573
574 // check if we've already created this player
smain@google.com698fff02014-11-20 20:39:33 -0800575 if (youTubePlayer == null) {
smain@google.com3de83c12014-12-12 19:06:52 -0800576 // check if there's a start time specified
577 var idAndHash = videoId.split("#");
578 var startTime = 0;
579 if (idAndHash.length > 1) {
580 startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0;
581 }
582 // enable localized player
583 var lang = getLangPref();
584 var captionsOn = lang == 'en' ? 0 : 1;
585
smain@google.com698fff02014-11-20 20:39:33 -0800586 youTubePlayer = new YT.Player('youTubePlayer', {
smain@google.com3de83c12014-12-12 19:06:52 -0800587 height: videoHeight,
588 width: videoWidth,
smain@google.com570c2212014-11-25 19:01:40 -0800589 videoId: idAndHash[0],
smain@google.com481f15c2014-12-12 11:57:27 -0800590 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
smain@google.com698fff02014-11-20 20:39:33 -0800591 events: {
smain@google.comf75ee212014-11-24 09:42:59 -0800592 'onReady': onPlayerReady,
593 'onStateChange': onPlayerStateChange
smain@google.com698fff02014-11-20 20:39:33 -0800594 }
595 });
596 } else {
smain@google.com3de83c12014-12-12 19:06:52 -0800597 // reset the size in case the user adjusted the window since last play
598 youTubePlayer.setSize(videoWidth, videoHeight);
smain@google.com94e0b532014-12-16 19:07:08 -0800599 // if a video different from the one already playing was requested, cue it up
smain@google.comd162be52015-02-05 13:27:16 -0800600 if (videoId != youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]) {
smain@google.com94e0b532014-12-16 19:07:08 -0800601 youTubePlayer.cueVideoById(videoId);
602 }
smain@google.com698fff02014-11-20 20:39:33 -0800603 youTubePlayer.playVideo();
604 }
smain@google.com698fff02014-11-20 20:39:33 -0800605}
606
607function onPlayerReady(event) {
608 event.target.playVideo();
smain@google.comd162be52015-02-05 13:27:16 -0800609 mPlayerPaused = false;
smain@google.com698fff02014-11-20 20:39:33 -0800610}
611
612function closeVideo() {
613 try {
smain@google.comf75ee212014-11-24 09:42:59 -0800614 youTubePlayer.pauseVideo();
smain@google.com698fff02014-11-20 20:39:33 -0800615 } catch(e) {
smain@google.com698fff02014-11-20 20:39:33 -0800616 }
smain@google.com3de83c12014-12-12 19:06:52 -0800617 $("#video-container").fadeOut(200);
smain@google.com698fff02014-11-20 20:39:33 -0800618}
619
smain@google.comf75ee212014-11-24 09:42:59 -0800620/* Track youtube playback for analytics */
621function onPlayerStateChange(event) {
622 // Video starts, send the video ID
623 if (event.data == YT.PlayerState.PLAYING) {
smain@google.comd162be52015-02-05 13:27:16 -0800624 if (mPlayerPaused) {
625 ga('send', 'event', 'Videos', 'Resume',
626 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]);
627 } else {
628 // track the start playing event so we know from which page the video was selected
629 ga('send', 'event', 'Videos', 'Start: ' +
630 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
631 'on: ' + document.location.href);
632 }
633 mPlayerPaused = false;
smain@google.comf75ee212014-11-24 09:42:59 -0800634 }
635 // Video paused, send video ID and video elapsed time
636 if (event.data == YT.PlayerState.PAUSED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800637 ga('send', 'event', 'Videos', 'Paused',
smain@google.comd162be52015-02-05 13:27:16 -0800638 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
639 youTubePlayer.getCurrentTime());
640 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800641 }
642 // Video finished, send video ID and video elapsed time
643 if (event.data == YT.PlayerState.ENDED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800644 ga('send', 'event', 'Videos', 'Finished',
smain@google.comd162be52015-02-05 13:27:16 -0800645 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
646 youTubePlayer.getCurrentTime());
647 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800648 }
649}
650
smain@google.com698fff02014-11-20 20:39:33 -0800651
652
Scott Mainad08f072013-08-20 16:49:57 -0700653function initExpandableNavItems(rootTag) {
654 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
655 var section = $(this).closest('li.nav-section');
656 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700657 /* hide me and descendants */
658 section.find('ul').slideUp(250, function() {
659 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700660 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700661 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700662 resizeNav();
663 });
664 } else {
665 /* show me */
666 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700667 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700668 $others.removeClass('expanded').children('ul').slideUp(250);
669
670 // now expand me
671 section.closest('li').addClass('expanded');
672 section.children('ul').slideDown(250, function() {
673 resizeNav();
674 });
675 }
676 });
Scott Mainf0093852013-08-22 11:37:11 -0700677
678 // Stop expand/collapse behavior when clicking on nav section links
679 // (since we're navigating away from the page)
680 // This selector captures the first instance of <a>, but not those with "#" as the href.
681 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
682 window.location.href = $(this).attr('href');
683 return false;
684 });
Scott Mainad08f072013-08-20 16:49:57 -0700685}
686
Dirk Doughertyc3921652014-05-13 16:55:26 -0700687
688/** Create the list of breadcrumb links in the sticky header */
689function buildBreadcrumbs() {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700690 var $breadcrumbUl = $(".dac-header-crumbs");
691 var primaryNavLink = ".dac-nav-list > .dac-nav-item > .dac-nav-link";
692
Dirk Doughertyc3921652014-05-13 16:55:26 -0700693 // Add the secondary horizontal nav item, if provided
Dirk Dougherty29e93432015-05-05 18:17:13 -0700694 var $selectedSecondNav = $(".dac-nav-secondary .dac-nav-link.selected").clone()
695 .attr('class', 'dac-header-crumbs-link');
696
Dirk Doughertyc3921652014-05-13 16:55:26 -0700697 if ($selectedSecondNav.length) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700698 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedSecondNav));
Dirk Doughertyc3921652014-05-13 16:55:26 -0700699 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700700
Dirk Doughertyc3921652014-05-13 16:55:26 -0700701 // Add the primary horizontal nav
Dirk Dougherty29e93432015-05-05 18:17:13 -0700702 var $selectedFirstNav = $(primaryNavLink + ".selected, " + primaryNavLink + ".has-subnav").clone()
703 .attr('class', 'dac-header-crumbs-link');
704
Dirk Doughertyc3921652014-05-13 16:55:26 -0700705 // If there's no header nav item, use the logo link and title from alt text
706 if ($selectedFirstNav.length < 1) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700707 $selectedFirstNav = $('<a class="dac-header-crumbs-link">')
Dirk Doughertyc3921652014-05-13 16:55:26 -0700708 .attr('href', $("div#header .logo a").attr('href'))
709 .text($("div#header .logo img").attr('alt'));
710 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700711 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedFirstNav));
Dirk Doughertyc3921652014-05-13 16:55:26 -0700712}
713
714
715
Scott Maine624b3f2013-09-12 12:56:41 -0700716/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700717function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700718 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
719 if ($("ul#nav li.selected").length) {
720 unHighlightSidenav();
721 }
722 // look for URL in sidenav, including the hash
723 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
724
725 // If the selNavLink is still empty, look for it without the hash
726 if ($selNavLink.length == 0) {
727 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
728 }
729
Scott Mainf6145542013-04-01 16:38:11 -0700730 var $selListItem;
731 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700732 // Find this page's <li> in sidenav and set selected
733 $selListItem = $selNavLink.closest('li');
734 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700735
Scott Mainf6145542013-04-01 16:38:11 -0700736 // Traverse up the tree and expand all parent nav-sections
737 $selNavLink.parents('li.nav-section').each(function() {
738 $(this).addClass('expanded');
739 $(this).children('ul').show();
740 });
741 }
742}
743
Scott Maine624b3f2013-09-12 12:56:41 -0700744function unHighlightSidenav() {
745 $("ul#nav li.selected").removeClass("selected");
746 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
747}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700748
749function toggleFullscreen(enable) {
750 var delay = 20;
751 var enabled = true;
752 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
753 if (enable) {
754 // Currently NOT USING fullscreen; enable fullscreen
755 stylesheet.removeAttr('disabled');
756 $('#nav-swap .fullscreen').removeClass('disabled');
757 $('#devdoc-nav').css({left:''});
758 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
759 enabled = true;
760 } else {
761 // Currently USING fullscreen; disable fullscreen
762 stylesheet.attr('disabled', 'disabled');
763 $('#nav-swap .fullscreen').addClass('disabled');
764 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
765 enabled = false;
766 }
smain@google.com6bdcb982014-11-14 11:53:07 -0800767 writeCookie("fullscreen", enabled, null);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700768 setNavBarDimensions();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700769 resizeNav(delay);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700770 updateSideNavDimensions();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700771 setTimeout(initSidenavHeightResize,delay);
772}
773
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700774// TODO: Refactor into a closure.
775var navBarLeftPos;
776var navBarWidth;
777function setNavBarDimensions() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700778 navBarLeftPos = $('#body-content').offset().left;
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700779 navBarWidth = $('#side-nav').width();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700780}
781
782
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700783function updateSideNavDimensions() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700784 var newLeft = $(window).scrollLeft() - navBarLeftPos;
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700785 $('#devdoc-nav').css({left: -newLeft, width: navBarWidth});
Dirk Dougherty29e93432015-05-05 18:17:13 -0700786 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('padding-left')))});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700787}
Scott Main3b90aff2013-08-01 18:09:35 -0700788
Scott Maine4d8f1b2012-06-21 18:03:05 -0700789// TODO: use $(document).ready instead
790function addLoadEvent(newfun) {
791 var current = window.onload;
792 if (typeof window.onload != 'function') {
793 window.onload = newfun;
794 } else {
795 window.onload = function() {
796 current();
797 newfun();
798 }
799 }
800}
801
802var agent = navigator['userAgent'].toLowerCase();
803// If a mobile phone, set flag and do mobile setup
804if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
805 (agent.indexOf("blackberry") != -1) ||
806 (agent.indexOf("webos") != -1) ||
807 (agent.indexOf("mini") != -1)) { // opera mini browsers
808 isMobile = true;
809}
810
811
Scott Main498d7102013-08-21 15:47:38 -0700812$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700813 $("pre:not(.no-pretty-print)").addClass("prettyprint");
814 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700815});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700816
Scott Maine4d8f1b2012-06-21 18:03:05 -0700817
818
819
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700820/* ######### RESIZE THE SIDENAV ########## */
Scott Maine4d8f1b2012-06-21 18:03:05 -0700821
822function resizeNav(delay) {
823 var $nav = $("#devdoc-nav");
824 var $window = $(window);
825 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700826
Scott Maine4d8f1b2012-06-21 18:03:05 -0700827 // Get the height of entire window and the total header height.
828 // Then figure out based on scroll position whether the header is visible
829 var windowHeight = $window.height();
830 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700831 var headerHeight = $('#header-wrapper').outerHeight();
832 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700833
834 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700835 // Could be either margin or top position, depending on whether the nav is fixed.
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700836 var topMargin = (parseInt($nav.css('top')) || 20) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700837 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700838
Scott Maine4d8f1b2012-06-21 18:03:05 -0700839 // Depending on whether the header is visible, set the side nav's height.
840 if (headerVisible) {
841 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700842 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700843 } else {
844 // Once header is off screen, the nav height is almost full window height
845 navHeight = windowHeight - topMargin;
846 }
Scott Main3b90aff2013-08-01 18:09:35 -0700847
848
849
Scott Maine4d8f1b2012-06-21 18:03:05 -0700850 $scrollPanes = $(".scroll-pane");
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700851 if ($window.width() < 720) {
852 $nav.css('height', '');
853 } else if ($scrollPanes.length > 1) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700854 // subtract the height of the api level widget and nav swapper from the available nav height
855 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700856
Scott Maine4d8f1b2012-06-21 18:03:05 -0700857 $("#swapper").css({height:navHeight + "px"});
858 if ($("#nav-tree").is(":visible")) {
859 $("#nav-tree").css({height:navHeight});
860 }
Scott Main3b90aff2013-08-01 18:09:35 -0700861
862 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700863 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700864
865 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700866 // then the package panel should begin to shrink
867 if (parseInt(classesHeight) <= 0) {
868 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
869 $("#packages-nav").css({height:navHeight - 10});
870 }
Scott Main3b90aff2013-08-01 18:09:35 -0700871
Scott Maine4d8f1b2012-06-21 18:03:05 -0700872 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
873 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700874
875
Scott Maine4d8f1b2012-06-21 18:03:05 -0700876 } else {
877 $nav.height(navHeight);
878 }
Scott Main3b90aff2013-08-01 18:09:35 -0700879
Scott Maine4d8f1b2012-06-21 18:03:05 -0700880 if (delay) {
881 updateFromResize = true;
882 delayedReInitScrollbars(delay);
883 } else {
884 reInitScrollbars();
885 }
Scott Main3b90aff2013-08-01 18:09:35 -0700886
Scott Maine4d8f1b2012-06-21 18:03:05 -0700887}
888
889var updateScrollbars = false;
890var updateFromResize = false;
891
892/* Re-initialize the scrollbars to account for changed nav size.
893 * This method postpones the actual update by a 1/4 second in order to optimize the
894 * scroll performance while the header is still visible, because re-initializing the
895 * scroll panes is an intensive process.
896 */
897function delayedReInitScrollbars(delay) {
898 // If we're scheduled for an update, but have received another resize request
899 // before the scheduled resize has occured, just ignore the new request
900 // (and wait for the scheduled one).
901 if (updateScrollbars && updateFromResize) {
902 updateFromResize = false;
903 return;
904 }
Scott Main3b90aff2013-08-01 18:09:35 -0700905
Scott Maine4d8f1b2012-06-21 18:03:05 -0700906 // We're scheduled for an update and the update request came from this method's setTimeout
907 if (updateScrollbars && !updateFromResize) {
908 reInitScrollbars();
909 updateScrollbars = false;
910 } else {
911 updateScrollbars = true;
912 updateFromResize = false;
913 setTimeout('delayedReInitScrollbars()',delay);
914 }
915}
916
917/* Re-initialize the scrollbars to account for changed nav size. */
918function reInitScrollbars() {
919 var pane = $(".scroll-pane").each(function(){
920 var api = $(this).data('jsp');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700921 if (!api) {return;}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700922 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700923 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700924 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
925}
926
927
928/* Resize the height of the nav panels in the reference,
929 * and save the new size to a cookie */
930function saveNavPanels() {
931 var basePath = getBaseUri(location.pathname);
932 var section = basePath.substring(1,basePath.indexOf("/",1));
smain@google.com6bdcb982014-11-14 11:53:07 -0800933 writeCookie("height", resizePackagesNav.css("height"), section);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700934}
935
936
937
938function restoreHeight(packageHeight) {
939 $("#resize-packages-nav").height(packageHeight);
940 $("#packages-nav").height(packageHeight);
941 // var classesHeight = navHeight - packageHeight;
942 // $("#classes-nav").css({height:classesHeight});
943 // $("#classes-nav .jspContainer").css({height:classesHeight});
944}
945
946
947
948/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
949
950
951
952
953
Scott Main3b90aff2013-08-01 18:09:35 -0700954/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700955 This is called when the page finished loading. */
956function scrollIntoView(nav) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700957 return;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700958 var $nav = $("#"+nav);
959 var element = $nav.jScrollPane({/* ...settings... */});
960 var api = element.data('jsp');
961
962 if ($nav.is(':visible')) {
963 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700964 if ($selected.length == 0) {
965 // If no selected item found, exit
966 return;
967 }
Scott Main52dd2062013-08-15 12:22:28 -0700968 // get the selected item's offset from its container nav by measuring the item's offset
969 // relative to the document then subtract the container nav's offset relative to the document
970 var selectedOffset = $selected.offset().top - $nav.offset().top;
971 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
972 // if it's more than 80% down the nav
973 // scroll the item up by an amount equal to 80% the container nav's height
974 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700975 }
976 }
977}
978
979
980
981
982
983
984/* Show popup dialogs */
985function showDialog(id) {
986 $dialog = $("#"+id);
987 $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>');
988 $dialog.wrapInner('<div/>');
989 $dialog.removeClass("hide");
990}
991
992
993
994
995
996/* ######### COOKIES! ########## */
997
998function readCookie(cookie) {
999 var myCookie = cookie_namespace+"_"+cookie+"=";
1000 if (document.cookie) {
1001 var index = document.cookie.indexOf(myCookie);
1002 if (index != -1) {
1003 var valStart = index + myCookie.length;
1004 var valEnd = document.cookie.indexOf(";", valStart);
1005 if (valEnd == -1) {
1006 valEnd = document.cookie.length;
1007 }
1008 var val = document.cookie.substring(valStart, valEnd);
1009 return val;
1010 }
1011 }
1012 return 0;
1013}
1014
smain@google.com6bdcb982014-11-14 11:53:07 -08001015function writeCookie(cookie, val, section) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001016 if (val==undefined) return;
1017 section = section == null ? "_" : "_"+section+"_";
smain@google.com6bdcb982014-11-14 11:53:07 -08001018 var age = 2*365*24*60*60; // set max-age to 2 years
Scott Main3b90aff2013-08-01 18:09:35 -07001019 var cookieValue = cookie_namespace + section + cookie + "=" + val
smain@google.com80e38f42014-11-03 10:47:12 -08001020 + "; max-age=" + age +"; path=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -07001021 document.cookie = cookieValue;
1022}
1023
1024/* ######### END COOKIES! ########## */
1025
1026
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001027var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -07001028var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001029var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -07001030/* Sets the vertical scoll position at which the sticky bar should appear.
1031 This method is called to reset the position when search results appear or hide */
1032function setStickyTop() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001033 stickyTop = $('#header-wrapper').outerHeight() - $('#header > .dac-header-inner').outerHeight();
Dirk Doughertyc3921652014-05-13 16:55:26 -07001034}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001035
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001036/*
Scott Mainb16376f2014-05-21 20:35:47 -07001037 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -07001038 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001039$(window).scroll(function(event) {
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001040 // Exit if the mouse target is a DIV, because that means the event is coming
1041 // from a scrollable div and so there's no need to make adjustments to our layout
1042 if ($(event.target).nodeName == "DIV") {
1043 return;
1044 }
1045
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001046 checkSticky();
1047});
1048
1049function checkSticky() {
1050 setStickyTop();
1051 var $headerEl = $('#header');
1052 // Exit if there's no sidenav
1053 if ($('#side-nav').length == 0) return;
1054
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001055 var top = $(window).scrollTop();
1056 // we set the navbar fixed when the scroll position is beyond the height of the site header...
1057 var shouldBeSticky = top >= stickyTop;
1058 // ... except if the document content is shorter than the sidenav height.
1059 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1060 if ($("#doc-col").height() < $("#side-nav").height()) {
1061 shouldBeSticky = false;
1062 }
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001063 // Nor on mobile
1064 if (window.innerWidth < 720) {
1065 shouldBeSticky = false;
1066 }
Scott Mainf5257812014-05-22 17:26:38 -07001067 // Account for horizontal scroll
1068 var scrollLeft = $(window).scrollLeft();
1069 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1070 if (sticky && (scrollLeft != prevScrollLeft)) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001071 updateSideNavDimensions();
Scott Mainf5257812014-05-22 17:26:38 -07001072 prevScrollLeft = scrollLeft;
1073 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001074
1075 // Don't continue if the header is sufficently far away
1076 // (to avoid intensive resizing that slows scrolling)
1077 if (sticky == shouldBeSticky) {
1078 return;
1079 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001080
1081 // If sticky header visible and position is now near top, hide sticky
1082 if (sticky && !shouldBeSticky) {
1083 sticky = false;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001084 // make the sidenav static again
1085 $('#devdoc-nav')
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001086 .removeClass('fixed')
1087 .css({'width':'auto','margin':''});
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001088 // delay hide the sticky
Dirk Dougherty29e93432015-05-05 18:17:13 -07001089 $headerEl.removeClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001090
1091 // update the sidenaav position for side scrolling
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001092 updateSideNavDimensions();
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001093 } else if (!sticky && shouldBeSticky) {
1094 sticky = true;
Dirk Dougherty29e93432015-05-05 18:17:13 -07001095 $headerEl.addClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001096
1097 // make the sidenav fixed
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001098 $('#devdoc-nav')
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001099 .addClass('fixed');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001100
1101 // update the sidenaav position for side scrolling
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001102 updateSideNavDimensions();
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001103
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001104 }
1105 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001106}
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001107
1108/*
1109 * Manages secion card states and nav resize to conclude loading
1110 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001111(function() {
1112 $(document).ready(function() {
1113
Dirk Doughertyc3921652014-05-13 16:55:26 -07001114 // Stack hover states
1115 $('.section-card-menu').each(function(index, el) {
1116 var height = $(el).height();
1117 $(el).css({height:height+'px', position:'relative'});
1118 var $cardInfo = $(el).find('.card-info');
1119
1120 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1121 });
1122
Dirk Doughertyc3921652014-05-13 16:55:26 -07001123 });
1124
1125})();
1126
Scott Maine4d8f1b2012-06-21 18:03:05 -07001127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
Scott Maind7026f72013-06-17 15:08:49 -07001140/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001141
1142
1143
1144
1145
1146function toggle(obj, slide) {
1147 var ul = $("ul:first", obj);
1148 var li = ul.parent();
1149 if (li.hasClass("closed")) {
1150 if (slide) {
1151 ul.slideDown("fast");
1152 } else {
1153 ul.show();
1154 }
1155 li.removeClass("closed");
1156 li.addClass("open");
1157 $(".toggle-img", li).attr("title", "hide pages");
1158 } else {
1159 ul.slideUp("fast");
1160 li.removeClass("open");
1161 li.addClass("closed");
1162 $(".toggle-img", li).attr("title", "show pages");
1163 }
1164}
1165
1166
Scott Maine4d8f1b2012-06-21 18:03:05 -07001167function buildToggleLists() {
1168 $(".toggle-list").each(
1169 function(i) {
1170 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1171 $(this).addClass("closed");
1172 });
1173}
1174
1175
1176
Scott Maind7026f72013-06-17 15:08:49 -07001177function hideNestedItems(list, toggle) {
1178 $list = $(list);
1179 // hide nested lists
1180 if($list.hasClass('showing')) {
1181 $("li ol", $list).hide('fast');
1182 $list.removeClass('showing');
1183 // show nested lists
1184 } else {
1185 $("li ol", $list).show('fast');
1186 $list.addClass('showing');
1187 }
1188 $(".more,.less",$(toggle)).toggle();
1189}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001190
1191
smain@google.com95948b82014-06-16 19:24:25 -07001192/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1193function setupIdeDocToggle() {
1194 $( "select.ide" ).change(function() {
1195 var selected = $(this).find("option:selected").attr("value");
1196 $(".select-ide").hide();
1197 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001198
smain@google.com95948b82014-06-16 19:24:25 -07001199 $("select.ide").val(selected);
1200 });
1201}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226/* REFERENCE NAV SWAP */
1227
1228
1229function getNavPref() {
1230 var v = readCookie('reference_nav');
1231 if (v != NAV_PREF_TREE) {
1232 v = NAV_PREF_PANELS;
1233 }
1234 return v;
1235}
1236
1237function chooseDefaultNav() {
1238 nav_pref = getNavPref();
1239 if (nav_pref == NAV_PREF_TREE) {
1240 $("#nav-panels").toggle();
1241 $("#panel-link").toggle();
1242 $("#nav-tree").toggle();
1243 $("#tree-link").toggle();
1244 }
1245}
1246
1247function swapNav() {
1248 if (nav_pref == NAV_PREF_TREE) {
1249 nav_pref = NAV_PREF_PANELS;
1250 } else {
1251 nav_pref = NAV_PREF_TREE;
1252 init_default_navtree(toRoot);
1253 }
smain@google.com6bdcb982014-11-14 11:53:07 -08001254 writeCookie("nav", nav_pref, "reference");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001255
1256 $("#nav-panels").toggle();
1257 $("#panel-link").toggle();
1258 $("#nav-tree").toggle();
1259 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001260
Scott Maine4d8f1b2012-06-21 18:03:05 -07001261 resizeNav();
1262
1263 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1264 $("#nav-tree .jspContainer:visible")
1265 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1266 // Another nasty hack to make the scrollbar appear now that we have height
1267 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001268
Scott Maine4d8f1b2012-06-21 18:03:05 -07001269 if ($("#nav-tree").is(':visible')) {
1270 scrollIntoView("nav-tree");
1271 } else {
1272 scrollIntoView("packages-nav");
1273 scrollIntoView("classes-nav");
1274 }
1275}
1276
1277
1278
Scott Mainf5089842012-08-14 16:31:07 -07001279/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001280/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001281/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001282
1283function getBaseUri(uri) {
1284 var intlUrl = (uri.substring(0,6) == "/intl/");
1285 if (intlUrl) {
1286 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1287 base = base.substring(base.indexOf('/')+1, base.length);
1288 //alert("intl, returning base url: /" + base);
1289 return ("/" + base);
1290 } else {
1291 //alert("not intl, returning uri as found.");
1292 return uri;
1293 }
1294}
1295
1296function requestAppendHL(uri) {
1297//append "?hl=<lang> to an outgoing request (such as to blog)
1298 var lang = getLangPref();
1299 if (lang) {
1300 var q = 'hl=' + lang;
1301 uri += '?' + q;
1302 window.location = uri;
1303 return false;
1304 } else {
1305 return true;
1306 }
1307}
1308
1309
Scott Maine4d8f1b2012-06-21 18:03:05 -07001310function changeNavLang(lang) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001311 if (lang === 'en') { return; }
1312
1313 var $links = $('a[' + lang + '-lang]');
1314 $links.each(function(){ // for each link with a translation
Scott Main6eb95f12012-10-02 17:12:23 -07001315 var $link = $(this);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001316 // put the desired language from the attribute as the text
1317 $link.text($link.attr(lang + '-lang'))
Scott Main6eb95f12012-10-02 17:12:23 -07001318 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001319}
1320
Scott Main015d6162013-01-29 09:01:52 -08001321function changeLangPref(lang, submit) {
smain@google.com6bdcb982014-11-14 11:53:07 -08001322 writeCookie("pref_lang", lang, null);
Scott Main015d6162013-01-29 09:01:52 -08001323
1324 // ####### TODO: Remove this condition once we're stable on devsite #######
1325 // This condition is only needed if we still need to support legacy GAE server
1326 if (devsite) {
1327 // Switch language when on Devsite server
1328 if (submit) {
1329 $("#setlang").submit();
1330 }
1331 } else {
1332 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001333 if (submit) {
1334 window.location = getBaseUri(location.pathname);
1335 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001336 }
1337}
1338
1339function loadLangPref() {
1340 var lang = readCookie("pref_lang");
1341 if (lang != 0) {
1342 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1343 }
1344}
1345
1346function getLangPref() {
1347 var lang = $("#language").find(":selected").attr("value");
1348 if (!lang) {
1349 lang = readCookie("pref_lang");
1350 }
1351 return (lang != 0) ? lang : 'en';
1352}
1353
1354/* ########## END LOCALIZATION ############ */
1355
1356
1357
1358
1359
1360
1361/* Used to hide and reveal supplemental content, such as long code samples.
1362 See the companion CSS in android-developer-docs.css */
1363function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001364 var div = $(obj).closest(".toggle-content");
1365 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001366 if (div.hasClass("closed")) { // if it's closed, open it
1367 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001368 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001369 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001370 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001371 + "assets/images/triangle-opened.png");
1372 } else { // if it's open, close it
1373 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001374 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001375 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001376 div.find(".toggle-content").removeClass("open").addClass("closed")
1377 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001378 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001379 + "assets/images/triangle-closed.png");
1380 });
1381 }
1382 return false;
1383}
Scott Mainf5089842012-08-14 16:31:07 -07001384
1385
Scott Maindb3678b2012-10-23 14:13:41 -07001386/* New version of expandable content */
1387function toggleExpandable(link,id) {
1388 if($(id).is(':visible')) {
1389 $(id).slideUp();
1390 $(link).removeClass('expanded');
1391 } else {
1392 $(id).slideDown();
1393 $(link).addClass('expanded');
1394 }
1395}
1396
1397function hideExpandable(ids) {
1398 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001399 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001400}
1401
Scott Mainf5089842012-08-14 16:31:07 -07001402
1403
1404
1405
Scott Main3b90aff2013-08-01 18:09:35 -07001406/*
Scott Mainf5089842012-08-14 16:31:07 -07001407 * Slideshow 1.0
1408 * Used on /index.html and /develop/index.html for carousel
1409 *
1410 * Sample usage:
1411 * HTML -
1412 * <div class="slideshow-container">
1413 * <a href="" class="slideshow-prev">Prev</a>
1414 * <a href="" class="slideshow-next">Next</a>
1415 * <ul>
1416 * <li class="item"><img src="images/marquee1.jpg"></li>
1417 * <li class="item"><img src="images/marquee2.jpg"></li>
1418 * <li class="item"><img src="images/marquee3.jpg"></li>
1419 * <li class="item"><img src="images/marquee4.jpg"></li>
1420 * </ul>
1421 * </div>
1422 *
1423 * <script type="text/javascript">
1424 * $('.slideshow-container').dacSlideshow({
1425 * auto: true,
1426 * btnPrev: '.slideshow-prev',
1427 * btnNext: '.slideshow-next'
1428 * });
1429 * </script>
1430 *
1431 * Options:
1432 * btnPrev: optional identifier for previous button
1433 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001434 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001435 * auto: whether or not to auto-proceed
1436 * speed: animation speed
1437 * autoTime: time between auto-rotation
1438 * easing: easing function for transition
1439 * start: item to select by default
1440 * scroll: direction to scroll in
1441 * pagination: whether or not to include dotted pagination
1442 *
1443 */
1444
1445 (function($) {
1446 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001447
Scott Mainf5089842012-08-14 16:31:07 -07001448 //Options - see above
1449 o = $.extend({
1450 btnPrev: null,
1451 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001452 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001453 auto: true,
1454 speed: 500,
1455 autoTime: 12000,
1456 easing: null,
1457 start: 0,
1458 scroll: 1,
1459 pagination: true
1460
1461 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001462
1463 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001464 return this.each(function() {
1465
1466 var running = false;
1467 var animCss = o.vertical ? "top" : "left";
1468 var sizeCss = o.vertical ? "height" : "width";
1469 var div = $(this);
1470 var ul = $("ul", div);
1471 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001472 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001473 var timer = null;
1474
1475 var li = $("li", ul);
1476 var itemLength = li.size();
1477 var curr = o.start;
1478
1479 li.css({float: o.vertical ? "none" : "left"});
1480 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1481 div.css({position: "relative", "z-index": "2", left: "0px"});
1482
1483 var liSize = o.vertical ? height(li) : width(li);
1484 var ulSize = liSize * itemLength;
1485 var divSize = liSize;
1486
1487 li.css({width: li.width(), height: li.height()});
1488 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1489
1490 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001491
Scott Mainf5089842012-08-14 16:31:07 -07001492 //Pagination
1493 if (o.pagination) {
1494 var pagination = $("<div class='pagination'></div>");
1495 var pag_ul = $("<ul></ul>");
1496 if (tl > 1) {
1497 for (var i=0;i<tl;i++) {
1498 var li = $("<li>"+i+"</li>");
1499 pag_ul.append(li);
1500 if (i==o.start) li.addClass('active');
1501 li.click(function() {
1502 go(parseInt($(this).text()));
1503 })
1504 }
1505 pagination.append(pag_ul);
1506 div.append(pagination);
1507 }
1508 }
Scott Main3b90aff2013-08-01 18:09:35 -07001509
Scott Mainf5089842012-08-14 16:31:07 -07001510 //Previous button
1511 if(o.btnPrev)
1512 $(o.btnPrev).click(function(e) {
1513 e.preventDefault();
1514 return go(curr-o.scroll);
1515 });
1516
1517 //Next button
1518 if(o.btnNext)
1519 $(o.btnNext).click(function(e) {
1520 e.preventDefault();
1521 return go(curr+o.scroll);
1522 });
Scott Maineb410352013-01-14 19:03:40 -08001523
1524 //Pause button
1525 if(o.btnPause)
1526 $(o.btnPause).click(function(e) {
1527 e.preventDefault();
1528 if ($(this).hasClass('paused')) {
1529 startRotateTimer();
1530 } else {
1531 pauseRotateTimer();
1532 }
1533 });
Scott Main3b90aff2013-08-01 18:09:35 -07001534
Scott Mainf5089842012-08-14 16:31:07 -07001535 //Auto rotation
1536 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001537
Scott Mainf5089842012-08-14 16:31:07 -07001538 function startRotateTimer() {
1539 clearInterval(timer);
1540 timer = setInterval(function() {
1541 if (curr == tl-1) {
1542 go(0);
1543 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001544 go(curr+o.scroll);
1545 }
Scott Mainf5089842012-08-14 16:31:07 -07001546 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001547 $(o.btnPause).removeClass('paused');
1548 }
1549
1550 function pauseRotateTimer() {
1551 clearInterval(timer);
1552 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001553 }
1554
1555 //Go to an item
1556 function go(to) {
1557 if(!running) {
1558
1559 if(to<0) {
1560 to = itemLength-1;
1561 } else if (to>itemLength-1) {
1562 to = 0;
1563 }
1564 curr = to;
1565
1566 running = true;
1567
1568 ul.animate(
1569 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1570 function() {
1571 running = false;
1572 }
1573 );
1574
1575 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1576 $( (curr-o.scroll<0 && o.btnPrev)
1577 ||
1578 (curr+o.scroll > itemLength && o.btnNext)
1579 ||
1580 []
1581 ).addClass("disabled");
1582
Scott Main3b90aff2013-08-01 18:09:35 -07001583
Scott Mainf5089842012-08-14 16:31:07 -07001584 var nav_items = $('li', pagination);
1585 nav_items.removeClass('active');
1586 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001587
Scott Mainf5089842012-08-14 16:31:07 -07001588
1589 }
1590 if(o.auto) startRotateTimer();
1591 return false;
1592 };
1593 });
1594 };
1595
1596 function css(el, prop) {
1597 return parseInt($.css(el[0], prop)) || 0;
1598 };
1599 function width(el) {
1600 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1601 };
1602 function height(el) {
1603 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1604 };
1605
1606 })(jQuery);
1607
1608
Scott Main3b90aff2013-08-01 18:09:35 -07001609/*
Scott Mainf5089842012-08-14 16:31:07 -07001610 * dacSlideshow 1.0
1611 * Used on develop/index.html for side-sliding tabs
1612 *
1613 * Sample usage:
1614 * HTML -
1615 * <div class="slideshow-container">
1616 * <a href="" class="slideshow-prev">Prev</a>
1617 * <a href="" class="slideshow-next">Next</a>
1618 * <ul>
1619 * <li class="item"><img src="images/marquee1.jpg"></li>
1620 * <li class="item"><img src="images/marquee2.jpg"></li>
1621 * <li class="item"><img src="images/marquee3.jpg"></li>
1622 * <li class="item"><img src="images/marquee4.jpg"></li>
1623 * </ul>
1624 * </div>
1625 *
1626 * <script type="text/javascript">
1627 * $('.slideshow-container').dacSlideshow({
1628 * auto: true,
1629 * btnPrev: '.slideshow-prev',
1630 * btnNext: '.slideshow-next'
1631 * });
1632 * </script>
1633 *
1634 * Options:
1635 * btnPrev: optional identifier for previous button
1636 * btnNext: optional identifier for next button
1637 * auto: whether or not to auto-proceed
1638 * speed: animation speed
1639 * autoTime: time between auto-rotation
1640 * easing: easing function for transition
1641 * start: item to select by default
1642 * scroll: direction to scroll in
1643 * pagination: whether or not to include dotted pagination
1644 *
1645 */
1646 (function($) {
1647 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001648
Scott Mainf5089842012-08-14 16:31:07 -07001649 //Options - see above
1650 o = $.extend({
1651 speed : 250,
1652 easing: null,
1653 nav_id: null,
1654 frame_id: null
1655 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001656
1657 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001658 return this.each(function() {
1659
1660 var curr = 0;
1661 var running = false;
1662 var animCss = "margin-left";
1663 var sizeCss = "width";
1664 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001665
Scott Mainf5089842012-08-14 16:31:07 -07001666 var nav = $(o.nav_id, div);
1667 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001668 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001669 var frame = div.find(o.frame_id);
1670 var content_width = $(frame).find('ul').width();
1671 //Buttons
1672 $(nav_li).click(function(e) {
1673 go($(nav_li).index($(this)));
1674 })
Scott Main3b90aff2013-08-01 18:09:35 -07001675
Scott Mainf5089842012-08-14 16:31:07 -07001676 //Go to an item
1677 function go(to) {
1678 if(!running) {
1679 curr = to;
1680 running = true;
1681
1682 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1683 function() {
1684 running = false;
1685 }
1686 );
1687
Scott Main3b90aff2013-08-01 18:09:35 -07001688
Scott Mainf5089842012-08-14 16:31:07 -07001689 nav_li.removeClass('active');
1690 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001691
Scott Mainf5089842012-08-14 16:31:07 -07001692
1693 }
1694 return false;
1695 };
1696 });
1697 };
1698
1699 function css(el, prop) {
1700 return parseInt($.css(el[0], prop)) || 0;
1701 };
1702 function width(el) {
1703 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1704 };
1705 function height(el) {
1706 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1707 };
1708
1709 })(jQuery);
1710
1711
1712
1713
1714
1715/* ######################################################## */
1716/* ################ SEARCH SUGGESTIONS ################## */
1717/* ######################################################## */
1718
1719
Scott Main7e447ed2013-02-19 17:22:37 -08001720
Scott Main0e76e7e2013-03-12 10:24:07 -07001721var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1722var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1723
Scott Mainf5089842012-08-14 16:31:07 -07001724var gMatches = new Array();
1725var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001726var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001727var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1728var gListLength = 0;
1729
1730
1731var gGoogleMatches = new Array();
1732var ROW_COUNT_GOOGLE = 15; // max number of results in list
1733var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001734
Scott Main0e76e7e2013-03-12 10:24:07 -07001735var gDocsMatches = new Array();
1736var ROW_COUNT_DOCS = 100; // max number of results in list
1737var gDocsListLength = 0;
1738
Scott Mainde295272013-03-25 15:48:35 -07001739function onSuggestionClick(link) {
1740 // When user clicks a suggested document, track it
smain@google.comed677d72014-12-12 10:19:17 -08001741 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
1742 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07001743}
1744
Scott Mainf5089842012-08-14 16:31:07 -07001745function set_item_selected($li, selected)
1746{
1747 if (selected) {
1748 $li.attr('class','jd-autocomplete jd-selected');
1749 } else {
1750 $li.attr('class','jd-autocomplete');
1751 }
1752}
1753
1754function set_item_values(toroot, $li, match)
1755{
1756 var $link = $('a',$li);
1757 $link.html(match.__hilabel || match.label);
1758 $link.attr('href',toroot + match.link);
1759}
1760
Scott Main719acb42013-12-05 16:05:09 -08001761function set_item_values_jd(toroot, $li, match)
1762{
1763 var $link = $('a',$li);
1764 $link.html(match.title);
1765 $link.attr('href',toroot + match.url);
1766}
1767
Scott Main0e76e7e2013-03-12 10:24:07 -07001768function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001769 var $li = $("<li class='jd-autocomplete'></li>");
1770 $list.append($li);
1771
1772 $li.mousedown(function() {
1773 window.location = this.firstChild.getAttribute("href");
1774 });
1775 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001776 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001777 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001778 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1779 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001780 });
Scott Mainde295272013-03-25 15:48:35 -07001781 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001782 $li.attr('class','show-item');
1783 return $li;
1784}
1785
Scott Mainf5089842012-08-14 16:31:07 -07001786function sync_selection_table(toroot)
1787{
Scott Mainf5089842012-08-14 16:31:07 -07001788 var $li; //list item jquery object
1789 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001790
Scott Main0e76e7e2013-03-12 10:24:07 -07001791 // if there are NO results at all, hide all columns
1792 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1793 $('.suggest-card').hide(300);
1794 return;
1795 }
1796
1797 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001798 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001799 // reveal suggestion list
Scott Main0e76e7e2013-03-12 10:24:07 -07001800 $('.suggest-card.reference').show();
1801 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001802
Scott Main0e76e7e2013-03-12 10:24:07 -07001803 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001804 $(".suggest-card.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001805
Scott Main0e76e7e2013-03-12 10:24:07 -07001806 // ########### ANDROID RESULTS #############
1807 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001808
Scott Main0e76e7e2013-03-12 10:24:07 -07001809 // determine android results to show
1810 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1811 gMatches.length : ROW_COUNT_FRAMEWORK;
1812 for (i=0; i<gListLength; i++) {
1813 var $li = new_suggestion($(".suggest-card.reference ul"));
1814 set_item_values(toroot, $li, gMatches[i]);
1815 set_item_selected($li, i == gSelectedIndex);
1816 }
1817 }
Scott Main7e447ed2013-02-19 17:22:37 -08001818
Scott Main0e76e7e2013-03-12 10:24:07 -07001819 // ########### GOOGLE RESULTS #############
1820 if (gGoogleMatches.length > 0) {
1821 // show header for list
1822 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001823
Scott Main0e76e7e2013-03-12 10:24:07 -07001824 // determine google results to show
1825 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1826 for (i=0; i<gGoogleListLength; i++) {
1827 var $li = new_suggestion($(".suggest-card.reference ul"));
1828 set_item_values(toroot, $li, gGoogleMatches[i]);
1829 set_item_selected($li, i == gSelectedIndex);
1830 }
1831 }
Scott Mainf5089842012-08-14 16:31:07 -07001832 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001833 $('.suggest-card.reference').hide();
Scott Main0e76e7e2013-03-12 10:24:07 -07001834 }
1835
1836 // ########### JD DOC RESULTS #############
1837 if (gDocsMatches.length > 0) {
1838 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001839 $(".suggest-card:not(.reference) li").remove();
Scott Main0e76e7e2013-03-12 10:24:07 -07001840
1841 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001842 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1843 // The order must match the reverse order that each section appears as a card in
1844 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001845 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1846 for (i=0; i<gDocsListLength; i++) {
1847 var sugg = gDocsMatches[i];
1848 var $li;
1849 if (sugg.type == "design") {
1850 $li = new_suggestion($(".suggest-card.design ul"));
1851 } else
1852 if (sugg.type == "distribute") {
1853 $li = new_suggestion($(".suggest-card.distribute ul"));
1854 } else
Scott Main719acb42013-12-05 16:05:09 -08001855 if (sugg.type == "samples") {
1856 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1857 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001858 if (sugg.type == "training") {
1859 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1860 } else
Scott Main719acb42013-12-05 16:05:09 -08001861 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001862 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1863 } else {
1864 continue;
1865 }
1866
Scott Main719acb42013-12-05 16:05:09 -08001867 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001868 set_item_selected($li, i == gSelectedIndex);
1869 }
1870
1871 // add heading and show or hide card
1872 if ($(".suggest-card.design li").length > 0) {
1873 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1874 $(".suggest-card.design").show(300);
1875 } else {
1876 $('.suggest-card.design').hide(300);
1877 }
1878 if ($(".suggest-card.distribute li").length > 0) {
1879 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1880 $(".suggest-card.distribute").show(300);
1881 } else {
1882 $('.suggest-card.distribute').hide(300);
1883 }
1884 if ($(".child-card.guides li").length > 0) {
1885 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1886 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1887 }
1888 if ($(".child-card.training li").length > 0) {
1889 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1890 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1891 }
Scott Main719acb42013-12-05 16:05:09 -08001892 if ($(".child-card.samples li").length > 0) {
1893 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1894 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1895 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001896
1897 if ($(".suggest-card.develop li").length > 0) {
1898 $(".suggest-card.develop").show(300);
1899 } else {
1900 $('.suggest-card.develop').hide(300);
1901 }
1902
1903 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001904 $('.suggest-card:not(.reference)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001905 }
1906}
1907
Scott Main0e76e7e2013-03-12 10:24:07 -07001908/** Called by the search input's onkeydown and onkeyup events.
1909 * Handles navigation with keyboard arrows, Enter key to invoke search,
1910 * otherwise invokes search suggestions on key-up event.
1911 * @param e The JS event
1912 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001913 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001914 * @returns True if the event should bubble up
1915 */
Scott Mainf5089842012-08-14 16:31:07 -07001916function search_changed(e, kd, toroot)
1917{
Scott Main719acb42013-12-05 16:05:09 -08001918 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001919 var search = document.getElementById("search_autocomplete");
1920 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001921 // get the ul hosting the currently selected item
1922 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1923 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1924 var $selectedUl = $columns[gSelectedColumn];
1925
Scott Mainf5089842012-08-14 16:31:07 -07001926 // show/hide the close button
1927 if (text != '') {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001928 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07001929 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001930 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07001931 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001932 // 27 = esc
1933 if (e.keyCode == 27) {
1934 // close all search results
Dirk Dougherty29e93432015-05-05 18:17:13 -07001935 if (kd) $('#search-close').trigger('click');
Scott Main0e76e7e2013-03-12 10:24:07 -07001936 return true;
1937 }
Scott Mainf5089842012-08-14 16:31:07 -07001938 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001939 else if (e.keyCode == 13) {
1940 if (gSelectedIndex < 0) {
1941 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001942 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1943 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001944 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001945 return true;
1946 } else {
1947 // otherwise, results are already showing, so allow ajax to auto refresh the results
1948 // and ignore this Enter press to avoid the reload.
1949 return false;
1950 }
1951 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001952 // click the link corresponding to selected item
1953 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001954 return false;
1955 }
1956 }
Scott Mainb16376f2014-05-21 20:35:47 -07001957 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001958 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001959 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001960 if ((sticky ) && (search.value != "")) {
1961 $('body,html').animate({scrollTop:0}, '500', 'swing');
1962 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001963 return true;
1964 }
1965 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001966 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001967 // if the next item is a header, skip it
1968 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001969 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001970 }
1971 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001972 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001973 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001974 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1975 // If user reaches top, reset selected column
1976 if (gSelectedIndex < 0) {
1977 gSelectedColumn = -1;
1978 }
Scott Mainf5089842012-08-14 16:31:07 -07001979 }
1980 return false;
1981 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001982 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001983 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001984 // if the next item is a header, skip it
1985 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001986 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08001987 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001988 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
1989 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
1990 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001991 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07001992 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07001993 }
1994 return false;
1995 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001996 // Consider left/right arrow navigation
1997 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
1998 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
1999 // 37 LEFT ARROW
2000 // go left only if current column is not left-most column (last column)
2001 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
2002 $('li', $selectedUl).removeClass('jd-selected');
2003 gSelectedColumn++;
2004 $selectedUl = $columns[gSelectedColumn];
2005 // keep or reset the selected item to last item as appropriate
2006 gSelectedIndex = gSelectedIndex >
2007 $("li", $selectedUl).length-1 ?
2008 $("li", $selectedUl).length-1 : gSelectedIndex;
2009 // if the corresponding item is a header, move down
2010 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2011 gSelectedIndex++;
2012 }
Scott Main3b90aff2013-08-01 18:09:35 -07002013 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002014 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2015 return false;
2016 }
2017 // 39 RIGHT ARROW
2018 // go right only if current column is not the right-most column (first column)
2019 else if (e.keyCode == 39 && gSelectedColumn > 0) {
2020 $('li', $selectedUl).removeClass('jd-selected');
2021 gSelectedColumn--;
2022 $selectedUl = $columns[gSelectedColumn];
2023 // keep or reset the selected item to last item as appropriate
2024 gSelectedIndex = gSelectedIndex >
2025 $("li", $selectedUl).length-1 ?
2026 $("li", $selectedUl).length-1 : gSelectedIndex;
2027 // if the corresponding item is a header, move down
2028 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2029 gSelectedIndex++;
2030 }
Scott Main3b90aff2013-08-01 18:09:35 -07002031 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002032 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2033 return false;
2034 }
2035 }
2036
Scott Main719acb42013-12-05 16:05:09 -08002037 // if key-up event and not arrow down/up/left/right,
2038 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07002039 else if (!kd && (e.keyCode != 40)
2040 && (e.keyCode != 38)
2041 && (e.keyCode != 37)
2042 && (e.keyCode != 39)) {
2043 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07002044 gMatches = new Array();
2045 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002046 gGoogleMatches = new Array();
2047 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07002048 gDocsMatches = new Array();
2049 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002050
2051 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07002052 for (var i=0; i<DATA.length; i++) {
2053 var s = DATA[i];
2054 if (text.length != 0 &&
2055 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2056 gMatches[matchedCount] = s;
2057 matchedCount++;
2058 }
2059 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002060 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07002061 for (var i=0; i<gMatches.length; i++) {
2062 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08002063 }
2064
2065
2066 // Search for Google matches
2067 for (var i=0; i<GOOGLE_DATA.length; i++) {
2068 var s = GOOGLE_DATA[i];
2069 if (text.length != 0 &&
2070 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2071 gGoogleMatches[matchedCountGoogle] = s;
2072 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07002073 }
2074 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002075 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08002076 for (var i=0; i<gGoogleMatches.length; i++) {
2077 var s = gGoogleMatches[i];
2078 }
2079
Scott Mainf5089842012-08-14 16:31:07 -07002080 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07002081
2082
2083
Scott Main719acb42013-12-05 16:05:09 -08002084 // Search for matching JD docs
Dirk Dougherty9b7f8f22014-11-01 17:08:56 -07002085 if (text.length >= 2) {
Scott Main719acb42013-12-05 16:05:09 -08002086 // Regex to match only the beginning of a word
2087 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
2088
2089
2090 // Search for Training classes
2091 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002092 // current search comparison, with counters for tag and title,
2093 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08002094 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07002095 s.matched_tag = 0;
2096 s.matched_title = 0;
2097 var matched = false;
2098
2099 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08002100 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002101 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08002102 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002103 matched = true;
2104 s.matched_tag = j + 1; // add 1 to index position
2105 }
2106 }
Scott Main719acb42013-12-05 16:05:09 -08002107 // Don't consider doc title for lessons (only for class landing pages),
2108 // unless the lesson has a tag that already matches
2109 if ((s.lang == currentLang) &&
2110 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002111 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08002112 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002113 matched = true;
2114 s.matched_title = 1;
2115 }
2116 }
2117 if (matched) {
2118 gDocsMatches[matchedCountDocs] = s;
2119 matchedCountDocs++;
2120 }
2121 }
Scott Main719acb42013-12-05 16:05:09 -08002122
2123
2124 // Search for API Guides
2125 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2126 // current search comparison, with counters for tag and title,
2127 // used later to improve ranking
2128 var s = GUIDE_RESOURCES[i];
2129 s.matched_tag = 0;
2130 s.matched_title = 0;
2131 var matched = false;
2132
2133 // Check if query matches any tags; work backwards toward 1 to assist ranking
2134 for (var j = s.keywords.length - 1; j >= 0; j--) {
2135 // it matches a tag
2136 if (s.keywords[j].toLowerCase().match(textRegex)) {
2137 matched = true;
2138 s.matched_tag = j + 1; // add 1 to index position
2139 }
2140 }
2141 // Check if query matches the doc title, but only for current language
2142 if (s.lang == currentLang) {
2143 // if query matches the doc title
2144 if (s.title.toLowerCase().match(textRegex)) {
2145 matched = true;
2146 s.matched_title = 1;
2147 }
2148 }
2149 if (matched) {
2150 gDocsMatches[matchedCountDocs] = s;
2151 matchedCountDocs++;
2152 }
2153 }
2154
2155
2156 // Search for Tools Guides
2157 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2158 // current search comparison, with counters for tag and title,
2159 // used later to improve ranking
2160 var s = TOOLS_RESOURCES[i];
2161 s.matched_tag = 0;
2162 s.matched_title = 0;
2163 var matched = false;
2164
2165 // Check if query matches any tags; work backwards toward 1 to assist ranking
2166 for (var j = s.keywords.length - 1; j >= 0; j--) {
2167 // it matches a tag
2168 if (s.keywords[j].toLowerCase().match(textRegex)) {
2169 matched = true;
2170 s.matched_tag = j + 1; // add 1 to index position
2171 }
2172 }
2173 // Check if query matches the doc title, but only for current language
2174 if (s.lang == currentLang) {
2175 // if query matches the doc title
2176 if (s.title.toLowerCase().match(textRegex)) {
2177 matched = true;
2178 s.matched_title = 1;
2179 }
2180 }
2181 if (matched) {
2182 gDocsMatches[matchedCountDocs] = s;
2183 matchedCountDocs++;
2184 }
2185 }
2186
2187
2188 // Search for About docs
2189 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2190 // current search comparison, with counters for tag and title,
2191 // used later to improve ranking
2192 var s = ABOUT_RESOURCES[i];
2193 s.matched_tag = 0;
2194 s.matched_title = 0;
2195 var matched = false;
2196
2197 // Check if query matches any tags; work backwards toward 1 to assist ranking
2198 for (var j = s.keywords.length - 1; j >= 0; j--) {
2199 // it matches a tag
2200 if (s.keywords[j].toLowerCase().match(textRegex)) {
2201 matched = true;
2202 s.matched_tag = j + 1; // add 1 to index position
2203 }
2204 }
2205 // Check if query matches the doc title, but only for current language
2206 if (s.lang == currentLang) {
2207 // if query matches the doc title
2208 if (s.title.toLowerCase().match(textRegex)) {
2209 matched = true;
2210 s.matched_title = 1;
2211 }
2212 }
2213 if (matched) {
2214 gDocsMatches[matchedCountDocs] = s;
2215 matchedCountDocs++;
2216 }
2217 }
2218
2219
2220 // Search for Design guides
2221 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2222 // current search comparison, with counters for tag and title,
2223 // used later to improve ranking
2224 var s = DESIGN_RESOURCES[i];
2225 s.matched_tag = 0;
2226 s.matched_title = 0;
2227 var matched = false;
2228
2229 // Check if query matches any tags; work backwards toward 1 to assist ranking
2230 for (var j = s.keywords.length - 1; j >= 0; j--) {
2231 // it matches a tag
2232 if (s.keywords[j].toLowerCase().match(textRegex)) {
2233 matched = true;
2234 s.matched_tag = j + 1; // add 1 to index position
2235 }
2236 }
2237 // Check if query matches the doc title, but only for current language
2238 if (s.lang == currentLang) {
2239 // if query matches the doc title
2240 if (s.title.toLowerCase().match(textRegex)) {
2241 matched = true;
2242 s.matched_title = 1;
2243 }
2244 }
2245 if (matched) {
2246 gDocsMatches[matchedCountDocs] = s;
2247 matchedCountDocs++;
2248 }
2249 }
2250
2251
2252 // Search for Distribute guides
2253 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2254 // current search comparison, with counters for tag and title,
2255 // used later to improve ranking
2256 var s = DISTRIBUTE_RESOURCES[i];
2257 s.matched_tag = 0;
2258 s.matched_title = 0;
2259 var matched = false;
2260
2261 // Check if query matches any tags; work backwards toward 1 to assist ranking
2262 for (var j = s.keywords.length - 1; j >= 0; j--) {
2263 // it matches a tag
2264 if (s.keywords[j].toLowerCase().match(textRegex)) {
2265 matched = true;
2266 s.matched_tag = j + 1; // add 1 to index position
2267 }
2268 }
2269 // Check if query matches the doc title, but only for current language
2270 if (s.lang == currentLang) {
2271 // if query matches the doc title
2272 if (s.title.toLowerCase().match(textRegex)) {
2273 matched = true;
2274 s.matched_title = 1;
2275 }
2276 }
2277 if (matched) {
2278 gDocsMatches[matchedCountDocs] = s;
2279 matchedCountDocs++;
2280 }
2281 }
2282
2283
2284 // Search for Google guides
2285 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2286 // current search comparison, with counters for tag and title,
2287 // used later to improve ranking
2288 var s = GOOGLE_RESOURCES[i];
2289 s.matched_tag = 0;
2290 s.matched_title = 0;
2291 var matched = false;
2292
2293 // Check if query matches any tags; work backwards toward 1 to assist ranking
2294 for (var j = s.keywords.length - 1; j >= 0; j--) {
2295 // it matches a tag
2296 if (s.keywords[j].toLowerCase().match(textRegex)) {
2297 matched = true;
2298 s.matched_tag = j + 1; // add 1 to index position
2299 }
2300 }
2301 // Check if query matches the doc title, but only for current language
2302 if (s.lang == currentLang) {
2303 // if query matches the doc title
2304 if (s.title.toLowerCase().match(textRegex)) {
2305 matched = true;
2306 s.matched_title = 1;
2307 }
2308 }
2309 if (matched) {
2310 gDocsMatches[matchedCountDocs] = s;
2311 matchedCountDocs++;
2312 }
2313 }
2314
2315
2316 // Search for Samples
2317 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2318 // current search comparison, with counters for tag and title,
2319 // used later to improve ranking
2320 var s = SAMPLES_RESOURCES[i];
2321 s.matched_tag = 0;
2322 s.matched_title = 0;
2323 var matched = false;
2324 // Check if query matches any tags; work backwards toward 1 to assist ranking
2325 for (var j = s.keywords.length - 1; j >= 0; j--) {
2326 // it matches a tag
2327 if (s.keywords[j].toLowerCase().match(textRegex)) {
2328 matched = true;
2329 s.matched_tag = j + 1; // add 1 to index position
2330 }
2331 }
2332 // Check if query matches the doc title, but only for current language
2333 if (s.lang == currentLang) {
2334 // if query matches the doc title.t
2335 if (s.title.toLowerCase().match(textRegex)) {
2336 matched = true;
2337 s.matched_title = 1;
2338 }
2339 }
2340 if (matched) {
2341 gDocsMatches[matchedCountDocs] = s;
2342 matchedCountDocs++;
2343 }
2344 }
2345
Joe Fernandeza9d796a2015-05-05 22:07:42 -07002346 // Search for Preview Guides
2347 for (var i=0; i<PREVIEW_RESOURCES.length; i++) {
2348 // current search comparison, with counters for tag and title,
2349 // used later to improve ranking
2350 var s = PREVIEW_RESOURCES[i];
2351 s.matched_tag = 0;
2352 s.matched_title = 0;
2353 var matched = false;
2354
2355 // Check if query matches any tags; work backwards toward 1 to assist ranking
2356 for (var j = s.keywords.length - 1; j >= 0; j--) {
2357 // it matches a tag
2358 if (s.keywords[j].toLowerCase().match(textRegex)) {
2359 matched = true;
2360 s.matched_tag = j + 1; // add 1 to index position
2361 }
2362 }
2363 // Check if query matches the doc title, but only for current language
2364 if (s.lang == currentLang) {
2365 // if query matches the doc title
2366 if (s.title.toLowerCase().match(textRegex)) {
2367 matched = true;
2368 s.matched_title = 1;
2369 }
2370 }
2371 if (matched) {
2372 gDocsMatches[matchedCountDocs] = s;
2373 matchedCountDocs++;
2374 }
2375 }
2376
Scott Main719acb42013-12-05 16:05:09 -08002377 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002378 rank_autocomplete_doc_results(text, gDocsMatches);
2379 }
2380
2381 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002382 sync_selection_table(toroot);
2383 return true; // allow the event to bubble up to the search api
2384 }
2385}
2386
Scott Main0e76e7e2013-03-12 10:24:07 -07002387/* Order the jd doc result list based on match quality */
2388function rank_autocomplete_doc_results(query, matches) {
2389 query = query || '';
2390 if (!matches || !matches.length)
2391 return;
2392
2393 var _resultScoreFn = function(match) {
2394 var score = 1.0;
2395
2396 // if the query matched a tag
2397 if (match.matched_tag > 0) {
2398 // multiply score by factor relative to position in tags list (max of 3)
2399 score *= 3 / match.matched_tag;
2400
2401 // if it also matched the title
2402 if (match.matched_title > 0) {
2403 score *= 2;
2404 }
2405 } else if (match.matched_title > 0) {
2406 score *= 3;
2407 }
2408
2409 return score;
2410 };
2411
2412 for (var i=0; i<matches.length; i++) {
2413 matches[i].__resultScore = _resultScoreFn(matches[i]);
2414 }
2415
2416 matches.sort(function(a,b){
2417 var n = b.__resultScore - a.__resultScore;
2418 if (n == 0) // lexicographical sort if scores are the same
2419 n = (a.label < b.label) ? -1 : 1;
2420 return n;
2421 });
2422}
2423
Scott Main7e447ed2013-02-19 17:22:37 -08002424/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002425function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002426 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002427 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002428 return;
2429
2430 // helper function that gets the last occurence index of the given regex
2431 // in the given string, or -1 if not found
2432 var _lastSearch = function(s, re) {
2433 if (s == '')
2434 return -1;
2435 var l = -1;
2436 var tmp;
2437 while ((tmp = s.search(re)) >= 0) {
2438 if (l < 0) l = 0;
2439 l += tmp;
2440 s = s.substr(tmp + 1);
2441 }
2442 return l;
2443 };
2444
2445 // helper function that counts the occurrences of a given character in
2446 // a given string
2447 var _countChar = function(s, c) {
2448 var n = 0;
2449 for (var i=0; i<s.length; i++)
2450 if (s.charAt(i) == c) ++n;
2451 return n;
2452 };
2453
2454 var queryLower = query.toLowerCase();
2455 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2456 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2457 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2458
2459 var _resultScoreFn = function(result) {
2460 // scores are calculated based on exact and prefix matches,
2461 // and then number of path separators (dots) from the last
2462 // match (i.e. favoring classes and deep package names)
2463 var score = 1.0;
2464 var labelLower = result.label.toLowerCase();
2465 var t;
2466 t = _lastSearch(labelLower, partExactAlnumRE);
2467 if (t >= 0) {
2468 // exact part match
2469 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2470 score *= 200 / (partsAfter + 1);
2471 } else {
2472 t = _lastSearch(labelLower, partPrefixAlnumRE);
2473 if (t >= 0) {
2474 // part prefix match
2475 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2476 score *= 20 / (partsAfter + 1);
2477 }
2478 }
2479
2480 return score;
2481 };
2482
Scott Main7e447ed2013-02-19 17:22:37 -08002483 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002484 // if the API is deprecated, default score is 0; otherwise, perform scoring
2485 if (matches[i].deprecated == "true") {
2486 matches[i].__resultScore = 0;
2487 } else {
2488 matches[i].__resultScore = _resultScoreFn(matches[i]);
2489 }
Scott Mainf5089842012-08-14 16:31:07 -07002490 }
2491
Scott Main7e447ed2013-02-19 17:22:37 -08002492 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002493 var n = b.__resultScore - a.__resultScore;
2494 if (n == 0) // lexicographical sort if scores are the same
2495 n = (a.label < b.label) ? -1 : 1;
2496 return n;
2497 });
2498}
2499
Scott Main7e447ed2013-02-19 17:22:37 -08002500/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002501function highlight_autocomplete_result_labels(query) {
2502 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002503 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002504 return;
2505
2506 var queryLower = query.toLowerCase();
2507 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2508 var queryRE = new RegExp(
2509 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2510 for (var i=0; i<gMatches.length; i++) {
2511 gMatches[i].__hilabel = gMatches[i].label.replace(
2512 queryRE, '<b>$1</b>');
2513 }
Scott Main7e447ed2013-02-19 17:22:37 -08002514 for (var i=0; i<gGoogleMatches.length; i++) {
2515 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2516 queryRE, '<b>$1</b>');
2517 }
Scott Mainf5089842012-08-14 16:31:07 -07002518}
2519
2520function search_focus_changed(obj, focused)
2521{
Scott Main3b90aff2013-08-01 18:09:35 -07002522 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002523 if(obj.value == ""){
Dirk Dougherty29e93432015-05-05 18:17:13 -07002524 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002525 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002526 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002527 }
2528}
2529
2530function submit_search() {
2531 var query = document.getElementById('search_autocomplete').value;
2532 location.hash = 'q=' + query;
2533 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002534 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002535 return false;
2536}
2537
2538
2539function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002540 $("#searchResults").slideUp('fast', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002541 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002542 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002543
Scott Mainf5089842012-08-14 16:31:07 -07002544 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002545
Scott Mainf5089842012-08-14 16:31:07 -07002546 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2547 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002548
2549 // forcefully regain key-up event control (previously jacked by search api)
2550 $("#search_autocomplete").keyup(function(event) {
2551 return search_changed(event, false, toRoot);
2552 });
2553
Scott Mainf5089842012-08-14 16:31:07 -07002554 return false;
2555}
2556
2557
2558
2559/* ########################################################## */
2560/* ################ CUSTOM SEARCH ENGINE ################## */
2561/* ########################################################## */
2562
Scott Mainf5089842012-08-14 16:31:07 -07002563var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002564google.load('search', '1', {"callback" : function() {
2565 searchControl = new google.search.SearchControl();
2566 } });
Scott Mainf5089842012-08-14 16:31:07 -07002567
2568function loadSearchResults() {
2569 document.getElementById("search_autocomplete").style.color = "#000";
2570
Scott Mainf5089842012-08-14 16:31:07 -07002571 searchControl = new google.search.SearchControl();
2572
2573 // use our existing search form and use tabs when multiple searchers are used
2574 drawOptions = new google.search.DrawOptions();
2575 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2576 drawOptions.setInput(document.getElementById("search_autocomplete"));
2577
2578 // configure search result options
2579 searchOptions = new google.search.SearcherOptions();
2580 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2581
2582 // configure each of the searchers, for each tab
2583 devSiteSearcher = new google.search.WebSearch();
2584 devSiteSearcher.setUserDefinedLabel("All");
2585 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2586
2587 designSearcher = new google.search.WebSearch();
2588 designSearcher.setUserDefinedLabel("Design");
2589 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2590
2591 trainingSearcher = new google.search.WebSearch();
2592 trainingSearcher.setUserDefinedLabel("Training");
2593 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2594
2595 guidesSearcher = new google.search.WebSearch();
2596 guidesSearcher.setUserDefinedLabel("Guides");
2597 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2598
2599 referenceSearcher = new google.search.WebSearch();
2600 referenceSearcher.setUserDefinedLabel("Reference");
2601 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2602
Scott Maindf08ada2012-12-03 08:54:37 -08002603 googleSearcher = new google.search.WebSearch();
2604 googleSearcher.setUserDefinedLabel("Google Services");
2605 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2606
Scott Mainf5089842012-08-14 16:31:07 -07002607 blogSearcher = new google.search.WebSearch();
2608 blogSearcher.setUserDefinedLabel("Blog");
2609 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2610
2611 // add each searcher to the search control
2612 searchControl.addSearcher(devSiteSearcher, searchOptions);
2613 searchControl.addSearcher(designSearcher, searchOptions);
2614 searchControl.addSearcher(trainingSearcher, searchOptions);
2615 searchControl.addSearcher(guidesSearcher, searchOptions);
2616 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002617 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002618 searchControl.addSearcher(blogSearcher, searchOptions);
2619
2620 // configure result options
2621 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2622 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2623 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2624 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2625
2626 // upon ajax search, refresh the url and search title
2627 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2628 updateResultTitle(query);
2629 var query = document.getElementById('search_autocomplete').value;
2630 location.hash = 'q=' + query;
2631 });
2632
Scott Mainde295272013-03-25 15:48:35 -07002633 // once search results load, set up click listeners
2634 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2635 addResultClickListeners();
2636 });
2637
Scott Mainf5089842012-08-14 16:31:07 -07002638 // draw the search results box
2639 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2640
2641 // get query and execute the search
2642 searchControl.execute(decodeURI(getQuery(location.hash)));
2643
2644 document.getElementById("search_autocomplete").focus();
2645 addTabListeners();
2646}
2647// End of loadSearchResults
2648
2649
2650google.setOnLoadCallback(function(){
2651 if (location.hash.indexOf("q=") == -1) {
2652 // if there's no query in the url, don't search and make sure results are hidden
2653 $('#searchResults').hide();
2654 return;
2655 } else {
2656 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002657 $('#searchResults').slideDown('slow', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002658 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002659 loadSearchResults();
2660 }
2661}, true);
2662
smain@google.com9a818f52014-10-03 09:25:59 -07002663/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2664 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002665function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002666 // Ignore if there's no search bar (some special pages have no header)
2667 if ($("#search-container").length < 1) return;
2668
smain@google.com3b77ab52014-06-17 11:57:27 -07002669 var hash = escape(location.hash.substr(1));
2670 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002671 // Sanity check that there's an element with that ID on the page
2672 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002673 // If the position of the target element is near the top of the page (<20px, where we expect it
2674 // to be because we need to move it down 60px to become in view), then move it down 60px
2675 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2676 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002677 }
2678 }
2679}
2680
Scott Mainf5089842012-08-14 16:31:07 -07002681// when an event on the browser history occurs (back, forward, load) requery hash and do search
2682$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002683 // Ignore if there's no search bar (some special pages have no header)
2684 if ($("#search-container").length < 1) return;
2685
Dirk Doughertyc3921652014-05-13 16:55:26 -07002686 // If the hash isn't a search query or there's an error in the query,
2687 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002688 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2689 // If the results pane is open, close it.
2690 if (!$("#searchResults").is(":hidden")) {
2691 hideResults();
2692 }
Scott Mainb16376f2014-05-21 20:35:47 -07002693 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002694 return;
2695 }
2696
2697 // Otherwise, we have a search to do
2698 var query = decodeURI(getQuery(location.hash));
2699 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002700 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002701 $("#search_autocomplete").focus();
Dirk Dougherty29e93432015-05-05 18:17:13 -07002702 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002703
2704 updateResultTitle(query);
2705});
2706
2707function updateResultTitle(query) {
2708 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2709}
2710
2711// forcefully regain key-up event control (previously jacked by search api)
2712$("#search_autocomplete").keyup(function(event) {
2713 return search_changed(event, false, toRoot);
2714});
2715
2716// add event listeners to each tab so we can track the browser history
2717function addTabListeners() {
2718 var tabHeaders = $(".gsc-tabHeader");
2719 for (var i = 0; i < tabHeaders.length; i++) {
2720 $(tabHeaders[i]).attr("id",i).click(function() {
2721 /*
2722 // make a copy of the page numbers for the search left pane
2723 setTimeout(function() {
2724 // remove any residual page numbers
2725 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002726 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002727 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002728 // and because we're going to remove it (previous line),
2729 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002730 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2731 .clone().appendTo('#searchResults .gsc-tabsArea');
2732 }, 200);
2733 */
2734 });
2735 }
2736 setTimeout(function(){$(tabHeaders[0]).click()},200);
2737}
2738
Scott Mainde295272013-03-25 15:48:35 -07002739// add analytics tracking events to each result link
2740function addResultClickListeners() {
2741 $("#searchResults a.gs-title").each(function(index, link) {
2742 // When user clicks enter for Google search results, track it
2743 $(link).click(function() {
smain@google.comed677d72014-12-12 10:19:17 -08002744 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'),
2745 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07002746 });
2747 });
2748}
2749
Scott Mainf5089842012-08-14 16:31:07 -07002750
2751function getQuery(hash) {
2752 var queryParts = hash.split('=');
2753 return queryParts[1];
2754}
2755
2756/* returns the given string with all HTML brackets converted to entities
2757 TODO: move this to the site's JS library */
2758function escapeHTML(string) {
2759 return string.replace(/</g,"&lt;")
2760 .replace(/>/g,"&gt;");
2761}
2762
2763
2764
2765
2766
2767
2768
2769/* ######################################################## */
2770/* ################# JAVADOC REFERENCE ################### */
2771/* ######################################################## */
2772
Scott Main65511c02012-09-07 15:51:32 -07002773/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002774if (location.pathname.indexOf("/reference") == 0) {
2775 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2776 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2777 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002778 $(document).ready(function() {
2779 // init available apis based on user pref
2780 changeApiLevel();
2781 initSidenavHeightResize()
2782 });
2783 }
Scott Main65511c02012-09-07 15:51:32 -07002784}
Scott Mainf5089842012-08-14 16:31:07 -07002785
2786var API_LEVEL_COOKIE = "api_level";
2787var minLevel = 1;
2788var maxLevel = 1;
2789
2790/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002791
Scott Mainf5089842012-08-14 16:31:07 -07002792 function initSidenavHeightResize() {
2793 // Change the drag bar size to nicely fit the scrollbar positions
2794 var $dragBar = $(".ui-resizable-s");
2795 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002796
2797 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002798 containment: "#nav-panels",
2799 handles: "s",
2800 alsoResize: "#packages-nav",
2801 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2802 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2803 });
Scott Main3b90aff2013-08-01 18:09:35 -07002804
Scott Mainf5089842012-08-14 16:31:07 -07002805 }
Scott Main3b90aff2013-08-01 18:09:35 -07002806
Scott Mainf5089842012-08-14 16:31:07 -07002807function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002808 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002809 $('#devdoc-nav').css({
2810 'width' : $('#side-nav').css('width'),
2811 'margin' : $('#side-nav').css('margin')
2812 });
2813 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002814
Scott Mainf5089842012-08-14 16:31:07 -07002815 initSidenavHeightResize();
2816}
2817
2818function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002819 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002820 $('#devdoc-nav').css({
2821 'width' : $('#side-nav').css('width'),
2822 'margin' : $('#side-nav').css('margin')
2823 });
2824 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002825
Scott Mainf5089842012-08-14 16:31:07 -07002826 initSidenavHeightResize();
2827}
2828
2829function buildApiLevelSelector() {
2830 maxLevel = SINCE_DATA.length;
2831 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2832 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2833
2834 minLevel = parseInt($("#doc-api-level").attr("class"));
2835 // Handle provisional api levels; the provisional level will always be the highest possible level
2836 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2837 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2838 if (isNaN(minLevel) && minLevel.length) {
2839 minLevel = maxLevel;
2840 }
2841 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2842 for (var i = maxLevel-1; i >= 0; i--) {
2843 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2844 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2845 select.append(option);
2846 }
2847
2848 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2849 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2850 selectedLevelItem.setAttribute('selected',true);
2851}
2852
2853function changeApiLevel() {
2854 maxLevel = SINCE_DATA.length;
2855 var selectedLevel = maxLevel;
2856
2857 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2858 toggleVisisbleApis(selectedLevel, "body");
2859
smain@google.com6bdcb982014-11-14 11:53:07 -08002860 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002861
2862 if (selectedLevel < minLevel) {
2863 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002864 $("#naMessage").show().html("<div><p><strong>This " + thing
2865 + " requires API level " + minLevel + " or higher.</strong></p>"
2866 + "<p>This document is hidden because your selected API level for the documentation is "
2867 + selectedLevel + ". You can change the documentation API level with the selector "
2868 + "above the left navigation.</p>"
2869 + "<p>For more information about specifying the API level your app requires, "
2870 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2871 + ">Supporting Different Platform Versions</a>.</p>"
2872 + "<input type='button' value='OK, make this page visible' "
2873 + "title='Change the API level to " + minLevel + "' "
2874 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2875 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002876 } else {
2877 $("#naMessage").hide();
2878 }
2879}
2880
2881function toggleVisisbleApis(selectedLevel, context) {
2882 var apis = $(".api",context);
2883 apis.each(function(i) {
2884 var obj = $(this);
2885 var className = obj.attr("class");
2886 var apiLevelIndex = className.lastIndexOf("-")+1;
2887 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2888 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2889 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2890 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2891 return;
2892 }
2893 apiLevel = parseInt(apiLevel);
2894
2895 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2896 var selectedLevelNum = parseInt(selectedLevel)
2897 var apiLevelNum = parseInt(apiLevel);
2898 if (isNaN(apiLevelNum)) {
2899 apiLevelNum = maxLevel;
2900 }
2901
2902 // Grey things out that aren't available and give a tooltip title
2903 if (apiLevelNum > selectedLevelNum) {
2904 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002905 + apiLevel + "\" or higher. To reveal, change the target API level "
2906 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002907 }
Scott Mainf5089842012-08-14 16:31:07 -07002908 else obj.removeClass("absent").removeAttr("title");
2909 });
2910}
2911
2912
2913
2914
2915/* ################# SIDENAV TREE VIEW ################### */
2916
2917function new_node(me, mom, text, link, children_data, api_level)
2918{
2919 var node = new Object();
2920 node.children = Array();
2921 node.children_data = children_data;
2922 node.depth = mom.depth + 1;
2923
2924 node.li = document.createElement("li");
2925 mom.get_children_ul().appendChild(node.li);
2926
2927 node.label_div = document.createElement("div");
2928 node.label_div.className = "label";
2929 if (api_level != null) {
2930 $(node.label_div).addClass("api");
2931 $(node.label_div).addClass("api-level-"+api_level);
2932 }
2933 node.li.appendChild(node.label_div);
2934
2935 if (children_data != null) {
2936 node.expand_toggle = document.createElement("a");
2937 node.expand_toggle.href = "javascript:void(0)";
2938 node.expand_toggle.onclick = function() {
2939 if (node.expanded) {
2940 $(node.get_children_ul()).slideUp("fast");
2941 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2942 node.expanded = false;
2943 } else {
2944 expand_node(me, node);
2945 }
2946 };
2947 node.label_div.appendChild(node.expand_toggle);
2948
2949 node.plus_img = document.createElement("img");
2950 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2951 node.plus_img.className = "plus";
2952 node.plus_img.width = "8";
2953 node.plus_img.border = "0";
2954 node.expand_toggle.appendChild(node.plus_img);
2955
2956 node.expanded = false;
2957 }
2958
2959 var a = document.createElement("a");
2960 node.label_div.appendChild(a);
2961 node.label = document.createTextNode(text);
2962 a.appendChild(node.label);
2963 if (link) {
2964 a.href = me.toroot + link;
2965 } else {
2966 if (children_data != null) {
2967 a.className = "nolink";
2968 a.href = "javascript:void(0)";
2969 a.onclick = node.expand_toggle.onclick;
2970 // This next line shouldn't be necessary. I'll buy a beer for the first
2971 // person who figures out how to remove this line and have the link
2972 // toggle shut on the first try. --joeo@android.com
2973 node.expanded = false;
2974 }
2975 }
Scott Main3b90aff2013-08-01 18:09:35 -07002976
Scott Mainf5089842012-08-14 16:31:07 -07002977
2978 node.children_ul = null;
2979 node.get_children_ul = function() {
2980 if (!node.children_ul) {
2981 node.children_ul = document.createElement("ul");
2982 node.children_ul.className = "children_ul";
2983 node.children_ul.style.display = "none";
2984 node.li.appendChild(node.children_ul);
2985 }
2986 return node.children_ul;
2987 };
2988
2989 return node;
2990}
2991
Robert Lyd2dd6e52012-11-29 21:28:48 -08002992
2993
2994
Scott Mainf5089842012-08-14 16:31:07 -07002995function expand_node(me, node)
2996{
2997 if (node.children_data && !node.expanded) {
2998 if (node.children_visited) {
2999 $(node.get_children_ul()).slideDown("fast");
3000 } else {
3001 get_node(me, node);
3002 if ($(node.label_div).hasClass("absent")) {
3003 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07003004 }
Scott Mainf5089842012-08-14 16:31:07 -07003005 $(node.get_children_ul()).slideDown("fast");
3006 }
3007 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
3008 node.expanded = true;
3009
3010 // perform api level toggling because new nodes are new to the DOM
3011 var selectedLevel = $("#apiLevelSelector option:selected").val();
3012 toggleVisisbleApis(selectedLevel, "#side-nav");
3013 }
3014}
3015
3016function get_node(me, mom)
3017{
3018 mom.children_visited = true;
3019 for (var i in mom.children_data) {
3020 var node_data = mom.children_data[i];
3021 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
3022 node_data[2], node_data[3]);
3023 }
3024}
3025
3026function this_page_relative(toroot)
3027{
3028 var full = document.location.pathname;
3029 var file = "";
3030 if (toroot.substr(0, 1) == "/") {
3031 if (full.substr(0, toroot.length) == toroot) {
3032 return full.substr(toroot.length);
3033 } else {
3034 // the file isn't under toroot. Fail.
3035 return null;
3036 }
3037 } else {
3038 if (toroot != "./") {
3039 toroot = "./" + toroot;
3040 }
3041 do {
3042 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3043 var pos = full.lastIndexOf("/");
3044 file = full.substr(pos) + file;
3045 full = full.substr(0, pos);
3046 toroot = toroot.substr(0, toroot.length-3);
3047 }
3048 } while (toroot != "" && toroot != "/");
3049 return file.substr(1);
3050 }
3051}
3052
3053function find_page(url, data)
3054{
3055 var nodes = data;
3056 var result = null;
3057 for (var i in nodes) {
3058 var d = nodes[i];
3059 if (d[1] == url) {
3060 return new Array(i);
3061 }
3062 else if (d[2] != null) {
3063 result = find_page(url, d[2]);
3064 if (result != null) {
3065 return (new Array(i).concat(result));
3066 }
3067 }
3068 }
3069 return null;
3070}
3071
Scott Mainf5089842012-08-14 16:31:07 -07003072function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07003073 // load json file for navtree data
3074 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3075 // when the file is loaded, initialize the tree
3076 if(jqxhr.status === 200) {
3077 init_navtree("tree-list", toroot, NAVTREE_DATA);
3078 }
3079 });
Scott Main3b90aff2013-08-01 18:09:35 -07003080
Scott Mainf5089842012-08-14 16:31:07 -07003081 // perform api level toggling because because the whole tree is new to the DOM
3082 var selectedLevel = $("#apiLevelSelector option:selected").val();
3083 toggleVisisbleApis(selectedLevel, "#side-nav");
3084}
3085
3086function init_navtree(navtree_id, toroot, root_nodes)
3087{
3088 var me = new Object();
3089 me.toroot = toroot;
3090 me.node = new Object();
3091
3092 me.node.li = document.getElementById(navtree_id);
3093 me.node.children_data = root_nodes;
3094 me.node.children = new Array();
3095 me.node.children_ul = document.createElement("ul");
3096 me.node.get_children_ul = function() { return me.node.children_ul; };
3097 //me.node.children_ul.className = "children_ul";
3098 me.node.li.appendChild(me.node.children_ul);
3099 me.node.depth = 0;
3100
3101 get_node(me, me.node);
3102
3103 me.this_page = this_page_relative(toroot);
3104 me.breadcrumbs = find_page(me.this_page, root_nodes);
3105 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3106 var mom = me.node;
3107 for (var i in me.breadcrumbs) {
3108 var j = me.breadcrumbs[i];
3109 mom = mom.children[j];
3110 expand_node(me, mom);
3111 }
3112 mom.label_div.className = mom.label_div.className + " selected";
3113 addLoadEvent(function() {
3114 scrollIntoView("nav-tree");
3115 });
3116 }
3117}
3118
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003119
3120
3121
3122
3123
3124
3125
Robert Lyd2dd6e52012-11-29 21:28:48 -08003126/* TODO: eliminate redundancy with non-google functions */
3127function init_google_navtree(navtree_id, toroot, root_nodes)
3128{
3129 var me = new Object();
3130 me.toroot = toroot;
3131 me.node = new Object();
3132
3133 me.node.li = document.getElementById(navtree_id);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003134 if (!me.node.li) {
3135 return;
3136 }
3137
Robert Lyd2dd6e52012-11-29 21:28:48 -08003138 me.node.children_data = root_nodes;
3139 me.node.children = new Array();
3140 me.node.children_ul = document.createElement("ul");
3141 me.node.get_children_ul = function() { return me.node.children_ul; };
3142 //me.node.children_ul.className = "children_ul";
3143 me.node.li.appendChild(me.node.children_ul);
3144 me.node.depth = 0;
3145
3146 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003147}
3148
3149function new_google_node(me, mom, text, link, children_data, api_level)
3150{
3151 var node = new Object();
3152 var child;
3153 node.children = Array();
3154 node.children_data = children_data;
3155 node.depth = mom.depth + 1;
3156 node.get_children_ul = function() {
3157 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003158 node.children_ul = document.createElement("ul");
3159 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003160 node.li.appendChild(node.children_ul);
3161 }
3162 return node.children_ul;
3163 };
3164 node.li = document.createElement("li");
3165
3166 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003167
3168
Robert Lyd2dd6e52012-11-29 21:28:48 -08003169 if(link) {
3170 child = document.createElement("a");
3171
3172 }
3173 else {
3174 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003175 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003176
3177 }
3178 if (children_data != null) {
3179 node.li.className="nav-section";
3180 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003181 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003182 node.li.appendChild(node.label_div);
3183 get_google_node(me, node);
3184 node.label_div.appendChild(child);
3185 }
3186 else {
3187 node.li.appendChild(child);
3188 }
3189 if(link) {
3190 child.href = me.toroot + link;
3191 }
3192 node.label = document.createTextNode(text);
3193 child.appendChild(node.label);
3194
3195 node.children_ul = null;
3196
3197 return node;
3198}
3199
3200function get_google_node(me, mom)
3201{
3202 mom.children_visited = true;
3203 var linkText;
3204 for (var i in mom.children_data) {
3205 var node_data = mom.children_data[i];
3206 linkText = node_data[0];
3207
3208 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3209 linkText = linkText.substr(19, linkText.length);
3210 }
3211 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3212 node_data[2], node_data[3]);
3213 }
3214}
Scott Mainad08f072013-08-20 16:49:57 -07003215
3216
3217
3218
3219
3220
3221/****** NEW version of script to build google and sample navs dynamically ******/
3222// TODO: update Google reference docs to tolerate this new implementation
3223
Scott Maine624b3f2013-09-12 12:56:41 -07003224var NODE_NAME = 0;
3225var NODE_HREF = 1;
3226var NODE_GROUP = 2;
3227var NODE_TAGS = 3;
3228var NODE_CHILDREN = 4;
3229
Scott Mainad08f072013-08-20 16:49:57 -07003230function init_google_navtree2(navtree_id, data)
3231{
3232 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003233 for (var i in data) {
3234 var node_data = data[i];
3235 $containerUl.append(new_google_node2(node_data));
3236 }
3237
Scott Main70557ee2013-10-30 14:47:40 -07003238 // Make all third-generation list items 'sticky' to prevent them from collapsing
3239 $containerUl.find('li li li.nav-section').addClass('sticky');
3240
Scott Mainad08f072013-08-20 16:49:57 -07003241 initExpandableNavItems("#"+navtree_id);
3242}
3243
3244function new_google_node2(node_data)
3245{
Scott Maine624b3f2013-09-12 12:56:41 -07003246 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003247 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3248 linkText = linkText.substr(19, linkText.length);
3249 }
3250 var $li = $('<li>');
3251 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003252 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003253 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3254 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003255 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003256 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3257 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003258 }
3259 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003260 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003261 $li.addClass("nav-section");
3262 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003263 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003264
Scott Maine624b3f2013-09-12 12:56:41 -07003265 for (var i in node_data[NODE_CHILDREN]) {
3266 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003267 $childUl.append(new_google_node2(child_node_data));
3268 }
3269 $li.append($childUl);
3270 }
3271 $li.prepend($a);
3272
3273 return $li;
3274}
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
Robert Lyd2dd6e52012-11-29 21:28:48 -08003286function showGoogleRefTree() {
3287 init_default_google_navtree(toRoot);
3288 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003289}
3290
3291function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003292 // load json file for navtree data
3293 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3294 // when the file is loaded, initialize the tree
3295 if(jqxhr.status === 200) {
3296 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3297 highlightSidenav();
3298 resizeNav();
3299 }
3300 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003301}
3302
3303function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003304 // load json file for navtree data
3305 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3306 // when the file is loaded, initialize the tree
3307 if(jqxhr.status === 200) {
3308 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3309 highlightSidenav();
3310 resizeNav();
3311 }
3312 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003313}
3314
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003315function showSamplesRefTree() {
3316 init_default_samples_navtree(toRoot);
3317}
3318
3319function init_default_samples_navtree(toroot) {
3320 // load json file for navtree data
3321 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3322 // when the file is loaded, initialize the tree
3323 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003324 // hack to remove the "about the samples" link then put it back in
3325 // after we nuke the list to remove the dummy static list of samples
3326 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3327 $("#nav.samples-nav").empty();
3328 $("#nav.samples-nav").append($firstLi);
3329
Scott Mainad08f072013-08-20 16:49:57 -07003330 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003331 highlightSidenav();
3332 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003333 if ($("#jd-content #samples").length) {
3334 showSamples();
3335 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003336 }
3337 });
3338}
3339
Scott Mainf5089842012-08-14 16:31:07 -07003340/* TOGGLE INHERITED MEMBERS */
3341
3342/* Toggle an inherited class (arrow toggle)
3343 * @param linkObj The link that was clicked.
3344 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3345 * 'null' to simply toggle.
3346 */
3347function toggleInherited(linkObj, expand) {
3348 var base = linkObj.getAttribute("id");
3349 var list = document.getElementById(base + "-list");
3350 var summary = document.getElementById(base + "-summary");
3351 var trigger = document.getElementById(base + "-trigger");
3352 var a = $(linkObj);
3353 if ( (expand == null && a.hasClass("closed")) || expand ) {
3354 list.style.display = "none";
3355 summary.style.display = "block";
3356 trigger.src = toRoot + "assets/images/triangle-opened.png";
3357 a.removeClass("closed");
3358 a.addClass("opened");
3359 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3360 list.style.display = "block";
3361 summary.style.display = "none";
3362 trigger.src = toRoot + "assets/images/triangle-closed.png";
3363 a.removeClass("opened");
3364 a.addClass("closed");
3365 }
3366 return false;
3367}
3368
3369/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3370 * @param linkObj The link that was clicked.
3371 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3372 * 'null' to simply toggle.
3373 */
3374function toggleAllInherited(linkObj, expand) {
3375 var a = $(linkObj);
3376 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3377 var expandos = $(".jd-expando-trigger", table);
3378 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3379 expandos.each(function(i) {
3380 toggleInherited(this, true);
3381 });
3382 a.text("[Collapse]");
3383 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3384 expandos.each(function(i) {
3385 toggleInherited(this, false);
3386 });
3387 a.text("[Expand]");
3388 }
3389 return false;
3390}
3391
3392/* Toggle all inherited members in the class (link in the class title)
3393 */
3394function toggleAllClassInherited() {
3395 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3396 var toggles = $(".toggle-all", $("#body-content"));
3397 if (a.text() == "[Expand All]") {
3398 toggles.each(function(i) {
3399 toggleAllInherited(this, true);
3400 });
3401 a.text("[Collapse All]");
3402 } else {
3403 toggles.each(function(i) {
3404 toggleAllInherited(this, false);
3405 });
3406 a.text("[Expand All]");
3407 }
3408 return false;
3409}
3410
3411/* Expand all inherited members in the class. Used when initiating page search */
3412function ensureAllInheritedExpanded() {
3413 var toggles = $(".toggle-all", $("#body-content"));
3414 toggles.each(function(i) {
3415 toggleAllInherited(this, true);
3416 });
3417 $("#toggleAllClassInherited").text("[Collapse All]");
3418}
3419
3420
3421/* HANDLE KEY EVENTS
3422 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3423 */
3424var agent = navigator['userAgent'].toLowerCase();
3425var mac = agent.indexOf("macintosh") != -1;
3426
3427$(document).keydown( function(e) {
3428var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3429 if (control && e.which == 70) { // 70 is "F"
3430 ensureAllInheritedExpanded();
3431 }
3432});
Scott Main498d7102013-08-21 15:47:38 -07003433
3434
3435
3436
3437
3438
3439/* On-demand functions */
3440
3441/** Move sample code line numbers out of PRE block and into non-copyable column */
3442function initCodeLineNumbers() {
3443 var numbers = $("#codesample-block a.number");
3444 if (numbers.length) {
3445 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3446 }
3447
3448 $(document).ready(function() {
3449 // select entire line when clicked
3450 $("span.code-line").click(function() {
3451 if (!shifted) {
3452 selectText(this);
3453 }
3454 });
3455 // invoke line link on double click
3456 $(".code-line").dblclick(function() {
3457 document.location.hash = $(this).attr('id');
3458 });
3459 // highlight the line when hovering on the number
3460 $("#codesample-line-numbers a.number").mouseover(function() {
3461 var id = $(this).attr('href');
3462 $(id).css('background','#e7e7e7');
3463 });
3464 $("#codesample-line-numbers a.number").mouseout(function() {
3465 var id = $(this).attr('href');
3466 $(id).css('background','none');
3467 });
3468 });
3469}
3470
3471// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3472var shifted = false;
3473$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3474
3475// courtesy of jasonedelman.com
3476function selectText(element) {
3477 var doc = document
3478 , range, selection
3479 ;
3480 if (doc.body.createTextRange) { //ms
3481 range = doc.body.createTextRange();
3482 range.moveToElementText(element);
3483 range.select();
3484 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003485 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003486 range = doc.createRange();
3487 range.selectNodeContents(element);
3488 selection.removeAllRanges();
3489 selection.addRange(range);
3490 }
Scott Main285f0772013-08-22 23:22:09 +00003491}
Scott Main03aca9a2013-10-31 07:20:55 -07003492
3493
3494
3495
3496/** Display links and other information about samples that match the
3497 group specified by the URL */
3498function showSamples() {
3499 var group = $("#samples").attr('class');
3500 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3501
3502 var $ul = $("<ul>");
3503 $selectedLi = $("#nav li.selected");
3504
3505 $selectedLi.children("ul").children("li").each(function() {
3506 var $li = $("<li>").append($(this).find("a").first().clone());
3507 $ul.append($li);
3508 });
3509
3510 $("#samples").append($ul);
3511
3512}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003513
3514
3515
3516/* ########################################################## */
3517/* ################### RESOURCE CARDS ##################### */
3518/* ########################################################## */
3519
3520/** Handle resource queries, collections, and grids (sections). Requires
3521 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3522
3523(function() {
3524 // Prevent the same resource from being loaded more than once per page.
3525 var addedPageResources = {};
3526
3527 $(document).ready(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003528 // Need to initialize hero carousel before other sections for dedupe
3529 // to work correctly.
3530 $('[data-carousel-query]').dacCarouselQuery();
3531
Dirk Doughertyc3921652014-05-13 16:55:26 -07003532 $('.resource-widget').each(function() {
3533 initResourceWidget(this);
3534 });
3535
3536 /* Pass the line height to ellipsisfade() to adjust the height of the
3537 text container to show the max number of lines possible, without
3538 showing lines that are cut off. This works with the css ellipsis
3539 classes to fade last text line and apply an ellipsis char. */
3540
Dirk Dougherty29e93432015-05-05 18:17:13 -07003541 //card text currently uses 20px line height.
3542 var lineHeight = 20;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003543 $('.card-info .text').ellipsisfade(lineHeight);
3544 });
3545
3546 /*
3547 Three types of resource layouts:
3548 Flow - Uses a fixed row-height flow using float left style.
3549 Carousel - Single card slideshow all same dimension absolute.
3550 Stack - Uses fixed columns and flexible element height.
3551 */
3552 function initResourceWidget(widget) {
3553 var $widget = $(widget);
3554 var isFlow = $widget.hasClass('resource-flow-layout'),
3555 isCarousel = $widget.hasClass('resource-carousel-layout'),
3556 isStack = $widget.hasClass('resource-stack-layout');
3557
Dirk Dougherty29e93432015-05-05 18:17:13 -07003558 // remove illegal col-x class which is not relevant anymore thanks to responsive styles.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003559 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
Dirk Dougherty29e93432015-05-05 18:17:13 -07003560 if (m && !$widget.is('.cols > *')) {
3561 $widget.removeClass('col-' + m[1]);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003562 }
3563
3564 var opts = {
3565 cardSizes: ($widget.data('cardsizes') || '').split(','),
3566 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3567 itemsPerPage: $widget.data('itemsperpage'),
3568 sortOrder: $widget.data('sortorder'),
3569 query: $widget.data('query'),
3570 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003571 /* Added by LFL 6/6/14 */
3572 resourceStyle: $widget.data('resourcestyle') || 'card',
3573 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003574 };
3575
3576 // run the search for the set of resources to show
3577
3578 var resources = buildResourceList(opts);
3579
3580 if (isFlow) {
3581 drawResourcesFlowWidget($widget, opts, resources);
3582 } else if (isCarousel) {
3583 drawResourcesCarouselWidget($widget, opts, resources);
3584 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003585 /* Looks like this got removed and is not used, so repurposing for the
3586 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003587 Modified by LFL 6/6/14
3588 */
3589 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003590 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003591 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003592 }
3593 }
3594
3595 /* Initializes a Resource Carousel Widget */
3596 function drawResourcesCarouselWidget($widget, opts, resources) {
3597 $widget.empty();
Dirk Dougherty29e93432015-05-05 18:17:13 -07003598 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003599
3600 $widget.addClass('resource-card slideshow-container')
3601 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3602 .append($('<a>').addClass('slideshow-next').text('Next'));
3603
3604 var css = { 'width': $widget.width() + 'px',
3605 'height': $widget.height() + 'px' };
3606
3607 var $ul = $('<ul>');
3608
3609 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003610 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003611 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003612 .decorateResourceCard(resources[i],plusone);
3613
3614 $('<li>').css(css)
3615 .append($card)
3616 .appendTo($ul);
3617 }
3618
3619 $('<div>').addClass('frame')
3620 .append($ul)
3621 .appendTo($widget);
3622
3623 $widget.dacSlideshow({
3624 auto: true,
3625 btnPrev: '.slideshow-prev',
3626 btnNext: '.slideshow-next'
3627 });
3628 };
3629
Robert Lye7eeb402014-06-03 19:35:24 -07003630 /* Initializes a Resource Card Stack Widget (column-based layout)
3631 Modified by LFL 6/6/14
3632 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003633 function drawResourcesStackWidget($widget, opts, resources, sections) {
3634 // Don't empty widget, grab all items inside since they will be the first
3635 // items stacked, followed by the resource query
Dirk Dougherty29e93432015-05-05 18:17:13 -07003636 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003637 var cards = $widget.find('.resource-card').detach().toArray();
3638 var numStacks = opts.numStacks || 1;
3639 var $stacks = [];
3640 var urlString;
3641
3642 for (var i = 0; i < numStacks; ++i) {
3643 $stacks[i] = $('<div>').addClass('resource-card-stack')
3644 .appendTo($widget);
3645 }
3646
3647 var sectionResources = [];
3648
3649 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003650 if (sections) {
3651 for (var i = 0; i < sections.length; ++i) {
3652 if (!sections[i].sections || !sections[i].sections.length) {
3653 // Render it as a resource card
3654 sectionResources.push(
3655 $('<a>')
3656 .addClass('resource-card section-card')
3657 .attr('href', cleanUrl(sections[i].resource.url))
3658 .decorateResourceCard(sections[i].resource,plusone)[0]
3659 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003660
Robert Lye7eeb402014-06-03 19:35:24 -07003661 } else {
3662 cards.push(
3663 $('<div>')
3664 .addClass('resource-card section-card-menu')
3665 .decorateResourceSection(sections[i],plusone)[0]
3666 );
3667 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003668 }
3669 }
3670
3671 cards = cards.concat(sectionResources);
3672
3673 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003674 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003675
Robert Lye7eeb402014-06-03 19:35:24 -07003676 if (opts.resourceStyle.indexOf('related') > -1) {
3677 $card.addClass('related-card');
3678 }
smain@google.com95948b82014-06-16 19:24:25 -07003679
Dirk Doughertyc3921652014-05-13 16:55:26 -07003680 cards.push($card[0]);
3681 }
3682
Robert Lye7eeb402014-06-03 19:35:24 -07003683 if (opts.stackSort != 'false') {
3684 for (var i = 0; i < cards.length; ++i) {
3685 // Find the stack with the shortest height, but give preference to
3686 // left to right order.
3687 var minHeight = $stacks[0].height();
3688 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003689
Robert Lye7eeb402014-06-03 19:35:24 -07003690 for (var j = 1; j < numStacks; ++j) {
3691 var height = $stacks[j].height();
3692 if (height < minHeight - 45) {
3693 minHeight = height;
3694 minIndex = j;
3695 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003696 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003697
Robert Lye7eeb402014-06-03 19:35:24 -07003698 $stacks[minIndex].append($(cards[i]));
3699 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003700 }
3701
3702 };
smain@google.com95948b82014-06-16 19:24:25 -07003703
3704 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003705 Create a resource card using the given resource object and a list of html
3706 configured options. Returns a jquery object containing the element.
3707 */
smain@google.com95948b82014-06-16 19:24:25 -07003708 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003709 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003710
Robert Lye7eeb402014-06-03 19:35:24 -07003711 // The difference here is that generic cards are not entirely clickable
3712 // so its a div instead of an a tag, also the generic one is not given
3713 // the resource-card class so it appears with a transparent background
3714 // and can be styled in whatever way the css setup.
3715 if (opts.resourceStyle == 'generic') {
3716 $el = $('<div>')
3717 .addClass('resource')
3718 .attr('href', cleanUrl(resource.url))
3719 .decorateResource(resource, opts);
3720 } else {
3721 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003722
Robert Lye7eeb402014-06-03 19:35:24 -07003723 $el = $('<a>')
3724 .addClass(cls)
3725 .attr('href', cleanUrl(resource.url))
3726 .decorateResourceCard(resource, plusone);
3727 }
smain@google.com95948b82014-06-16 19:24:25 -07003728
Robert Lye7eeb402014-06-03 19:35:24 -07003729 return $el;
3730 }
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003731
Dirk Dougherty29e93432015-05-05 18:17:13 -07003732 function createResponsiveFlowColumn(cardSize) {
3733 var cardWidth = parseInt(cardSize.match(/(\d+)/)[1], 10);
3734 var column = $('<div>').addClass('col-' + (cardWidth / 3) + 'of6');
3735 if (cardWidth < 9) {
3736 column.addClass('col-tablet-1of2');
3737 } else if (cardWidth > 9 && cardWidth < 18) {
3738 column.addClass('col-tablet-1of1');
3739 }
3740 if (cardWidth < 18) {
3741 column.addClass('col-mobile-1of1')
3742 }
3743 return column;
3744 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003745
3746 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3747 function drawResourcesFlowWidget($widget, opts, resources) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003748 $widget.empty().addClass('cols');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003749 var cardSizes = opts.cardSizes || ['6x6'];
3750 var i = 0, j = 0;
Dirk Dougherty29e93432015-05-05 18:17:13 -07003751 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003752
3753 while (i < resources.length) {
3754 var cardSize = cardSizes[j++ % cardSizes.length];
3755 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003756
Dirk Dougherty29e93432015-05-05 18:17:13 -07003757 var column = createResponsiveFlowColumn(cardSize).appendTo($widget);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003758
3759 // A stack has a third dimension which is the number of stacked items
3760 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3761 var stackCount = 0;
3762 var $stackDiv = null;
3763
3764 if (isStack) {
3765 // Create a stack container which should have the dimensions defined
3766 // by the product of the items inside.
3767 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
Dirk Dougherty29e93432015-05-05 18:17:13 -07003768 + 'x' + isStack[2] * isStack[3]) .appendTo(column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003769 }
3770
3771 // Build each stack item or just a single item
3772 do {
3773 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003774
Robert Lye7eeb402014-06-03 19:35:24 -07003775 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003776
3777 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003778 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003779
Dirk Doughertyc3921652014-05-13 16:55:26 -07003780 if (isStack) {
3781 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3782 if (++stackCount == parseInt(isStack[3])) {
3783 $card.addClass('resource-card-row-stack-last');
3784 stackCount = 0;
3785 }
3786 } else {
3787 stackCount = 0;
3788 }
3789
Dirk Dougherty29e93432015-05-05 18:17:13 -07003790 $card.appendTo($stackDiv || column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003791
3792 } while (++i < resources.length && stackCount > 0);
3793 }
3794 }
3795
3796 /* Build a site map of resources using a section as a root. */
3797 function buildSectionList(opts) {
3798 if (opts.section && SECTION_BY_ID[opts.section]) {
3799 return SECTION_BY_ID[opts.section].sections || [];
3800 }
3801 return [];
3802 }
3803
3804 function buildResourceList(opts) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003805 return $.queryResources(opts);
3806 }
3807
3808 $.queryResources = function(opts) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003809 var maxResults = opts.maxResults || 100;
3810
3811 var query = opts.query || '';
3812 var expressions = parseResourceQuery(query);
3813 var addedResourceIndices = {};
3814 var results = [];
3815
3816 for (var i = 0; i < expressions.length; i++) {
3817 var clauses = expressions[i];
3818
3819 // build initial set of resources from first clause
3820 var firstClause = clauses[0];
3821 var resources = [];
3822 switch (firstClause.attr) {
3823 case 'type':
3824 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3825 break;
3826 case 'lang':
3827 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3828 break;
3829 case 'tag':
3830 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3831 break;
3832 case 'collection':
3833 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3834 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3835 break;
3836 case 'section':
3837 var urls = SITE_MAP[firstClause.value].sections || [];
3838 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3839 break;
3840 }
3841 // console.log(firstClause.attr + ':' + firstClause.value);
3842 resources = resources || [];
3843
3844 // use additional clauses to filter corpus
3845 if (clauses.length > 1) {
3846 var otherClauses = clauses.slice(1);
3847 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3848 }
3849
3850 // filter out resources already added
3851 if (i > 1) {
3852 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3853 }
3854
3855 // add to list of already added indices
3856 for (var j = 0; j < resources.length; j++) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003857 if (resources[j]) {
3858 addedResourceIndices[resources[j].index] = 1;
3859 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003860 }
3861
3862 // concat to final results list
3863 results = results.concat(resources);
3864 }
3865
3866 if (opts.sortOrder && results.length) {
3867 var attr = opts.sortOrder;
3868
3869 if (opts.sortOrder == 'random') {
3870 var i = results.length, j, temp;
3871 while (--i) {
3872 j = Math.floor(Math.random() * (i + 1));
3873 temp = results[i];
3874 results[i] = results[j];
3875 results[j] = temp;
3876 }
3877 } else {
3878 var desc = attr.charAt(0) == '-';
3879 if (desc) {
3880 attr = attr.substring(1);
3881 }
3882 results = results.sort(function(x,y) {
3883 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3884 });
3885 }
3886 }
3887
3888 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3889 results = results.slice(0, maxResults);
3890
3891 for (var j = 0; j < results.length; ++j) {
3892 addedPageResources[results[j].index] = 1;
3893 }
3894
3895 return results;
3896 }
3897
3898
3899 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3900 return function(resource) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003901 return resource && !addedResourceIndices[resource.index];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003902 };
3903 }
3904
3905
3906 function getResourceMatchesClausesFilter(clauses) {
3907 return function(resource) {
3908 return doesResourceMatchClauses(resource, clauses);
3909 };
3910 }
3911
3912
3913 function doesResourceMatchClauses(resource, clauses) {
3914 for (var i = 0; i < clauses.length; i++) {
3915 var map;
3916 switch (clauses[i].attr) {
3917 case 'type':
3918 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3919 break;
3920 case 'lang':
3921 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3922 break;
3923 case 'tag':
3924 map = IS_RESOURCE_TAGGED[clauses[i].value];
3925 break;
3926 }
3927
3928 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3929 return clauses[i].negative;
3930 }
3931 }
3932 return true;
3933 }
smain@google.com95948b82014-06-16 19:24:25 -07003934
Robert Lye7eeb402014-06-03 19:35:24 -07003935 function cleanUrl(url)
3936 {
3937 if (url && url.indexOf('//') === -1) {
3938 url = toRoot + url;
3939 }
smain@google.com95948b82014-06-16 19:24:25 -07003940
Robert Lye7eeb402014-06-03 19:35:24 -07003941 return url;
3942 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003943
3944
3945 function parseResourceQuery(query) {
3946 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3947 var expressions = [];
3948 var expressionStrs = query.split(',') || [];
3949 for (var i = 0; i < expressionStrs.length; i++) {
3950 var expr = expressionStrs[i] || '';
3951
3952 // Break expression into clauses (clause e.g. 'tag:foo')
3953 var clauses = [];
3954 var clauseStrs = expr.split(/(?=[\+\-])/);
3955 for (var j = 0; j < clauseStrs.length; j++) {
3956 var clauseStr = clauseStrs[j] || '';
3957
3958 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3959 var parts = clauseStr.split(':');
3960 var clause = {};
3961
3962 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3963 if (clause.attr) {
3964 if (clause.attr.charAt(0) == '+') {
3965 clause.attr = clause.attr.substring(1);
3966 } else if (clause.attr.charAt(0) == '-') {
3967 clause.negative = true;
3968 clause.attr = clause.attr.substring(1);
3969 }
3970 }
3971
3972 if (parts.length > 1) {
3973 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3974 }
3975
3976 clauses.push(clause);
3977 }
3978
3979 if (!clauses.length) {
3980 continue;
3981 }
3982
3983 expressions.push(clauses);
3984 }
3985
3986 return expressions;
3987 }
3988})();
3989
3990(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07003991
smain@google.com95948b82014-06-16 19:24:25 -07003992 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003993 Utility method for creating dom for the description area of a card.
3994 Used in decorateResourceCard and decorateResource.
3995 */
3996 function buildResourceCardDescription(resource, plusone) {
3997 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07003998
Robert Lye7eeb402014-06-03 19:35:24 -07003999 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07004000
Robert Lye7eeb402014-06-03 19:35:24 -07004001 if (resource.cta) {
4002 $description.append($('<a>').addClass('cta').html(resource.cta));
4003 }
smain@google.com95948b82014-06-16 19:24:25 -07004004
Robert Lye7eeb402014-06-03 19:35:24 -07004005 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07004006 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07004007 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004008
Robert Lye7eeb402014-06-03 19:35:24 -07004009 $description.append($('<div>').addClass('util')
4010 .append($('<div>').addClass('g-plusone')
4011 .attr('data-size', 'small')
4012 .attr('data-align', 'right')
4013 .attr('data-href', plusurl)));
4014 }
smain@google.com95948b82014-06-16 19:24:25 -07004015
Robert Lye7eeb402014-06-03 19:35:24 -07004016 return $description;
4017 }
smain@google.com95948b82014-06-16 19:24:25 -07004018
4019
Dirk Doughertyc3921652014-05-13 16:55:26 -07004020 /* Simple jquery function to create dom for a standard resource card */
4021 $.fn.decorateResourceCard = function(resource,plusone) {
4022 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07004023 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004024 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07004025
Robert Lye7eeb402014-06-03 19:35:24 -07004026 if (imgUrl.indexOf('//') === -1) {
4027 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07004028 }
Robert Lye7eeb402014-06-03 19:35:24 -07004029
4030 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07004031 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07004032 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07004033 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07004034
Robert Lye7eeb402014-06-03 19:35:24 -07004035 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
4036 .append($('<div>').addClass('section').text(section))
4037 .append($('<div>').addClass('title').html(resource.title))
4038 .append(buildResourceCardDescription(resource, plusone))
4039 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07004040
4041 return this;
4042 };
4043
4044 /* Simple jquery function to create dom for a resource section card (menu) */
4045 $.fn.decorateResourceSection = function(section,plusone) {
4046 var resource = section.resource;
4047 //keep url clean for matching and offline mode handling
4048 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
4049 var $base = $('<a>')
4050 .addClass('card-bg')
4051 .attr('href', resource.url)
4052 .append($('<div>').addClass('card-section-icon')
4053 .append($('<div>').addClass('icon'))
4054 .append($('<div>').addClass('section').html(resource.title)))
4055 .appendTo(this);
4056
4057 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
4058
4059 if (section.sections && section.sections.length) {
4060 // Recurse the section sub-tree to find a resource image.
4061 var stack = [section];
4062
4063 while (stack.length) {
4064 if (stack[0].resource.image) {
4065 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4066 break;
4067 }
4068
4069 if (stack[0].sections) {
4070 stack = stack.concat(stack[0].sections);
4071 }
4072
4073 stack.shift();
4074 }
4075
4076 var $ul = $('<ul>')
4077 .appendTo($cardInfo);
4078
4079 var max = section.sections.length > 3 ? 3 : section.sections.length;
4080
4081 for (var i = 0; i < max; ++i) {
4082
4083 var subResource = section.sections[i];
4084 if (!plusone) {
4085 $('<li>')
4086 .append($('<a>').attr('href', subResource.url)
4087 .append($('<div>').addClass('title').html(subResource.title))
4088 .append($('<div>').addClass('description ellipsis')
4089 .append($('<div>').addClass('text').html(subResource.summary))
4090 .append($('<div>').addClass('util'))))
4091 .appendTo($ul);
4092 } else {
4093 $('<li>')
4094 .append($('<a>').attr('href', subResource.url)
4095 .append($('<div>').addClass('title').html(subResource.title))
4096 .append($('<div>').addClass('description ellipsis')
4097 .append($('<div>').addClass('text').html(subResource.summary))
4098 .append($('<div>').addClass('util')
4099 .append($('<div>').addClass('g-plusone')
4100 .attr('data-size', 'small')
4101 .attr('data-align', 'right')
4102 .attr('data-href', resource.url)))))
4103 .appendTo($ul);
4104 }
4105 }
4106
4107 // Add a more row
4108 if (max < section.sections.length) {
4109 $('<li>')
4110 .append($('<a>').attr('href', resource.url)
4111 .append($('<div>')
4112 .addClass('title')
4113 .text('More')))
4114 .appendTo($ul);
4115 }
4116 } else {
4117 // No sub-resources, just render description?
4118 }
4119
4120 return this;
4121 };
smain@google.com95948b82014-06-16 19:24:25 -07004122
4123
4124
4125
Robert Lye7eeb402014-06-03 19:35:24 -07004126 /* Render other types of resource styles that are not cards. */
4127 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07004128 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004129 'assets/images/resource-card-default-android.jpg';
4130 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004131
Robert Lye7eeb402014-06-03 19:35:24 -07004132 if (imgUrl.indexOf('//') === -1) {
4133 imgUrl = toRoot + imgUrl;
4134 }
smain@google.com95948b82014-06-16 19:24:25 -07004135
Robert Lye7eeb402014-06-03 19:35:24 -07004136 if (linkUrl && linkUrl.indexOf('//') === -1) {
4137 linkUrl = toRoot + linkUrl;
4138 }
4139
4140 $(this).append(
4141 $('<div>').addClass('image')
4142 .css('background-image', 'url(' + imgUrl + ')'),
4143 $('<div>').addClass('info').append(
4144 $('<h4>').addClass('title').html(resource.title),
4145 $('<p>').addClass('summary').html(resource.summary),
4146 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4147 )
4148 );
4149
4150 return this;
4151 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004152})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004153
4154
Dirk Doughertyc3921652014-05-13 16:55:26 -07004155/* Calculate the vertical area remaining */
4156(function($) {
4157 $.fn.ellipsisfade= function(lineHeight) {
4158 this.each(function() {
4159 // get element text
4160 var $this = $(this);
4161 var remainingHeight = $this.parent().parent().height();
4162 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004163 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004164 if ($(this).is(":visible")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004165 var h = $(this).outerHeight(true);
smain@google.comcda1a9a2014-06-19 17:07:46 -07004166 remainingHeight = remainingHeight - h;
4167 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004168 });
4169
4170 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4171 $this.parent().css({'height': adjustedRemainingHeight});
4172 $this.css({'height': "auto"});
4173 });
4174
4175 return this;
4176 };
4177}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004178
4179/*
4180 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004181
Robert Lye7eeb402014-06-03 19:35:24 -07004182 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004183 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004184 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004185
Robert Lye7eeb402014-06-03 19:35:24 -07004186 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004187
Robert Lye7eeb402014-06-03 19:35:24 -07004188 <div class="fullscreen-carousel">
4189 <div class="fullscreen-carousel-content">
4190 <!-- content here -->
4191 </div>
4192 <div class="fullscreen-carousel-content">
4193 <!-- content here -->
4194 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004195
Robert Lye7eeb402014-06-03 19:35:24 -07004196 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004197
Robert Lye7eeb402014-06-03 19:35:24 -07004198 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004199
Robert Lye7eeb402014-06-03 19:35:24 -07004200 Control over how the carousel takes over the screen can mostly be defined in
4201 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004202 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004203 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004204 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004205 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004206
Robert Lye7eeb402014-06-03 19:35:24 -07004207 There is limited functionality for having multiple sections since that request
4208 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4209 scroll between multiple content areas.
4210*/
4211
4212(function() {
4213 $(document).ready(function() {
4214 $('.fullscreen-carousel').each(function() {
4215 initWidget(this);
4216 });
4217 });
4218
4219 function initWidget(widget) {
4220 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004221
Robert Lye7eeb402014-06-03 19:35:24 -07004222 var topOffset = $widget.offset().top;
4223 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4224 var maxHeight = 0;
4225 var minHeight = 0;
4226 var $content = $widget.find('.fullscreen-carousel-content');
4227 var $nextArrow = $widget.find('.next-arrow');
4228 var $prevArrow = $widget.find('.prev-arrow');
4229 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004230
Robert Lye7eeb402014-06-03 19:35:24 -07004231 if ($content.length <= 1) {
4232 $nextArrow.hide();
4233 $prevArrow.hide();
4234 } else {
4235 $nextArrow.click(function() {
4236 var index = ($content.index($curSection) + 1);
4237 $curSection.hide();
4238 $curSection = $($content[index >= $content.length ? 0 : index]);
4239 $curSection.show();
4240 });
smain@google.com95948b82014-06-16 19:24:25 -07004241
Robert Lye7eeb402014-06-03 19:35:24 -07004242 $prevArrow.click(function() {
4243 var index = ($content.index($curSection) - 1);
4244 $curSection.hide();
4245 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4246 $curSection.show();
4247 });
4248 }
4249
4250 // Just hide all content sections except first.
4251 $content.each(function(index) {
4252 if ($(this).height() > minHeight) minHeight = $(this).height();
4253 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4254 });
4255
4256 // Register for changes to window size, and trigger.
4257 $(window).resize(resizeWidget);
4258 resizeWidget();
4259
4260 function resizeWidget() {
4261 var height = $(window).height() - topOffset - padBottom;
4262 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004263 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004264 (maxHeight && height > maxHeight ? maxHeight : height));
4265 }
smain@google.com95948b82014-06-16 19:24:25 -07004266 }
Robert Lye7eeb402014-06-03 19:35:24 -07004267})();
4268
4269
4270
4271
4272
4273/*
4274 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004275
Robert Lye7eeb402014-06-03 19:35:24 -07004276 The following allows tab widgets to be installed via the html below. Each
4277 tab content section should have a data-tab attribute matching one of the
4278 nav items'. Also each tab content section should have a width matching the
4279 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004280
Robert Lye7eeb402014-06-03 19:35:24 -07004281 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004282
Robert Lye7eeb402014-06-03 19:35:24 -07004283 <div class="tab-carousel">
4284 <ul class="tab-nav">
4285 <li><a href="#" data-tab="handsets">Handsets</a>
4286 <li><a href="#" data-tab="wearable">Wearable</a>
4287 <li><a href="#" data-tab="tv">TV</a>
4288 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004289
Robert Lye7eeb402014-06-03 19:35:24 -07004290 <div class="tab-carousel-content">
4291 <div data-tab="handsets">
4292 <!--Full width content here-->
4293 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004294
Robert Lye7eeb402014-06-03 19:35:24 -07004295 <div data-tab="wearable">
4296 <!--Full width content here-->
4297 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004298
Robert Lye7eeb402014-06-03 19:35:24 -07004299 <div data-tab="tv">
4300 <!--Full width content here-->
4301 </div>
4302 </div>
4303 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004304
Robert Lye7eeb402014-06-03 19:35:24 -07004305*/
4306(function() {
4307 $(document).ready(function() {
4308 $('.tab-carousel').each(function() {
4309 initWidget(this);
4310 });
4311 });
4312
4313 function initWidget(widget) {
4314 var $widget = $(widget);
4315 var $nav = $widget.find('.tab-nav');
4316 var $anchors = $nav.find('[data-tab]');
4317 var $li = $nav.find('li');
4318 var $contentContainer = $widget.find('.tab-carousel-content');
4319 var $tabs = $contentContainer.find('[data-tab]');
4320 var $curTab = $($tabs[0]); // Current tab is first tab.
4321 var width = $widget.width();
4322
4323 // Setup nav interactivity.
4324 $anchors.click(function(evt) {
4325 evt.preventDefault();
4326 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004327 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004328 });
smain@google.com95948b82014-06-16 19:24:25 -07004329
Robert Lye7eeb402014-06-03 19:35:24 -07004330 // Add highlight for navigation on first item.
4331 var $highlight = $('<div>').addClass('highlight')
4332 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4333 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004334
Robert Lye7eeb402014-06-03 19:35:24 -07004335 // Store height since we will change contents to absolute.
4336 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004337
Robert Lye7eeb402014-06-03 19:35:24 -07004338 // Absolutely position tabs so they're ready for transition.
4339 $tabs.each(function(index) {
4340 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4341 });
smain@google.com95948b82014-06-16 19:24:25 -07004342
Robert Lye7eeb402014-06-03 19:35:24 -07004343 function transitionWidget($toTab) {
4344 if (!$curTab.is($toTab)) {
4345 var curIndex = $tabs.index($curTab[0]);
4346 var toIndex = $tabs.index($toTab[0]);
4347 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004348
Robert Lye7eeb402014-06-03 19:35:24 -07004349 // Animate content sections.
4350 $toTab.css({left:(width * dir) + 'px'});
4351 $curTab.animate({left:(width * -dir) + 'px'});
4352 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004353
Robert Lye7eeb402014-06-03 19:35:24 -07004354 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004355 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004356 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004357
Robert Lye7eeb402014-06-03 19:35:24 -07004358 // Store new current section.
4359 $curTab = $toTab;
4360 }
4361 }
smain@google.com95948b82014-06-16 19:24:25 -07004362 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08004363})();
Dirk Dougherty29e93432015-05-05 18:17:13 -07004364
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004365/**
4366 * Auto TOC
4367 *
4368 * Upgrades h2s on the page to have a rule and be toggle-able on mobile.
4369 */
4370(function($) {
4371 var upgraded = false;
4372 var h2Titles;
4373
4374 function initWidget() {
4375 // add HRs below all H2s (except for a few other h2 variants)
4376 // Consider doing this with css instead.
4377 h2Titles = $('h2').not('#qv h2, #tb h2, .sidebox h2, #devdoc-nav h2, h2.norule');
4378 h2Titles.css({marginBottom:0}).after('<hr/>');
4379
4380 // Exit early if on older browser.
4381 if (!window.matchMedia) {
4382 return;
4383 }
4384
4385 // Only run logic in mobile layout.
4386 var query = window.matchMedia('(max-width: 719px)');
4387 if (query.matches) {
4388 makeTogglable();
4389 } else {
4390 query.addListener(makeTogglable);
4391 }
4392 }
4393
4394 function makeTogglable() {
4395 // Only run this logic once.
4396 if (upgraded) { return; }
4397 upgraded = true;
4398
4399 // Only make content h2s togglable.
4400 var contentTitles = h2Titles.filter('#jd-content *');
4401
4402 // If there are more than 1
4403 if (contentTitles.size() < 2) {
4404 return;
4405 }
4406
4407 contentTitles.each(function() {
4408 // Find all the relevant nodes.
4409 var $title = $(this);
4410 var $hr = $title.next();
4411 var $contents = $hr.nextUntil('h2, .next-docs');
4412 var $section = $($title)
4413 .add($hr)
4414 .add($title.prev('a[name]'))
4415 .add($contents);
4416 var $anchor = $section.first().prev();
4417 var anchorMethod = 'after';
4418 if ($anchor.length === 0) {
4419 $anchor = $title.parent();
4420 anchorMethod = 'prepend';
4421 }
4422
4423 // Remove from DOM before messing with it. DOM is slow!
4424 $section.detach();
4425
4426 // Add mobile-only expand arrows.
4427 $title.prepend('<span class="dac-visible-mobile-inline-block">' +
4428 '<i class="dac-toggle-expand dac-sprite dac-expand-more-black"></i>' +
4429 '<i class="dac-toggle-collapse dac-sprite dac-expand-less-black"></i>' +
4430 '</span>')
4431 .attr('data-toggle', 'section');
4432
4433 // Wrap in magic markup.
4434 $section = $section.wrapAll('<div class="dac-toggle dac-mobile">').parent();
4435 $contents.wrapAll('<div class="dac-toggle-content"><div>'); // extra div used for max-height calculation.
4436
4437 // Add it back to the dom.
4438 $anchor[anchorMethod].call($anchor, $section);
4439 });
4440 }
4441
4442 $(function() {
4443 initWidget();
4444 });
4445})(jQuery);
4446
Dirk Dougherty29e93432015-05-05 18:17:13 -07004447(function($) {
4448 'use strict';
4449
4450 /**
4451 * Toggle Floating Label state.
4452 * @param {HTMLElement} el - The DOM element.
4453 * @param options
4454 * @constructor
4455 */
4456 function FloatingLabel(el, options) {
4457 this.el = $(el);
4458 this.options = $.extend({}, FloatingLabel.DEFAULTS_, options);
4459 this.group = this.el.closest('.dac-form-input-group');
4460 this.input = this.group.find('.dac-form-input');
4461
4462 this.checkValue_ = this.checkValue_.bind(this);
4463 this.checkValue_();
4464
4465 this.input.on('focus', function() {
4466 this.group.addClass('dac-focused');
4467 }.bind(this));
4468 this.input.on('blur', function() {
4469 this.group.removeClass('dac-focused');
4470 this.checkValue_();
4471 }.bind(this));
4472 this.input.on('keyup', this.checkValue_);
4473 }
4474
4475 /**
4476 * The label is moved out of the textbox when it has a value.
4477 */
4478 FloatingLabel.prototype.checkValue_ = function() {
4479 if (this.input.val().length) {
4480 this.group.addClass('dac-has-value');
4481 } else {
4482 this.group.removeClass('dac-has-value');
4483 }
4484 };
4485
4486 /**
4487 * jQuery plugin
4488 * @param {object} options - Override default options.
4489 */
4490 $.fn.dacFloatingLabel = function(options) {
4491 return this.each(function() {
4492 new FloatingLabel(this, options);
4493 });
4494 };
4495
4496 $(document).on('ready.aranja', function() {
4497 $('.dac-form-floatlabel').each(function() {
4498 $(this).dacFloatingLabel($(this).data());
4499 });
4500 });
4501})(jQuery);
4502
4503/* global toRoot, CAROUSEL_OVERRIDE */
4504(function($) {
4505 // Ordering matters
4506 var TAG_MAP = [
4507 {from: 'developerstory', to: 'Android Developer Story'},
4508 {from: 'googleplay', to: 'Google Play'}
4509 ];
4510
4511 function DacCarouselQuery(el) {
4512 this.el = $(el);
4513
4514 var opts = this.el.data();
4515 opts.maxResults = parseInt(opts.maxResults || '100', 10);
4516 opts.query = opts.carouselQuery;
4517 var resources = $.queryResources(opts);
4518
4519 this.el.empty();
4520 $(resources).map(function() {
4521 var resource = $.extend({}, this, CAROUSEL_OVERRIDE[this.url]);
4522 var slide = $('<article class="dac-expand dac-hero">');
4523 var image = cleanUrl(resource.heroImage || resource.image);
4524 var fullBleed = image && !resource.heroColor;
4525
4526 // Configure background
4527 slide.css({
4528 backgroundImage: fullBleed ? 'url(' + image + ')' : '',
4529 backgroundColor: resource.heroColor || ''
4530 });
4531
4532 // Should copy be inverted
4533 slide.toggleClass('dac-invert', resource.heroInvert || fullBleed);
4534 slide.toggleClass('dac-darken', fullBleed);
4535
4536 var cols = $('<div class="cols dac-hero-content">');
4537
4538 // inline image column
4539 var rightCol = $('<div class="col-1of2 col-push-1of2 dac-hero-figure">')
4540 .appendTo(cols);
4541
4542 if (!fullBleed && image) {
4543 rightCol.append($('<img>').attr('src', image));
4544 }
4545
4546 // info column
4547 $('<div class="col-1of2 col-pull-1of2">')
4548 .append($('<div class="dac-hero-tag">').text(formatTag(resource)))
4549 .append($('<h1 class="dac-hero-title">').text(formatTitle(resource)))
4550 .append($('<p class="dac-hero-description">').text(resource.summary))
4551 .append($('<a class="dac-hero-cta">')
4552 .text(formatCTA(resource))
4553 .attr('href', cleanUrl(resource.url))
4554 .prepend($('<span class="dac-sprite dac-auto-chevron">'))
4555 )
4556 .appendTo(cols);
4557
4558 slide.append(cols.wrap('<div class="wrap">').parent());
4559 return slide[0];
4560 }).prependTo(this.el);
4561
4562 // Pagination element.
4563 this.el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>');
4564
4565 this.el.dacCarousel();
4566 }
4567
4568 function cleanUrl(url) {
4569 if (url && url.indexOf('//') === -1) {
4570 url = toRoot + url;
4571 }
4572 return url;
4573 }
4574
4575 function formatTag(resource) {
4576 // Hmm, need a better more scalable solution for this.
4577 for (var i = 0, mapping; mapping = TAG_MAP[i]; i++) {
4578 if (resource.tags.indexOf(mapping.from) > -1) {
4579 return mapping.to;
4580 }
4581 }
4582 return resource.type;
4583 }
4584
4585 function formatTitle(resource) {
4586 return resource.title.replace(/android developer story: /i, '');
4587 }
4588
4589 function formatCTA(resource) {
4590 return resource.type === 'youtube' ? 'Watch the video' : 'Learn more';
4591 }
4592
4593 // jQuery plugin
4594 $.fn.dacCarouselQuery = function() {
4595 return this.each(function() {
4596 var el = $(this);
4597 var data = el.data('dac.carouselQuery');
4598
4599 if (!data) { el.data('dac.carouselQuery', (data = new DacCarouselQuery(el))); }
4600 });
4601 };
4602
4603 // Data API
4604 $(function() {
4605 $('[data-carousel-query]').dacCarouselQuery();
4606 });
4607})(jQuery);
4608
4609(function($) {
4610 /**
4611 * A CSS based carousel, inspired by SequenceJS.
4612 * @param {jQuery} el
4613 * @param {object} options
4614 * @constructor
4615 */
4616 function DacCarousel(el, options) {
4617 this.el = $(el);
4618 this.options = options = $.extend({}, DacCarousel.OPTIONS, this.el.data(), options || {});
4619 this.frames = this.el.find(options.frameSelector);
4620 this.count = this.frames.size();
4621 this.current = options.start;
4622
4623 this.initPagination();
4624 this.initEvents();
4625 this.initFrame();
4626 }
4627
4628 DacCarousel.OPTIONS = {
4629 auto: true,
4630 autoTime: 10000,
4631 autoMinTime: 5000,
4632 btnPrev: '[data-carousel-prev]',
4633 btnNext: '[data-carousel-next]',
4634 frameSelector: 'article',
4635 loop: true,
4636 start: 0,
4637 pagination: '[data-carousel-pagination]'
4638 };
4639
4640 DacCarousel.prototype.initPagination = function() {
4641 this.pagination = $([]);
4642 if (!this.options.pagination) { return; }
4643
4644 var pagination = $('<ul class="dac-pagination">');
4645 var parent = this.el;
4646 if (typeof this.options.pagination === 'string') { parent = this.el.find(this.options.pagination); }
4647
4648 if (this.count > 1) {
4649 for (var i = 0; i < this.count; i++) {
4650 var li = $('<li class="dac-pagination-item">').text(i);
4651 if (i === this.options.start) { li.addClass('active'); }
4652 li.click(this.go.bind(this, i));
4653
4654 pagination.append(li);
4655 }
4656 this.pagination = pagination.children();
4657 parent.append(pagination);
4658 }
4659 };
4660
4661 DacCarousel.prototype.initEvents = function() {
4662 var that = this;
4663
4664 this.el.hover(function() {
4665 that.pauseRotateTimer();
4666 }, function() {
4667 that.startRotateTimer();
4668 });
4669
4670 $(this.options.btnPrev).click(function(e) {
4671 e.preventDefault();
4672 that.prev();
4673 });
4674
4675 $(this.options.btnNext).click(function(e) {
4676 e.preventDefault();
4677 that.next();
4678 });
4679 };
4680
4681 DacCarousel.prototype.initFrame = function() {
4682 this.frames.removeClass('active').eq(this.options.start).addClass('active');
4683 };
4684
4685 DacCarousel.prototype.startRotateTimer = function() {
4686 if (!this.options.auto || this.rotateTimer) { return; }
4687 this.rotateTimer = setTimeout(this.next.bind(this), this.options.autoTime);
4688 };
4689
4690 DacCarousel.prototype.pauseRotateTimer = function() {
4691 clearTimeout(this.rotateTimer);
4692 this.rotateTimer = null;
4693 };
4694
4695 DacCarousel.prototype.prev = function() {
4696 this.go(this.current - 1);
4697 };
4698
4699 DacCarousel.prototype.next = function() {
4700 this.go(this.current + 1);
4701 };
4702
4703 DacCarousel.prototype.go = function(next) {
4704 // Figure out what the next slide is.
4705 while (this.count > 0 && next >= this.count) { next -= this.count; }
4706 while (next < 0) { next += this.count; }
4707
4708 // Cancel if we're already on that slide.
4709 if (next === this.current) { return; }
4710
4711 // Prepare next slide.
4712 this.frames.eq(next).removeClass('out');
4713
4714 // Recalculate styles before starting slide transition.
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004715 this.el.resolveStyles();
4716 // Update pagination
4717 this.pagination.removeClass('active').eq(next).addClass('active');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004718
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004719 // Transition out current frame
4720 this.frames.eq(this.current).toggleClass('active out');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004721
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004722 // Transition in a new frame
4723 this.frames.eq(next).toggleClass('active');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004724
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004725 this.current = next;
Dirk Dougherty29e93432015-05-05 18:17:13 -07004726 };
4727
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004728 // Helper which resolves new styles for an element, so it can start transitioning
4729 // from the new values.
4730 $.fn.resolveStyles = function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004731 /*jshint expr:true*/
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004732 this[0] && this[0].offsetTop;
4733 return this;
4734 };
Dirk Dougherty29e93432015-05-05 18:17:13 -07004735
4736 // jQuery plugin
4737 $.fn.dacCarousel = function() {
4738 this.each(function() {
4739 var $el = $(this);
4740 $el.data('dac-carousel', new DacCarousel(this));
4741 });
4742 return this;
4743 };
4744
4745 // Data API
4746 $(function() {
4747 $('[data-carousel]').dacCarousel();
4748 });
4749})(jQuery);
4750
4751(function($) {
4752 'use strict';
4753
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004754 function Modal(el, options) {
4755 this.el = $(el);
4756 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
4757 this.isOpen = false;
4758
4759 this.el.on('click', function(event) {
4760 if (!$.contains($('.dac-modal-window')[0], event.target)) {
4761 return this.close_();
4762 }
4763 }.bind(this));
4764
4765 this.el.on('open', this.open_.bind(this));
4766 this.el.on('close', this.close_.bind(this));
4767 this.el.on('toggle', this.toggle_.bind(this));
4768 }
4769
4770 Modal.prototype.toggle_ = function() {
4771 if (this.isOpen) {
4772 this.close_();
4773 } else {
4774 this.open_();
4775 }
4776 };
4777
4778 Modal.prototype.close_ = function() {
4779 this.el.removeClass('dac-active');
4780 $('body').removeClass('dac-modal-open');
4781 this.isOpen = false;
4782 };
4783
4784 Modal.prototype.open_ = function() {
4785 this.el.addClass('dac-active');
4786 $('body').addClass('dac-modal-open');
4787 this.isOpen = true;
4788 };
4789
Dirk Dougherty29e93432015-05-05 18:17:13 -07004790 function ToggleModal(el, options) {
4791 this.el = $(el);
4792 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004793 this.modal = this.options.modalToggle ? $('[data-modal="' + this.options.modalToggle + '"]') :
4794 this.el.closest('[data-modal]');
4795
Dirk Dougherty29e93432015-05-05 18:17:13 -07004796 this.el.on('click', this.clickHandler_.bind(this));
4797 }
4798
Dirk Dougherty29e93432015-05-05 18:17:13 -07004799 ToggleModal.prototype.clickHandler_ = function(event) {
4800 event.preventDefault();
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004801 this.modal.trigger('toggle');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004802 };
4803
4804 /**
4805 * jQuery plugin
4806 * @param {object} options - Override default options.
4807 */
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004808 $.fn.dacModal = function(options) {
4809 return this.each(function() {
4810 new Modal(this, options);
4811 });
4812 };
4813
Dirk Dougherty29e93432015-05-05 18:17:13 -07004814 $.fn.dacToggleModal = function(options) {
4815 return this.each(function() {
4816 new ToggleModal(this, options);
4817 });
4818 };
4819
4820 /**
4821 * Data Attribute API
4822 */
4823 $(document).on('ready.aranja', function() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004824 $('[data-modal]').each(function() {
4825 $(this).dacModal($(this).data());
4826 });
4827
4828 $('[data-modal-toggle]').each(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004829 $(this).dacToggleModal($(this).data());
4830 });
4831 });
4832})(jQuery);
4833
4834(function($) {
4835 'use strict';
4836
4837 /**
4838 * Toggle the visabilty of the mobile navigation.
4839 * @param {HTMLElement} el - The DOM element.
4840 * @param options
4841 * @constructor
4842 */
4843 function ToggleNav(el, options) {
4844 this.el = $(el);
4845 this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
4846 this.options.target = [this.options.navigation];
4847
4848 if (this.options.body) {this.options.target.push('body')}
4849 if (this.options.dimmer) {this.options.target.push(this.options.dimmer)}
4850
4851 this.el.on('click', this.clickHandler_.bind(this));
4852 }
4853
4854 /**
4855 * ToggleNav Default Settings
4856 * @type {{body: boolean, dimmer: string, navigation: string, toggleClass: string}}
4857 * @private
4858 */
4859 ToggleNav.DEFAULTS_ = {
4860 body: true,
4861 dimmer: '.dac-nav-dimmer',
4862 navigation: '[data-dac-nav]',
4863 toggleClass: 'dac-nav-open'
4864 };
4865
4866 /**
4867 * The actual toggle logic.
4868 * @param event
4869 * @private
4870 */
4871 ToggleNav.prototype.clickHandler_ = function(event) {
4872 event.preventDefault();
4873 $(this.options.target.join(', ')).toggleClass(this.options.toggleClass);
4874 };
4875
4876 /**
4877 * jQuery plugin
4878 * @param {object} options - Override default options.
4879 */
4880 $.fn.dacToggleMobileNav = function(options) {
4881 return this.each(function() {
4882 new ToggleNav(this, options);
4883 });
4884 };
4885
4886 /**
4887 * Data Attribute API
4888 */
4889 $(window).on('load.aranja', function() {
4890 $('[data-dac-toggle-nav]').each(function() {
4891 $(this).dacToggleMobileNav($(this).data());
4892 });
4893 });
4894})(jQuery);
4895
4896(function($) {
4897 'use strict';
4898
4899 /**
4900 * Submit the newsletter form to a Google Form.
4901 * @param {HTMLElement} el - The Form DOM element.
4902 * @constructor
4903 */
4904 function NewsletterForm(el) {
4905 this.el = $(el);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004906 this.form = this.el.find('form');
4907 $('<iframe/>').hide()
4908 .attr('name', 'dac-newsletter-iframe')
4909 .attr('src', '')
4910 .insertBefore(this.form);
4911 this.form.on('submit', this.submitHandler_.bind(this));
Dirk Dougherty29e93432015-05-05 18:17:13 -07004912 }
4913
4914 /**
4915 * Close the modal when the form is sent.
4916 * @private
4917 */
4918 NewsletterForm.prototype.submitHandler_ = function() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004919 this.form.trigger('reset');
4920 this.el.trigger('close');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004921 };
4922
4923 /**
4924 * jQuery plugin
4925 * @param {object} options - Override default options.
4926 */
4927 $.fn.dacNewsletterForm = function(options) {
4928 return this.each(function() {
4929 new NewsletterForm(this, options);
4930 });
4931 };
4932
4933 /**
4934 * Data Attribute API
4935 */
4936 $(document).on('ready.aranja', function() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004937 $('[data-newsletter]').each(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004938 $(this).dacNewsletterForm();
4939 });
4940 });
4941})(jQuery);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004942
4943(function($) {
4944 'use strict';
4945
4946 /**
4947 * Smoothly scroll to location on current page.
4948 * @param el
4949 * @param options
4950 * @constructor
4951 */
4952 function ScrollButton(el, options) {
4953 this.el = $(el);
4954 this.target = $(this.el.attr('href'));
4955 this.options = $.extend({}, ScrollButton.DEFAULTS_, options);
4956
4957 if (typeof this.options.offset === 'string') {
4958 this.options.offset = $(this.options.offset).height();
4959 }
4960
4961 this.el.on('click', this.clickHandler_.bind(this));
4962 }
4963
4964 /**
4965 * Default options
4966 * @type {{duration: number, easing: string, offset: number, scrollContainer: string}}
4967 * @private
4968 */
4969 ScrollButton.DEFAULTS_ = {
4970 duration: 300,
4971 easing: 'swing',
4972 offset: 0,
4973 scrollContainer: 'html, body'
4974 };
4975
4976 /**
4977 * Scroll logic
4978 * @param event
4979 * @private
4980 */
4981 ScrollButton.prototype.clickHandler_ = function(event) {
4982 if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
4983 return;
4984 }
4985
4986 event.preventDefault();
4987
4988 $(this.options.scrollContainer).animate({
4989 scrollTop: this.target.offset().top - this.options.offset
4990 }, this.options);
4991 };
4992
4993 /**
4994 * jQuery plugin
4995 * @param {object} options - Override default options.
4996 */
4997 $.fn.dacScrollButton = function(options) {
4998 return this.each(function() {
4999 new ScrollButton(this, options);
5000 });
5001 };
5002
5003 /**
5004 * Data Attribute API
5005 */
5006 $(document).on('ready.aranja', function() {
5007 $('[data-scroll-button]').each(function() {
5008 $(this).dacScrollButton($(this).data());
5009 });
5010 });
5011})(jQuery);
5012
5013(function($) {
5014 function Toggle(el) {
5015 $(el).on('click.dac.togglesection', this.toggle);
5016 }
5017
5018 Toggle.prototype.toggle = function() {
5019 var $this = $(this);
5020
5021 var $parent = getParent($this);
5022 var isExpanded = $parent.hasClass('is-expanded');
5023
5024 transitionMaxHeight($parent.find('.dac-toggle-content'), !isExpanded);
5025 $parent.toggleClass('is-expanded');
5026
5027 return false;
5028 };
5029
5030 function getParent($this) {
5031 var selector = $this.attr('data-target');
5032
5033 if (!selector) {
5034 selector = $this.attr('href');
5035 selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '');
5036 }
5037
5038 var $parent = selector && $(selector);
5039
5040 return $parent && $parent.length ? $parent : $this.parent();
5041 }
5042
5043 /**
5044 * Runs a transition of max-height along with responsive styles which hide or expand the element.
5045 * @param $el
5046 * @param visible
5047 */
5048 function transitionMaxHeight($el, visible) {
5049 // Only supports 1 child
5050 var contentHeight = $el.children().outerHeight();
5051 var targetHeight = visible ? contentHeight : 0;
5052 var duration = $el.transitionDuration();
5053
5054 // If we're hiding, first set the maxHeight we're transitioning from.
5055 if (!visible) {
5056 $el.css('maxHeight', contentHeight + 'px')
5057 .resolveStyles();
5058 }
5059
5060 // Transition to new state
5061 $el.css('maxHeight', targetHeight);
5062
5063 // Reset maxHeight to css value after transition.
5064 setTimeout(function() {
5065 $el.css('maxHeight', '');
5066 }, duration);
5067 }
5068
5069 // Utility to get the transition duration for the element.
5070 $.fn.transitionDuration = function() {
5071 var d = $(this).css('transitionDuration') || '0s';
5072
5073 return +(parseFloat(d) * (/ms/.test(d) ? 1 : 1000)).toFixed(0);
5074 };
5075
5076 // jQuery plugin
5077 $.fn.toggleSection = function(option) {
5078 return this.each(function() {
5079 var $this = $(this);
5080 var data = $this.data('dac.togglesection');
5081 if (!data) {$this.data('dac.togglesection', (data = new Toggle(this)));}
5082 if (typeof option === 'string') {data[option].call($this);}
5083 });
5084 };
5085
5086 // Data api
5087 $(document)
5088 .on('click.toggle', '[data-toggle="section"]', Toggle.prototype.toggle);
5089})(jQuery);