blob: ca642171f30899840fe5e0e060d12f2ffc5e1bb1 [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) {
59 if (event.which == 191) {
60 $('#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
74 $('.scroll-pane').jScrollPane( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -070075
Scott Maine4d8f1b2012-06-21 18:03:05 -070076 // add HRs below all H2s (except for a few other h2 variants)
Scott Mainc29b3f52014-05-30 21:18:30 -070077 $('h2').not('#qv h2')
78 .not('#tb h2')
79 .not('.sidebox h2')
80 .not('#devdoc-nav h2')
81 .not('h2.norule').css({marginBottom:0})
82 .after('<hr/>');
Scott Maine4d8f1b2012-06-21 18:03:05 -070083
84 // set up the search close button
Dirk Dougherty29e93432015-05-05 18:17:13 -070085 $('#search-close').click(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -070086 $searchInput = $('#search_autocomplete');
87 $searchInput.attr('value', '');
88 $(this).addClass("hide");
89 $("#search-container").removeClass('active');
90 $("#search_autocomplete").blur();
Scott Main0e76e7e2013-03-12 10:24:07 -070091 search_focus_changed($searchInput.get(), false);
92 hideResults();
Scott Maine4d8f1b2012-06-21 18:03:05 -070093 });
94
Scott Main3b90aff2013-08-01 18:09:35 -070095
Scott Maine4d8f1b2012-06-21 18:03:05 -070096 //Set up search
97 $("#search_autocomplete").focus(function() {
98 $("#search-container").addClass('active');
99 })
100 $("#search-container").mouseover(function() {
101 $("#search-container").addClass('active');
102 $("#search_autocomplete").focus();
103 })
104 $("#search-container").mouseout(function() {
105 if ($("#search_autocomplete").is(":focus")) return;
106 if ($("#search_autocomplete").val() == '') {
107 setTimeout(function(){
108 $("#search-container").removeClass('active');
109 $("#search_autocomplete").blur();
110 },250);
111 }
112 })
113 $("#search_autocomplete").blur(function() {
114 if ($("#search_autocomplete").val() == '') {
115 $("#search-container").removeClass('active');
116 }
117 })
118
Scott Main3b90aff2013-08-01 18:09:35 -0700119
Scott Maine4d8f1b2012-06-21 18:03:05 -0700120 // prep nav expandos
121 var pagePath = document.location.pathname;
122 // account for intl docs by removing the intl/*/ path
123 if (pagePath.indexOf("/intl/") == 0) {
124 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
125 }
Scott Mainac2aef52013-02-12 14:15:23 -0800126
Scott Maine4d8f1b2012-06-21 18:03:05 -0700127 if (pagePath.indexOf(SITE_ROOT) == 0) {
128 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
129 pagePath += 'index.html';
130 }
131 }
132
Scott Main01a25452013-02-12 17:32:27 -0800133 // Need a copy of the pagePath before it gets changed in the next block;
134 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
135 var pagePathOriginal = pagePath;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700136 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
137 // If running locally, SITE_ROOT will be a relative path, so account for that by
138 // finding the relative URL to this page. This will allow us to find links on the page
139 // leading back to this page.
140 var pathParts = pagePath.split('/');
141 var relativePagePathParts = [];
142 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
143 for (var i = 0; i < upDirs; i++) {
144 relativePagePathParts.push('..');
145 }
146 for (var i = 0; i < upDirs; i++) {
147 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
148 }
149 relativePagePathParts.push(pathParts[pathParts.length - 1]);
150 pagePath = relativePagePathParts.join('/');
151 } else {
152 // Otherwise the page path is already an absolute URL
153 }
154
Scott Mainac2aef52013-02-12 14:15:23 -0800155 // Highlight the header tabs...
156 // highlight Design tab
Dirk Dougherty29e93432015-05-05 18:17:13 -0700157 var urlSegments = pagePathOriginal.split('/');
158 var navEl = $(".dac-nav-list");
159 var subNavEl = navEl.find(".dac-nav-secondary");
160 var parentNavEl;
Scott Mainac2aef52013-02-12 14:15:23 -0800161
Dirk Dougherty29e93432015-05-05 18:17:13 -0700162 if ($("body").hasClass("design")) {
163 navEl.find("> li.design > a").addClass("selected");
smain@google.com6040ffa2014-06-13 15:06:23 -0700164 // highlight About tabs
165 } else if ($("body").hasClass("about")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700166 if (urlSegments[1] == "about" || urlSegments[1] == "wear" || urlSegments[1] == "tv" || urlSegments[1] == "auto") {
167 navEl.find("> li.home > a").addClass('has-subnav');
168 subNavEl.find("li." + urlSegments[1] + " > a").addClass("selected");
169 } else {
170 navEl.find("> li.home > a").addClass('selected');
smain@google.com6040ffa2014-06-13 15:06:23 -0700171 }
Scott Mainac2aef52013-02-12 14:15:23 -0800172 // highlight Develop tab
173 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700174 parentNavEl = navEl.find("> li.develop > a");
175 parentNavEl.addClass('has-subnav');
176
Scott Mainac2aef52013-02-12 14:15:23 -0800177 // In Develop docs, also highlight appropriate sub-tab
Dirk Dougherty29e93432015-05-05 18:17:13 -0700178 if (urlSegments[1] == "training") {
179 subNavEl.find("li.training > a").addClass("selected");
180 } else if (urlSegments[1] == "guide") {
181 subNavEl.find("li.guide > a").addClass("selected");
182 } else if (urlSegments[1] == "reference") {
Scott Mainac2aef52013-02-12 14:15:23 -0800183 // If the root is reference, but page is also part of Google Services, select Google
184 if ($("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700185 subNavEl.find("li.google > a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800186 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700187 subNavEl.find("li.reference > a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800188 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700189 } else if ((urlSegments[1] == "tools") || (urlSegments[1] == "sdk")) {
190 subNavEl.find("li.tools > a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800191 } else if ($("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700192 subNavEl.find("li.google > a").addClass("selected");
Dirk Dougherty4f7e5152010-09-16 10:43:40 -0700193 } else if ($("body").hasClass("samples")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700194 subNavEl.find("li.samples > a").addClass("selected");
195 } else {
196 parentNavEl.removeClass('has-subnav').addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800197 }
Scott Mainac2aef52013-02-12 14:15:23 -0800198 // highlight Distribute tab
199 } else if ($("body").hasClass("distribute")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700200 parentNavEl = navEl.find("> li.distribute > a");
201 parentNavEl.addClass('has-subnav');
Dirk Doughertyc3921652014-05-13 16:55:26 -0700202
Dirk Dougherty29e93432015-05-05 18:17:13 -0700203 if (urlSegments[2] == "users") {
204 subNavEl.find("li.users > a").addClass("selected");
205 } else if (urlSegments[2] == "engage") {
206 subNavEl.find("li.engage > a").addClass("selected");
207 } else if (urlSegments[2] == "monetize") {
208 subNavEl.find("li.monetize > a").addClass("selected");
209 } else if (urlSegments[2] == "analyze") {
210 subNavEl.find("li.analyze > a").addClass("selected");
211 } else if (urlSegments[2] == "tools") {
212 subNavEl.find("li.disttools > a").addClass("selected");
213 } else if (urlSegments[2] == "stories") {
214 subNavEl.find("li.stories > a").addClass("selected");
215 } else if (urlSegments[2] == "essentials") {
216 subNavEl.find("li.essentials > a").addClass("selected");
217 } else if (urlSegments[2] == "googleplay") {
218 subNavEl.find("li.googleplay > a").addClass("selected");
219 } else {
220 parentNavEl.removeClass('has-subnav').addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700221 }
Scott Mainb16376f2014-05-21 20:35:47 -0700222 }
Scott Mainac2aef52013-02-12 14:15:23 -0800223
Scott Mainf6145542013-04-01 16:38:11 -0700224 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
225 // and highlight the sidenav
226 mPagePath = pagePath;
227 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700228 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800229
Scott Mainf6145542013-04-01 16:38:11 -0700230 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700231 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700232 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700233 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800234 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700235
236 // set up prev links
237 var $prevLink = [];
238 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700239
Scott Maine4d8f1b2012-06-21 18:03:05 -0700240 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
241false; // navigate across topic boundaries only in design docs
242 if ($prevListItem.length) {
smain@google.comc91ecb72014-06-23 10:22:23 -0700243 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Scott Main5a1123e2012-09-26 12:51:28 -0700244 // jump to last topic of previous section
245 $prevLink = $prevListItem.find('a:last');
246 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700247 // jump to previous topic in this section
248 $prevLink = $prevListItem.find('a:eq(0)');
249 }
250 } else {
251 // jump to this section's index page (if it exists)
252 var $parentListItem = $selListItem.parents('li');
253 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700254
Scott Maine4d8f1b2012-06-21 18:03:05 -0700255 // except if cross boundaries aren't allowed, and we're at the top of a section already
256 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700257 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700258 && $selListItem.hasClass('nav-section')) {
259 $prevLink = [];
260 }
261 }
262
Scott Maine4d8f1b2012-06-21 18:03:05 -0700263 // set up next links
264 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700265 var startClass = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700266 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700267
Scott Main1a00f7f2013-10-29 11:11:19 -0700268 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700269 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700270 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700271
272 // if there aren't any children, go to the next section (required for About pages)
273 if($nextLink.length == 0) {
274 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700275 } else if ($('.topic-start-link').length) {
276 // as long as there's a child link and there is a "topic start link" (we're on a landing)
277 // then set the landing page "start link" text to be the first doc title
278 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700279 }
Scott Main3b90aff2013-08-01 18:09:35 -0700280
Scott Main5a1123e2012-09-26 12:51:28 -0700281 // If the selected page has a description, then it's a class or article homepage
282 if ($selListItem.find('a[description]').length) {
283 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700284 startClass = true;
285 }
286 } else {
287 // jump to the next topic in this section (if it exists)
288 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700289 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700290 isCrossingBoundary = true;
291 // no more topics in this section, jump to the first topic in the next section
smain@google.comabf34112014-06-23 11:39:02 -0700292 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Scott Main5a1123e2012-09-26 12:51:28 -0700293 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
294 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700295 if ($nextLink.length == 0) {
296 // if that doesn't work, we're at the end of the list, so disable NEXT link
297 $('.next-page-link').attr('href','').addClass("disabled")
298 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700299 // and completely hide the one in the footer
300 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700301 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700302 }
303 }
304 }
Scott Main5a1123e2012-09-26 12:51:28 -0700305
306 if (startClass) {
307 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
308
Scott Main3b90aff2013-08-01 18:09:35 -0700309 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700310 // then we need to add a bottom border to button
311 if (!$("#tb").length) {
312 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700313 }
Scott Main5a1123e2012-09-26 12:51:28 -0700314 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
315 $('.content-footer.next-class').show();
316 $('.next-page-link').attr('href','')
317 .removeClass("hide").addClass("disabled")
318 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700319 // and completely hide the one in the footer
320 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700321 if ($nextLink.length) {
322 $('.next-class-link').attr('href',$nextLink.attr('href'))
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700323 .removeClass("hide")
324 .append(": " + $nextLink.html());
Scott Main1a00f7f2013-10-29 11:11:19 -0700325 $('.next-class-link').find('.new').empty();
326 }
Scott Main5a1123e2012-09-26 12:51:28 -0700327 } else {
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700328 $('.next-page-link').attr('href', $nextLink.attr('href'))
329 .removeClass("hide");
330 // for the footer link, also add the next page title
331 $('.content-footer .next-page-link').append(": " + $nextLink.html());
Scott Main5a1123e2012-09-26 12:51:28 -0700332 }
333
334 if (!startClass && $prevLink.length) {
335 var prevHref = $prevLink.attr('href');
336 if (prevHref == SITE_ROOT + 'index.html') {
337 // Don't show Previous when it leads to the homepage
338 } else {
339 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
340 }
Scott Main3b90aff2013-08-01 18:09:35 -0700341 }
Scott Main5a1123e2012-09-26 12:51:28 -0700342
Scott Maine4d8f1b2012-06-21 18:03:05 -0700343 }
Scott Main3b90aff2013-08-01 18:09:35 -0700344
345
346
Scott Main5a1123e2012-09-26 12:51:28 -0700347 // Set up the course landing pages for Training with class names and descriptions
348 if ($('body.trainingcourse').length) {
349 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700350
351 // create an array for all the class descriptions
352 var $classDescriptions = new Array($classLinks.length);
353 var lang = getLangPref();
354 $classLinks.each(function(index) {
355 var langDescr = $(this).attr(lang + "-description");
356 if (typeof langDescr !== 'undefined' && langDescr !== false) {
357 // if there's a class description in the selected language, use that
358 $classDescriptions[index] = langDescr;
359 } else {
360 // otherwise, use the default english description
361 $classDescriptions[index] = $(this).attr("description");
362 }
363 });
Scott Main3b90aff2013-08-01 18:09:35 -0700364
Scott Main5a1123e2012-09-26 12:51:28 -0700365 var $olClasses = $('<ol class="class-list"></ol>');
366 var $liClass;
Scott Main5a1123e2012-09-26 12:51:28 -0700367 var $h2Title;
368 var $pSummary;
369 var $olLessons;
370 var $liLesson;
371 $classLinks.each(function(index) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700372 $liClass = $('<li class="clearfix"></li>');
Scott Main5a1123e2012-09-26 12:51:28 -0700373 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700374 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700375
Scott Main5a1123e2012-09-26 12:51:28 -0700376 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700377
Scott Main5a1123e2012-09-26 12:51:28 -0700378 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700379
Scott Main5a1123e2012-09-26 12:51:28 -0700380 if ($lessons.length) {
Scott Main5a1123e2012-09-26 12:51:28 -0700381 $lessons.each(function(index) {
382 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
383 });
384 } else {
Scott Main5a1123e2012-09-26 12:51:28 -0700385 $pSummary.addClass('article');
386 }
387
Dirk Dougherty29e93432015-05-05 18:17:13 -0700388 $liClass.append($h2Title).append($pSummary).append($olLessons);
Scott Main5a1123e2012-09-26 12:51:28 -0700389 $olClasses.append($liClass);
390 });
391 $('.jd-descr').append($olClasses);
392 }
393
Scott Maine4d8f1b2012-06-21 18:03:05 -0700394 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700395 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700396
Scott Main3b90aff2013-08-01 18:09:35 -0700397
Scott Maine4d8f1b2012-06-21 18:03:05 -0700398 $(".scroll-pane").scroll(function(event) {
399 event.preventDefault();
400 return false;
401 });
402
403 /* Resize nav height when window height changes */
404 $(window).resize(function() {
405 if ($('#side-nav').length == 0) return;
406 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
407 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
408 // make sidenav behave when resizing the window and side-scolling is a concern
Scott Mainf5257812014-05-22 17:26:38 -0700409 if (sticky) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700410 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
411 updateSideNavPosition();
412 } else {
413 updateSidenavFullscreenWidth();
414 }
415 }
416 resizeNav();
417 });
418
419
Scott Maine4d8f1b2012-06-21 18:03:05 -0700420 var navBarLeftPos;
421 if ($('#devdoc-nav').length) {
422 setNavBarLeftPos();
423 }
424
425
Scott Maine4d8f1b2012-06-21 18:03:05 -0700426 // Set up play-on-hover <video> tags.
427 $('video.play-on-hover').bind('click', function(){
428 $(this).get(0).load(); // in case the video isn't seekable
429 $(this).get(0).play();
430 });
431
432 // Set up tooltips
433 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700434 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700435 var $target = $(this);
436 var $tooltip = $('<div>')
437 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700438 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700439 .hide()
440 .appendTo('body');
441 $target.removeAttr('title');
442
443 $target.hover(function() {
444 // in
445 var targetRect = $target.offset();
446 targetRect.width = $target.width();
447 targetRect.height = $target.height();
448
449 $tooltip.css({
450 left: targetRect.left,
451 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
452 });
453 $tooltip.addClass('below');
454 $tooltip.show();
455 }, function() {
456 // out
457 $tooltip.hide();
458 });
459 });
460
461 // Set up <h2> deeplinks
462 $('h2').click(function() {
463 var id = $(this).attr('id');
464 if (id) {
465 document.location.hash = id;
466 }
467 });
468
469 //Loads the +1 button
470 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
471 po.src = 'https://apis.google.com/js/plusone.js';
472 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
473
Scott Maine4d8f1b2012-06-21 18:03:05 -0700474 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700475
Scott Maine4d8f1b2012-06-21 18:03:05 -0700476 if ($(".scroll-pane").length > 1) {
477 // Check if there's a user preference for the panel heights
478 var cookieHeight = readCookie("reference_height");
479 if (cookieHeight) {
480 restoreHeight(cookieHeight);
481 }
482 }
Scott Main3b90aff2013-08-01 18:09:35 -0700483
Scott Main06f3f2c2014-05-30 11:23:00 -0700484 // Resize once loading is finished
Scott Maine4d8f1b2012-06-21 18:03:05 -0700485 resizeNav();
Scott Main06f3f2c2014-05-30 11:23:00 -0700486 // Check if there's an anchor that we need to scroll into view.
487 // A delay is needed, because some browsers do not immediately scroll down to the anchor
488 window.setTimeout(offsetScrollForSticky, 100);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700489
Scott Main015d6162013-01-29 09:01:52 -0800490 /* init the language selector based on user cookie for lang */
491 loadLangPref();
492 changeNavLang(getLangPref());
493
494 /* setup event handlers to ensure the overflow menu is visible while picking lang */
495 $("#language select")
496 .mousedown(function() {
497 $("div.morehover").addClass("hover"); })
498 .blur(function() {
499 $("div.morehover").removeClass("hover"); });
500
501 /* some global variable setup */
502 resizePackagesNav = $("#resize-packages-nav");
503 classesNav = $("#classes-nav");
504 devdocNav = $("#devdoc-nav");
505
506 var cookiePath = "";
507 if (location.href.indexOf("/reference/") != -1) {
508 cookiePath = "reference_";
509 } else if (location.href.indexOf("/guide/") != -1) {
510 cookiePath = "guide_";
511 } else if (location.href.indexOf("/tools/") != -1) {
512 cookiePath = "tools_";
513 } else if (location.href.indexOf("/training/") != -1) {
514 cookiePath = "training_";
515 } else if (location.href.indexOf("/design/") != -1) {
516 cookiePath = "design_";
517 } else if (location.href.indexOf("/distribute/") != -1) {
518 cookiePath = "distribute_";
519 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700520
smain@google.com698fff02014-11-20 20:39:33 -0800521
522 /* setup shadowbox for any videos that want it */
smain@google.comf75ee212014-11-24 09:42:59 -0800523 var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
smain@google.com698fff02014-11-20 20:39:33 -0800524 if ($videoLinks.length) {
525 // if there's at least one, add the shadowbox HTML to the body
526 $('body').prepend(
527'<div id="video-container">'+
528 '<div id="video-frame">'+
529 '<div class="video-close">'+
530 '<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
531 '</div>'+
532 '<div id="youTubePlayer"></div>'+
533 '</div>'+
534'</div>');
535
536 // loads the IFrame Player API code asynchronously.
537 $.getScript("https://www.youtube.com/iframe_api");
538
539 $videoLinks.each(function() {
540 var videoId = $(this).attr('href').split('?v=')[1];
541 $(this).click(function(event) {
542 event.preventDefault();
543 startYouTubePlayer(videoId);
544 });
545 });
smain@google.com698fff02014-11-20 20:39:33 -0800546 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700547
548 // Responsive testing
549 var responsiveParam = location.href.match(/[?&]responsive=?(|true|false)/);
550 if (responsiveParam) {
551 localStorage['test-responsive'] = ['', 'true'].indexOf(responsiveParam) > -1;
552 }
553 if (localStorage['test-responsive']) {
554 $(document.body).addClass('responsive');
555 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700556});
Scott Main7e447ed2013-02-19 17:22:37 -0800557// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700558
559
smain@google.com698fff02014-11-20 20:39:33 -0800560var youTubePlayer;
561function onYouTubeIframeAPIReady() {
562}
563
smain@google.com3de83c12014-12-12 19:06:52 -0800564/* Returns the height the shadowbox video should be. It's based on the current
565 height of the "video-frame" element, which is 100% height for the window.
566 Then minus the margin so the video isn't actually the full window height. */
567function getVideoHeight() {
568 var frameHeight = $("#video-frame").height();
569 var marginTop = $("#video-frame").css('margin-top').split('px')[0];
570 return frameHeight - (marginTop * 2);
571}
572
smain@google.comd162be52015-02-05 13:27:16 -0800573var mPlayerPaused = false;
574
smain@google.com698fff02014-11-20 20:39:33 -0800575function startYouTubePlayer(videoId) {
smain@google.com3de83c12014-12-12 19:06:52 -0800576 $("#video-container").show();
577 $("#video-frame").show();
smain@google.comd162be52015-02-05 13:27:16 -0800578 mPlayerPaused = false;
smain@google.com3de83c12014-12-12 19:06:52 -0800579
580 // compute the size of the player so it's centered in window
581 var maxWidth = 940; // the width of the web site content
582 var videoAspect = .5625; // based on 1280x720 resolution
583 var maxHeight = maxWidth * videoAspect;
584 var videoHeight = getVideoHeight();
585 var videoWidth = videoHeight / videoAspect;
586 if (videoWidth > maxWidth) {
587 videoWidth = maxWidth;
588 videoHeight = maxHeight;
smain@google.com570c2212014-11-25 19:01:40 -0800589 }
smain@google.com3de83c12014-12-12 19:06:52 -0800590 $("#video-frame").css('width', videoWidth);
591
592 // check if we've already created this player
smain@google.com698fff02014-11-20 20:39:33 -0800593 if (youTubePlayer == null) {
smain@google.com3de83c12014-12-12 19:06:52 -0800594 // check if there's a start time specified
595 var idAndHash = videoId.split("#");
596 var startTime = 0;
597 if (idAndHash.length > 1) {
598 startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0;
599 }
600 // enable localized player
601 var lang = getLangPref();
602 var captionsOn = lang == 'en' ? 0 : 1;
603
smain@google.com698fff02014-11-20 20:39:33 -0800604 youTubePlayer = new YT.Player('youTubePlayer', {
smain@google.com3de83c12014-12-12 19:06:52 -0800605 height: videoHeight,
606 width: videoWidth,
smain@google.com570c2212014-11-25 19:01:40 -0800607 videoId: idAndHash[0],
smain@google.com481f15c2014-12-12 11:57:27 -0800608 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
smain@google.com698fff02014-11-20 20:39:33 -0800609 events: {
smain@google.comf75ee212014-11-24 09:42:59 -0800610 'onReady': onPlayerReady,
611 'onStateChange': onPlayerStateChange
smain@google.com698fff02014-11-20 20:39:33 -0800612 }
613 });
614 } else {
smain@google.com3de83c12014-12-12 19:06:52 -0800615 // reset the size in case the user adjusted the window since last play
616 youTubePlayer.setSize(videoWidth, videoHeight);
smain@google.com94e0b532014-12-16 19:07:08 -0800617 // if a video different from the one already playing was requested, cue it up
smain@google.comd162be52015-02-05 13:27:16 -0800618 if (videoId != youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]) {
smain@google.com94e0b532014-12-16 19:07:08 -0800619 youTubePlayer.cueVideoById(videoId);
620 }
smain@google.com698fff02014-11-20 20:39:33 -0800621 youTubePlayer.playVideo();
622 }
smain@google.com698fff02014-11-20 20:39:33 -0800623}
624
625function onPlayerReady(event) {
626 event.target.playVideo();
smain@google.comd162be52015-02-05 13:27:16 -0800627 mPlayerPaused = false;
smain@google.com698fff02014-11-20 20:39:33 -0800628}
629
630function closeVideo() {
631 try {
smain@google.comf75ee212014-11-24 09:42:59 -0800632 youTubePlayer.pauseVideo();
smain@google.com698fff02014-11-20 20:39:33 -0800633 } catch(e) {
smain@google.com698fff02014-11-20 20:39:33 -0800634 }
smain@google.com3de83c12014-12-12 19:06:52 -0800635 $("#video-container").fadeOut(200);
smain@google.com698fff02014-11-20 20:39:33 -0800636}
637
smain@google.comf75ee212014-11-24 09:42:59 -0800638/* Track youtube playback for analytics */
639function onPlayerStateChange(event) {
640 // Video starts, send the video ID
641 if (event.data == YT.PlayerState.PLAYING) {
smain@google.comd162be52015-02-05 13:27:16 -0800642 if (mPlayerPaused) {
643 ga('send', 'event', 'Videos', 'Resume',
644 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]);
645 } else {
646 // track the start playing event so we know from which page the video was selected
647 ga('send', 'event', 'Videos', 'Start: ' +
648 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
649 'on: ' + document.location.href);
650 }
651 mPlayerPaused = false;
smain@google.comf75ee212014-11-24 09:42:59 -0800652 }
653 // Video paused, send video ID and video elapsed time
654 if (event.data == YT.PlayerState.PAUSED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800655 ga('send', 'event', 'Videos', 'Paused',
smain@google.comd162be52015-02-05 13:27:16 -0800656 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
657 youTubePlayer.getCurrentTime());
658 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800659 }
660 // Video finished, send video ID and video elapsed time
661 if (event.data == YT.PlayerState.ENDED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800662 ga('send', 'event', 'Videos', 'Finished',
smain@google.comd162be52015-02-05 13:27:16 -0800663 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
664 youTubePlayer.getCurrentTime());
665 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800666 }
667}
668
smain@google.com698fff02014-11-20 20:39:33 -0800669
670
Scott Mainad08f072013-08-20 16:49:57 -0700671function initExpandableNavItems(rootTag) {
672 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
673 var section = $(this).closest('li.nav-section');
674 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700675 /* hide me and descendants */
676 section.find('ul').slideUp(250, function() {
677 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700678 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700679 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700680 resizeNav();
681 });
682 } else {
683 /* show me */
684 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700685 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700686 $others.removeClass('expanded').children('ul').slideUp(250);
687
688 // now expand me
689 section.closest('li').addClass('expanded');
690 section.children('ul').slideDown(250, function() {
691 resizeNav();
692 });
693 }
694 });
Scott Mainf0093852013-08-22 11:37:11 -0700695
696 // Stop expand/collapse behavior when clicking on nav section links
697 // (since we're navigating away from the page)
698 // This selector captures the first instance of <a>, but not those with "#" as the href.
699 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
700 window.location.href = $(this).attr('href');
701 return false;
702 });
Scott Mainad08f072013-08-20 16:49:57 -0700703}
704
Dirk Doughertyc3921652014-05-13 16:55:26 -0700705
706/** Create the list of breadcrumb links in the sticky header */
707function buildBreadcrumbs() {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700708 var $breadcrumbUl = $(".dac-header-crumbs");
709 var primaryNavLink = ".dac-nav-list > .dac-nav-item > .dac-nav-link";
710
Dirk Doughertyc3921652014-05-13 16:55:26 -0700711 // Add the secondary horizontal nav item, if provided
Dirk Dougherty29e93432015-05-05 18:17:13 -0700712 var $selectedSecondNav = $(".dac-nav-secondary .dac-nav-link.selected").clone()
713 .attr('class', 'dac-header-crumbs-link');
714
Dirk Doughertyc3921652014-05-13 16:55:26 -0700715 if ($selectedSecondNav.length) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700716 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedSecondNav));
Dirk Doughertyc3921652014-05-13 16:55:26 -0700717 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700718
Dirk Doughertyc3921652014-05-13 16:55:26 -0700719 // Add the primary horizontal nav
Dirk Dougherty29e93432015-05-05 18:17:13 -0700720 var $selectedFirstNav = $(primaryNavLink + ".selected, " + primaryNavLink + ".has-subnav").clone()
721 .attr('class', 'dac-header-crumbs-link');
722
Dirk Doughertyc3921652014-05-13 16:55:26 -0700723 // If there's no header nav item, use the logo link and title from alt text
724 if ($selectedFirstNav.length < 1) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700725 $selectedFirstNav = $('<a class="dac-header-crumbs-link">')
Dirk Doughertyc3921652014-05-13 16:55:26 -0700726 .attr('href', $("div#header .logo a").attr('href'))
727 .text($("div#header .logo img").attr('alt'));
728 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700729 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedFirstNav));
Dirk Doughertyc3921652014-05-13 16:55:26 -0700730}
731
732
733
Scott Maine624b3f2013-09-12 12:56:41 -0700734/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700735function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700736 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
737 if ($("ul#nav li.selected").length) {
738 unHighlightSidenav();
739 }
740 // look for URL in sidenav, including the hash
741 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
742
743 // If the selNavLink is still empty, look for it without the hash
744 if ($selNavLink.length == 0) {
745 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
746 }
747
Scott Mainf6145542013-04-01 16:38:11 -0700748 var $selListItem;
749 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700750 // Find this page's <li> in sidenav and set selected
751 $selListItem = $selNavLink.closest('li');
752 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700753
Scott Mainf6145542013-04-01 16:38:11 -0700754 // Traverse up the tree and expand all parent nav-sections
755 $selNavLink.parents('li.nav-section').each(function() {
756 $(this).addClass('expanded');
757 $(this).children('ul').show();
758 });
759 }
760}
761
Scott Maine624b3f2013-09-12 12:56:41 -0700762function unHighlightSidenav() {
763 $("ul#nav li.selected").removeClass("selected");
764 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
765}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700766
767function toggleFullscreen(enable) {
768 var delay = 20;
769 var enabled = true;
770 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
771 if (enable) {
772 // Currently NOT USING fullscreen; enable fullscreen
773 stylesheet.removeAttr('disabled');
774 $('#nav-swap .fullscreen').removeClass('disabled');
775 $('#devdoc-nav').css({left:''});
776 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
777 enabled = true;
778 } else {
779 // Currently USING fullscreen; disable fullscreen
780 stylesheet.attr('disabled', 'disabled');
781 $('#nav-swap .fullscreen').addClass('disabled');
782 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
783 enabled = false;
784 }
smain@google.com6bdcb982014-11-14 11:53:07 -0800785 writeCookie("fullscreen", enabled, null);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700786 setNavBarLeftPos();
787 resizeNav(delay);
788 updateSideNavPosition();
789 setTimeout(initSidenavHeightResize,delay);
790}
791
792
793function setNavBarLeftPos() {
794 navBarLeftPos = $('#body-content').offset().left;
795}
796
797
798function updateSideNavPosition() {
799 var newLeft = $(window).scrollLeft() - navBarLeftPos;
800 $('#devdoc-nav').css({left: -newLeft});
Dirk Dougherty29e93432015-05-05 18:17:13 -0700801 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('padding-left')))});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700802}
Scott Main3b90aff2013-08-01 18:09:35 -0700803
Scott Maine4d8f1b2012-06-21 18:03:05 -0700804// TODO: use $(document).ready instead
805function addLoadEvent(newfun) {
806 var current = window.onload;
807 if (typeof window.onload != 'function') {
808 window.onload = newfun;
809 } else {
810 window.onload = function() {
811 current();
812 newfun();
813 }
814 }
815}
816
817var agent = navigator['userAgent'].toLowerCase();
818// If a mobile phone, set flag and do mobile setup
819if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
820 (agent.indexOf("blackberry") != -1) ||
821 (agent.indexOf("webos") != -1) ||
822 (agent.indexOf("mini") != -1)) { // opera mini browsers
823 isMobile = true;
824}
825
826
Scott Main498d7102013-08-21 15:47:38 -0700827$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700828 $("pre:not(.no-pretty-print)").addClass("prettyprint");
829 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700830});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700831
Scott Maine4d8f1b2012-06-21 18:03:05 -0700832
833
834
835/* ######### RESIZE THE SIDENAV HEIGHT ########## */
836
837function resizeNav(delay) {
838 var $nav = $("#devdoc-nav");
839 var $window = $(window);
840 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700841
Scott Maine4d8f1b2012-06-21 18:03:05 -0700842 // Get the height of entire window and the total header height.
843 // Then figure out based on scroll position whether the header is visible
844 var windowHeight = $window.height();
845 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700846 var headerHeight = $('#header-wrapper').outerHeight();
847 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700848
849 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700850 // Could be either margin or top position, depending on whether the nav is fixed.
Scott Main3b90aff2013-08-01 18:09:35 -0700851 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700852 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700853
Scott Maine4d8f1b2012-06-21 18:03:05 -0700854 // Depending on whether the header is visible, set the side nav's height.
855 if (headerVisible) {
856 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700857 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700858 } else {
859 // Once header is off screen, the nav height is almost full window height
860 navHeight = windowHeight - topMargin;
861 }
Scott Main3b90aff2013-08-01 18:09:35 -0700862
863
864
Scott Maine4d8f1b2012-06-21 18:03:05 -0700865 $scrollPanes = $(".scroll-pane");
866 if ($scrollPanes.length > 1) {
867 // subtract the height of the api level widget and nav swapper from the available nav height
868 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700869
Scott Maine4d8f1b2012-06-21 18:03:05 -0700870 $("#swapper").css({height:navHeight + "px"});
871 if ($("#nav-tree").is(":visible")) {
872 $("#nav-tree").css({height:navHeight});
873 }
Scott Main3b90aff2013-08-01 18:09:35 -0700874
875 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700876 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700877
878 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700879 // then the package panel should begin to shrink
880 if (parseInt(classesHeight) <= 0) {
881 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
882 $("#packages-nav").css({height:navHeight - 10});
883 }
Scott Main3b90aff2013-08-01 18:09:35 -0700884
Scott Maine4d8f1b2012-06-21 18:03:05 -0700885 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
886 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700887
888
Scott Maine4d8f1b2012-06-21 18:03:05 -0700889 } else {
890 $nav.height(navHeight);
891 }
Scott Main3b90aff2013-08-01 18:09:35 -0700892
Scott Maine4d8f1b2012-06-21 18:03:05 -0700893 if (delay) {
894 updateFromResize = true;
895 delayedReInitScrollbars(delay);
896 } else {
897 reInitScrollbars();
898 }
Scott Main3b90aff2013-08-01 18:09:35 -0700899
Scott Maine4d8f1b2012-06-21 18:03:05 -0700900}
901
902var updateScrollbars = false;
903var updateFromResize = false;
904
905/* Re-initialize the scrollbars to account for changed nav size.
906 * This method postpones the actual update by a 1/4 second in order to optimize the
907 * scroll performance while the header is still visible, because re-initializing the
908 * scroll panes is an intensive process.
909 */
910function delayedReInitScrollbars(delay) {
911 // If we're scheduled for an update, but have received another resize request
912 // before the scheduled resize has occured, just ignore the new request
913 // (and wait for the scheduled one).
914 if (updateScrollbars && updateFromResize) {
915 updateFromResize = false;
916 return;
917 }
Scott Main3b90aff2013-08-01 18:09:35 -0700918
Scott Maine4d8f1b2012-06-21 18:03:05 -0700919 // We're scheduled for an update and the update request came from this method's setTimeout
920 if (updateScrollbars && !updateFromResize) {
921 reInitScrollbars();
922 updateScrollbars = false;
923 } else {
924 updateScrollbars = true;
925 updateFromResize = false;
926 setTimeout('delayedReInitScrollbars()',delay);
927 }
928}
929
930/* Re-initialize the scrollbars to account for changed nav size. */
931function reInitScrollbars() {
932 var pane = $(".scroll-pane").each(function(){
933 var api = $(this).data('jsp');
934 if (!api) { setTimeout(reInitScrollbars,300); return;}
935 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700936 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700937 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
938}
939
940
941/* Resize the height of the nav panels in the reference,
942 * and save the new size to a cookie */
943function saveNavPanels() {
944 var basePath = getBaseUri(location.pathname);
945 var section = basePath.substring(1,basePath.indexOf("/",1));
smain@google.com6bdcb982014-11-14 11:53:07 -0800946 writeCookie("height", resizePackagesNav.css("height"), section);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700947}
948
949
950
951function restoreHeight(packageHeight) {
952 $("#resize-packages-nav").height(packageHeight);
953 $("#packages-nav").height(packageHeight);
954 // var classesHeight = navHeight - packageHeight;
955 // $("#classes-nav").css({height:classesHeight});
956 // $("#classes-nav .jspContainer").css({height:classesHeight});
957}
958
959
960
961/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
962
963
964
965
966
Scott Main3b90aff2013-08-01 18:09:35 -0700967/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700968 This is called when the page finished loading. */
969function scrollIntoView(nav) {
970 var $nav = $("#"+nav);
971 var element = $nav.jScrollPane({/* ...settings... */});
972 var api = element.data('jsp');
973
974 if ($nav.is(':visible')) {
975 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700976 if ($selected.length == 0) {
977 // If no selected item found, exit
978 return;
979 }
Scott Main52dd2062013-08-15 12:22:28 -0700980 // get the selected item's offset from its container nav by measuring the item's offset
981 // relative to the document then subtract the container nav's offset relative to the document
982 var selectedOffset = $selected.offset().top - $nav.offset().top;
983 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
984 // if it's more than 80% down the nav
985 // scroll the item up by an amount equal to 80% the container nav's height
986 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700987 }
988 }
989}
990
991
992
993
994
995
996/* Show popup dialogs */
997function showDialog(id) {
998 $dialog = $("#"+id);
999 $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>');
1000 $dialog.wrapInner('<div/>');
1001 $dialog.removeClass("hide");
1002}
1003
1004
1005
1006
1007
1008/* ######### COOKIES! ########## */
1009
1010function readCookie(cookie) {
1011 var myCookie = cookie_namespace+"_"+cookie+"=";
1012 if (document.cookie) {
1013 var index = document.cookie.indexOf(myCookie);
1014 if (index != -1) {
1015 var valStart = index + myCookie.length;
1016 var valEnd = document.cookie.indexOf(";", valStart);
1017 if (valEnd == -1) {
1018 valEnd = document.cookie.length;
1019 }
1020 var val = document.cookie.substring(valStart, valEnd);
1021 return val;
1022 }
1023 }
1024 return 0;
1025}
1026
smain@google.com6bdcb982014-11-14 11:53:07 -08001027function writeCookie(cookie, val, section) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001028 if (val==undefined) return;
1029 section = section == null ? "_" : "_"+section+"_";
smain@google.com6bdcb982014-11-14 11:53:07 -08001030 var age = 2*365*24*60*60; // set max-age to 2 years
Scott Main3b90aff2013-08-01 18:09:35 -07001031 var cookieValue = cookie_namespace + section + cookie + "=" + val
smain@google.com80e38f42014-11-03 10:47:12 -08001032 + "; max-age=" + age +"; path=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -07001033 document.cookie = cookieValue;
1034}
1035
1036/* ######### END COOKIES! ########## */
1037
1038
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001039var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -07001040var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001041var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -07001042/* Sets the vertical scoll position at which the sticky bar should appear.
1043 This method is called to reset the position when search results appear or hide */
1044function setStickyTop() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001045 stickyTop = $('#header-wrapper').outerHeight() - $('#header > .dac-header-inner').outerHeight();
Dirk Doughertyc3921652014-05-13 16:55:26 -07001046}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001047
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001048/*
Scott Mainb16376f2014-05-21 20:35:47 -07001049 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -07001050 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001051$(window).scroll(function(event) {
1052
1053 setStickyTop();
1054 var hiding = false;
Dirk Dougherty29e93432015-05-05 18:17:13 -07001055 var $headerEl = $('#header');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001056 // Exit if there's no sidenav
1057 if ($('#side-nav').length == 0) return;
1058 // Exit if the mouse target is a DIV, because that means the event is coming
1059 // from a scrollable div and so there's no need to make adjustments to our layout
1060 if ($(event.target).nodeName == "DIV") {
1061 return;
1062 }
1063
1064 var top = $(window).scrollTop();
1065 // we set the navbar fixed when the scroll position is beyond the height of the site header...
1066 var shouldBeSticky = top >= stickyTop;
1067 // ... except if the document content is shorter than the sidenav height.
1068 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1069 if ($("#doc-col").height() < $("#side-nav").height()) {
1070 shouldBeSticky = false;
1071 }
Scott Mainf5257812014-05-22 17:26:38 -07001072 // Account for horizontal scroll
1073 var scrollLeft = $(window).scrollLeft();
1074 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1075 if (sticky && (scrollLeft != prevScrollLeft)) {
1076 updateSideNavPosition();
1077 prevScrollLeft = scrollLeft;
1078 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001079
1080 // Don't continue if the header is sufficently far away
1081 // (to avoid intensive resizing that slows scrolling)
1082 if (sticky == shouldBeSticky) {
1083 return;
1084 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001085
1086 // If sticky header visible and position is now near top, hide sticky
1087 if (sticky && !shouldBeSticky) {
1088 sticky = false;
1089 hiding = true;
1090 // make the sidenav static again
1091 $('#devdoc-nav')
1092 .removeClass('fixed')
1093 .css({'width':'auto','margin':''})
1094 .prependTo('#side-nav');
1095 // delay hide the sticky
Dirk Dougherty29e93432015-05-05 18:17:13 -07001096 $headerEl.removeClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001097 hiding = false;
1098
1099 // update the sidenaav position for side scrolling
1100 updateSideNavPosition();
1101 } else if (!sticky && shouldBeSticky) {
1102 sticky = true;
Dirk Dougherty29e93432015-05-05 18:17:13 -07001103 $headerEl.addClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001104
1105 // make the sidenav fixed
1106 var width = $('#devdoc-nav').width();
1107 $('#devdoc-nav')
1108 .addClass('fixed')
1109 .css({'width':width+'px'})
1110 .prependTo('#body-content');
1111
1112 // update the sidenaav position for side scrolling
1113 updateSideNavPosition();
1114
1115 } else if (hiding && top < 15) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001116 $headerEl.removeClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001117 hiding = false;
1118 }
1119 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
1120});
1121
1122/*
1123 * Manages secion card states and nav resize to conclude loading
1124 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001125(function() {
1126 $(document).ready(function() {
1127
Dirk Doughertyc3921652014-05-13 16:55:26 -07001128 // Stack hover states
1129 $('.section-card-menu').each(function(index, el) {
1130 var height = $(el).height();
1131 $(el).css({height:height+'px', position:'relative'});
1132 var $cardInfo = $(el).find('.card-info');
1133
1134 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1135 });
1136
Dirk Doughertyc3921652014-05-13 16:55:26 -07001137 });
1138
1139})();
1140
Scott Maine4d8f1b2012-06-21 18:03:05 -07001141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
Scott Maind7026f72013-06-17 15:08:49 -07001154/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001155
1156
1157
1158
1159
1160function toggle(obj, slide) {
1161 var ul = $("ul:first", obj);
1162 var li = ul.parent();
1163 if (li.hasClass("closed")) {
1164 if (slide) {
1165 ul.slideDown("fast");
1166 } else {
1167 ul.show();
1168 }
1169 li.removeClass("closed");
1170 li.addClass("open");
1171 $(".toggle-img", li).attr("title", "hide pages");
1172 } else {
1173 ul.slideUp("fast");
1174 li.removeClass("open");
1175 li.addClass("closed");
1176 $(".toggle-img", li).attr("title", "show pages");
1177 }
1178}
1179
1180
Scott Maine4d8f1b2012-06-21 18:03:05 -07001181function buildToggleLists() {
1182 $(".toggle-list").each(
1183 function(i) {
1184 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1185 $(this).addClass("closed");
1186 });
1187}
1188
1189
1190
Scott Maind7026f72013-06-17 15:08:49 -07001191function hideNestedItems(list, toggle) {
1192 $list = $(list);
1193 // hide nested lists
1194 if($list.hasClass('showing')) {
1195 $("li ol", $list).hide('fast');
1196 $list.removeClass('showing');
1197 // show nested lists
1198 } else {
1199 $("li ol", $list).show('fast');
1200 $list.addClass('showing');
1201 }
1202 $(".more,.less",$(toggle)).toggle();
1203}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001204
1205
smain@google.com95948b82014-06-16 19:24:25 -07001206/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1207function setupIdeDocToggle() {
1208 $( "select.ide" ).change(function() {
1209 var selected = $(this).find("option:selected").attr("value");
1210 $(".select-ide").hide();
1211 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001212
smain@google.com95948b82014-06-16 19:24:25 -07001213 $("select.ide").val(selected);
1214 });
1215}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240/* REFERENCE NAV SWAP */
1241
1242
1243function getNavPref() {
1244 var v = readCookie('reference_nav');
1245 if (v != NAV_PREF_TREE) {
1246 v = NAV_PREF_PANELS;
1247 }
1248 return v;
1249}
1250
1251function chooseDefaultNav() {
1252 nav_pref = getNavPref();
1253 if (nav_pref == NAV_PREF_TREE) {
1254 $("#nav-panels").toggle();
1255 $("#panel-link").toggle();
1256 $("#nav-tree").toggle();
1257 $("#tree-link").toggle();
1258 }
1259}
1260
1261function swapNav() {
1262 if (nav_pref == NAV_PREF_TREE) {
1263 nav_pref = NAV_PREF_PANELS;
1264 } else {
1265 nav_pref = NAV_PREF_TREE;
1266 init_default_navtree(toRoot);
1267 }
smain@google.com6bdcb982014-11-14 11:53:07 -08001268 writeCookie("nav", nav_pref, "reference");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001269
1270 $("#nav-panels").toggle();
1271 $("#panel-link").toggle();
1272 $("#nav-tree").toggle();
1273 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001274
Scott Maine4d8f1b2012-06-21 18:03:05 -07001275 resizeNav();
1276
1277 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1278 $("#nav-tree .jspContainer:visible")
1279 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1280 // Another nasty hack to make the scrollbar appear now that we have height
1281 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001282
Scott Maine4d8f1b2012-06-21 18:03:05 -07001283 if ($("#nav-tree").is(':visible')) {
1284 scrollIntoView("nav-tree");
1285 } else {
1286 scrollIntoView("packages-nav");
1287 scrollIntoView("classes-nav");
1288 }
1289}
1290
1291
1292
Scott Mainf5089842012-08-14 16:31:07 -07001293/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001294/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001295/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001296
1297function getBaseUri(uri) {
1298 var intlUrl = (uri.substring(0,6) == "/intl/");
1299 if (intlUrl) {
1300 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1301 base = base.substring(base.indexOf('/')+1, base.length);
1302 //alert("intl, returning base url: /" + base);
1303 return ("/" + base);
1304 } else {
1305 //alert("not intl, returning uri as found.");
1306 return uri;
1307 }
1308}
1309
1310function requestAppendHL(uri) {
1311//append "?hl=<lang> to an outgoing request (such as to blog)
1312 var lang = getLangPref();
1313 if (lang) {
1314 var q = 'hl=' + lang;
1315 uri += '?' + q;
1316 window.location = uri;
1317 return false;
1318 } else {
1319 return true;
1320 }
1321}
1322
1323
Scott Maine4d8f1b2012-06-21 18:03:05 -07001324function changeNavLang(lang) {
Scott Main6eb95f12012-10-02 17:12:23 -07001325 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1326 $links.each(function(i){ // for each link with a translation
1327 var $link = $(this);
1328 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1329 // put the desired language from the attribute as the text
1330 $link.text($link.attr(lang+"-lang"))
Scott Maine4d8f1b2012-06-21 18:03:05 -07001331 }
Scott Main6eb95f12012-10-02 17:12:23 -07001332 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001333}
1334
Scott Main015d6162013-01-29 09:01:52 -08001335function changeLangPref(lang, submit) {
smain@google.com6bdcb982014-11-14 11:53:07 -08001336 writeCookie("pref_lang", lang, null);
Scott Main015d6162013-01-29 09:01:52 -08001337
1338 // ####### TODO: Remove this condition once we're stable on devsite #######
1339 // This condition is only needed if we still need to support legacy GAE server
1340 if (devsite) {
1341 // Switch language when on Devsite server
1342 if (submit) {
1343 $("#setlang").submit();
1344 }
1345 } else {
1346 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001347 if (submit) {
1348 window.location = getBaseUri(location.pathname);
1349 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001350 }
1351}
1352
1353function loadLangPref() {
1354 var lang = readCookie("pref_lang");
1355 if (lang != 0) {
1356 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1357 }
1358}
1359
1360function getLangPref() {
1361 var lang = $("#language").find(":selected").attr("value");
1362 if (!lang) {
1363 lang = readCookie("pref_lang");
1364 }
1365 return (lang != 0) ? lang : 'en';
1366}
1367
1368/* ########## END LOCALIZATION ############ */
1369
1370
1371
1372
1373
1374
1375/* Used to hide and reveal supplemental content, such as long code samples.
1376 See the companion CSS in android-developer-docs.css */
1377function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001378 var div = $(obj).closest(".toggle-content");
1379 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001380 if (div.hasClass("closed")) { // if it's closed, open it
1381 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001382 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001383 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001384 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001385 + "assets/images/triangle-opened.png");
1386 } else { // if it's open, close it
1387 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001388 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001389 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001390 div.find(".toggle-content").removeClass("open").addClass("closed")
1391 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001392 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001393 + "assets/images/triangle-closed.png");
1394 });
1395 }
1396 return false;
1397}
Scott Mainf5089842012-08-14 16:31:07 -07001398
1399
Scott Maindb3678b2012-10-23 14:13:41 -07001400/* New version of expandable content */
1401function toggleExpandable(link,id) {
1402 if($(id).is(':visible')) {
1403 $(id).slideUp();
1404 $(link).removeClass('expanded');
1405 } else {
1406 $(id).slideDown();
1407 $(link).addClass('expanded');
1408 }
1409}
1410
1411function hideExpandable(ids) {
1412 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001413 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001414}
1415
Scott Mainf5089842012-08-14 16:31:07 -07001416
1417
1418
1419
Scott Main3b90aff2013-08-01 18:09:35 -07001420/*
Scott Mainf5089842012-08-14 16:31:07 -07001421 * Slideshow 1.0
1422 * Used on /index.html and /develop/index.html for carousel
1423 *
1424 * Sample usage:
1425 * HTML -
1426 * <div class="slideshow-container">
1427 * <a href="" class="slideshow-prev">Prev</a>
1428 * <a href="" class="slideshow-next">Next</a>
1429 * <ul>
1430 * <li class="item"><img src="images/marquee1.jpg"></li>
1431 * <li class="item"><img src="images/marquee2.jpg"></li>
1432 * <li class="item"><img src="images/marquee3.jpg"></li>
1433 * <li class="item"><img src="images/marquee4.jpg"></li>
1434 * </ul>
1435 * </div>
1436 *
1437 * <script type="text/javascript">
1438 * $('.slideshow-container').dacSlideshow({
1439 * auto: true,
1440 * btnPrev: '.slideshow-prev',
1441 * btnNext: '.slideshow-next'
1442 * });
1443 * </script>
1444 *
1445 * Options:
1446 * btnPrev: optional identifier for previous button
1447 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001448 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001449 * auto: whether or not to auto-proceed
1450 * speed: animation speed
1451 * autoTime: time between auto-rotation
1452 * easing: easing function for transition
1453 * start: item to select by default
1454 * scroll: direction to scroll in
1455 * pagination: whether or not to include dotted pagination
1456 *
1457 */
1458
1459 (function($) {
1460 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001461
Scott Mainf5089842012-08-14 16:31:07 -07001462 //Options - see above
1463 o = $.extend({
1464 btnPrev: null,
1465 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001466 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001467 auto: true,
1468 speed: 500,
1469 autoTime: 12000,
1470 easing: null,
1471 start: 0,
1472 scroll: 1,
1473 pagination: true
1474
1475 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001476
1477 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001478 return this.each(function() {
1479
1480 var running = false;
1481 var animCss = o.vertical ? "top" : "left";
1482 var sizeCss = o.vertical ? "height" : "width";
1483 var div = $(this);
1484 var ul = $("ul", div);
1485 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001486 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001487 var timer = null;
1488
1489 var li = $("li", ul);
1490 var itemLength = li.size();
1491 var curr = o.start;
1492
1493 li.css({float: o.vertical ? "none" : "left"});
1494 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1495 div.css({position: "relative", "z-index": "2", left: "0px"});
1496
1497 var liSize = o.vertical ? height(li) : width(li);
1498 var ulSize = liSize * itemLength;
1499 var divSize = liSize;
1500
1501 li.css({width: li.width(), height: li.height()});
1502 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1503
1504 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001505
Scott Mainf5089842012-08-14 16:31:07 -07001506 //Pagination
1507 if (o.pagination) {
1508 var pagination = $("<div class='pagination'></div>");
1509 var pag_ul = $("<ul></ul>");
1510 if (tl > 1) {
1511 for (var i=0;i<tl;i++) {
1512 var li = $("<li>"+i+"</li>");
1513 pag_ul.append(li);
1514 if (i==o.start) li.addClass('active');
1515 li.click(function() {
1516 go(parseInt($(this).text()));
1517 })
1518 }
1519 pagination.append(pag_ul);
1520 div.append(pagination);
1521 }
1522 }
Scott Main3b90aff2013-08-01 18:09:35 -07001523
Scott Mainf5089842012-08-14 16:31:07 -07001524 //Previous button
1525 if(o.btnPrev)
1526 $(o.btnPrev).click(function(e) {
1527 e.preventDefault();
1528 return go(curr-o.scroll);
1529 });
1530
1531 //Next button
1532 if(o.btnNext)
1533 $(o.btnNext).click(function(e) {
1534 e.preventDefault();
1535 return go(curr+o.scroll);
1536 });
Scott Maineb410352013-01-14 19:03:40 -08001537
1538 //Pause button
1539 if(o.btnPause)
1540 $(o.btnPause).click(function(e) {
1541 e.preventDefault();
1542 if ($(this).hasClass('paused')) {
1543 startRotateTimer();
1544 } else {
1545 pauseRotateTimer();
1546 }
1547 });
Scott Main3b90aff2013-08-01 18:09:35 -07001548
Scott Mainf5089842012-08-14 16:31:07 -07001549 //Auto rotation
1550 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001551
Scott Mainf5089842012-08-14 16:31:07 -07001552 function startRotateTimer() {
1553 clearInterval(timer);
1554 timer = setInterval(function() {
1555 if (curr == tl-1) {
1556 go(0);
1557 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001558 go(curr+o.scroll);
1559 }
Scott Mainf5089842012-08-14 16:31:07 -07001560 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001561 $(o.btnPause).removeClass('paused');
1562 }
1563
1564 function pauseRotateTimer() {
1565 clearInterval(timer);
1566 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001567 }
1568
1569 //Go to an item
1570 function go(to) {
1571 if(!running) {
1572
1573 if(to<0) {
1574 to = itemLength-1;
1575 } else if (to>itemLength-1) {
1576 to = 0;
1577 }
1578 curr = to;
1579
1580 running = true;
1581
1582 ul.animate(
1583 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1584 function() {
1585 running = false;
1586 }
1587 );
1588
1589 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1590 $( (curr-o.scroll<0 && o.btnPrev)
1591 ||
1592 (curr+o.scroll > itemLength && o.btnNext)
1593 ||
1594 []
1595 ).addClass("disabled");
1596
Scott Main3b90aff2013-08-01 18:09:35 -07001597
Scott Mainf5089842012-08-14 16:31:07 -07001598 var nav_items = $('li', pagination);
1599 nav_items.removeClass('active');
1600 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001601
Scott Mainf5089842012-08-14 16:31:07 -07001602
1603 }
1604 if(o.auto) startRotateTimer();
1605 return false;
1606 };
1607 });
1608 };
1609
1610 function css(el, prop) {
1611 return parseInt($.css(el[0], prop)) || 0;
1612 };
1613 function width(el) {
1614 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1615 };
1616 function height(el) {
1617 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1618 };
1619
1620 })(jQuery);
1621
1622
Scott Main3b90aff2013-08-01 18:09:35 -07001623/*
Scott Mainf5089842012-08-14 16:31:07 -07001624 * dacSlideshow 1.0
1625 * Used on develop/index.html for side-sliding tabs
1626 *
1627 * Sample usage:
1628 * HTML -
1629 * <div class="slideshow-container">
1630 * <a href="" class="slideshow-prev">Prev</a>
1631 * <a href="" class="slideshow-next">Next</a>
1632 * <ul>
1633 * <li class="item"><img src="images/marquee1.jpg"></li>
1634 * <li class="item"><img src="images/marquee2.jpg"></li>
1635 * <li class="item"><img src="images/marquee3.jpg"></li>
1636 * <li class="item"><img src="images/marquee4.jpg"></li>
1637 * </ul>
1638 * </div>
1639 *
1640 * <script type="text/javascript">
1641 * $('.slideshow-container').dacSlideshow({
1642 * auto: true,
1643 * btnPrev: '.slideshow-prev',
1644 * btnNext: '.slideshow-next'
1645 * });
1646 * </script>
1647 *
1648 * Options:
1649 * btnPrev: optional identifier for previous button
1650 * btnNext: optional identifier for next button
1651 * auto: whether or not to auto-proceed
1652 * speed: animation speed
1653 * autoTime: time between auto-rotation
1654 * easing: easing function for transition
1655 * start: item to select by default
1656 * scroll: direction to scroll in
1657 * pagination: whether or not to include dotted pagination
1658 *
1659 */
1660 (function($) {
1661 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001662
Scott Mainf5089842012-08-14 16:31:07 -07001663 //Options - see above
1664 o = $.extend({
1665 speed : 250,
1666 easing: null,
1667 nav_id: null,
1668 frame_id: null
1669 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001670
1671 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001672 return this.each(function() {
1673
1674 var curr = 0;
1675 var running = false;
1676 var animCss = "margin-left";
1677 var sizeCss = "width";
1678 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001679
Scott Mainf5089842012-08-14 16:31:07 -07001680 var nav = $(o.nav_id, div);
1681 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001682 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001683 var frame = div.find(o.frame_id);
1684 var content_width = $(frame).find('ul').width();
1685 //Buttons
1686 $(nav_li).click(function(e) {
1687 go($(nav_li).index($(this)));
1688 })
Scott Main3b90aff2013-08-01 18:09:35 -07001689
Scott Mainf5089842012-08-14 16:31:07 -07001690 //Go to an item
1691 function go(to) {
1692 if(!running) {
1693 curr = to;
1694 running = true;
1695
1696 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1697 function() {
1698 running = false;
1699 }
1700 );
1701
Scott Main3b90aff2013-08-01 18:09:35 -07001702
Scott Mainf5089842012-08-14 16:31:07 -07001703 nav_li.removeClass('active');
1704 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001705
Scott Mainf5089842012-08-14 16:31:07 -07001706
1707 }
1708 return false;
1709 };
1710 });
1711 };
1712
1713 function css(el, prop) {
1714 return parseInt($.css(el[0], prop)) || 0;
1715 };
1716 function width(el) {
1717 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1718 };
1719 function height(el) {
1720 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1721 };
1722
1723 })(jQuery);
1724
1725
1726
1727
1728
1729/* ######################################################## */
1730/* ################ SEARCH SUGGESTIONS ################## */
1731/* ######################################################## */
1732
1733
Scott Main7e447ed2013-02-19 17:22:37 -08001734
Scott Main0e76e7e2013-03-12 10:24:07 -07001735var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1736var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1737
Scott Mainf5089842012-08-14 16:31:07 -07001738var gMatches = new Array();
1739var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001740var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001741var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1742var gListLength = 0;
1743
1744
1745var gGoogleMatches = new Array();
1746var ROW_COUNT_GOOGLE = 15; // max number of results in list
1747var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001748
Scott Main0e76e7e2013-03-12 10:24:07 -07001749var gDocsMatches = new Array();
1750var ROW_COUNT_DOCS = 100; // max number of results in list
1751var gDocsListLength = 0;
1752
Scott Mainde295272013-03-25 15:48:35 -07001753function onSuggestionClick(link) {
1754 // When user clicks a suggested document, track it
smain@google.comed677d72014-12-12 10:19:17 -08001755 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
1756 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07001757}
1758
Scott Mainf5089842012-08-14 16:31:07 -07001759function set_item_selected($li, selected)
1760{
1761 if (selected) {
1762 $li.attr('class','jd-autocomplete jd-selected');
1763 } else {
1764 $li.attr('class','jd-autocomplete');
1765 }
1766}
1767
1768function set_item_values(toroot, $li, match)
1769{
1770 var $link = $('a',$li);
1771 $link.html(match.__hilabel || match.label);
1772 $link.attr('href',toroot + match.link);
1773}
1774
Scott Main719acb42013-12-05 16:05:09 -08001775function set_item_values_jd(toroot, $li, match)
1776{
1777 var $link = $('a',$li);
1778 $link.html(match.title);
1779 $link.attr('href',toroot + match.url);
1780}
1781
Scott Main0e76e7e2013-03-12 10:24:07 -07001782function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001783 var $li = $("<li class='jd-autocomplete'></li>");
1784 $list.append($li);
1785
1786 $li.mousedown(function() {
1787 window.location = this.firstChild.getAttribute("href");
1788 });
1789 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001790 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001791 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001792 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1793 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001794 });
Scott Mainde295272013-03-25 15:48:35 -07001795 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001796 $li.attr('class','show-item');
1797 return $li;
1798}
1799
Scott Mainf5089842012-08-14 16:31:07 -07001800function sync_selection_table(toroot)
1801{
Scott Mainf5089842012-08-14 16:31:07 -07001802 var $li; //list item jquery object
1803 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001804
Scott Main0e76e7e2013-03-12 10:24:07 -07001805 // if there are NO results at all, hide all columns
1806 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1807 $('.suggest-card').hide(300);
1808 return;
1809 }
1810
1811 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001812 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001813 // reveal suggestion list
Scott Main0e76e7e2013-03-12 10:24:07 -07001814 $('.suggest-card.reference').show();
1815 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001816
Scott Main0e76e7e2013-03-12 10:24:07 -07001817 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001818 $(".suggest-card.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001819
Scott Main0e76e7e2013-03-12 10:24:07 -07001820 // ########### ANDROID RESULTS #############
1821 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001822
Scott Main0e76e7e2013-03-12 10:24:07 -07001823 // determine android results to show
1824 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1825 gMatches.length : ROW_COUNT_FRAMEWORK;
1826 for (i=0; i<gListLength; i++) {
1827 var $li = new_suggestion($(".suggest-card.reference ul"));
1828 set_item_values(toroot, $li, gMatches[i]);
1829 set_item_selected($li, i == gSelectedIndex);
1830 }
1831 }
Scott Main7e447ed2013-02-19 17:22:37 -08001832
Scott Main0e76e7e2013-03-12 10:24:07 -07001833 // ########### GOOGLE RESULTS #############
1834 if (gGoogleMatches.length > 0) {
1835 // show header for list
1836 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001837
Scott Main0e76e7e2013-03-12 10:24:07 -07001838 // determine google results to show
1839 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1840 for (i=0; i<gGoogleListLength; i++) {
1841 var $li = new_suggestion($(".suggest-card.reference ul"));
1842 set_item_values(toroot, $li, gGoogleMatches[i]);
1843 set_item_selected($li, i == gSelectedIndex);
1844 }
1845 }
Scott Mainf5089842012-08-14 16:31:07 -07001846 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001847 $('.suggest-card.reference').hide();
Scott Main0e76e7e2013-03-12 10:24:07 -07001848 }
1849
1850 // ########### JD DOC RESULTS #############
1851 if (gDocsMatches.length > 0) {
1852 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001853 $(".suggest-card:not(.reference) li").remove();
Scott Main0e76e7e2013-03-12 10:24:07 -07001854
1855 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001856 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1857 // The order must match the reverse order that each section appears as a card in
1858 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001859 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1860 for (i=0; i<gDocsListLength; i++) {
1861 var sugg = gDocsMatches[i];
1862 var $li;
1863 if (sugg.type == "design") {
1864 $li = new_suggestion($(".suggest-card.design ul"));
1865 } else
1866 if (sugg.type == "distribute") {
1867 $li = new_suggestion($(".suggest-card.distribute ul"));
1868 } else
Scott Main719acb42013-12-05 16:05:09 -08001869 if (sugg.type == "samples") {
1870 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1871 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001872 if (sugg.type == "training") {
1873 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1874 } else
Scott Main719acb42013-12-05 16:05:09 -08001875 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001876 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1877 } else {
1878 continue;
1879 }
1880
Scott Main719acb42013-12-05 16:05:09 -08001881 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001882 set_item_selected($li, i == gSelectedIndex);
1883 }
1884
1885 // add heading and show or hide card
1886 if ($(".suggest-card.design li").length > 0) {
1887 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1888 $(".suggest-card.design").show(300);
1889 } else {
1890 $('.suggest-card.design').hide(300);
1891 }
1892 if ($(".suggest-card.distribute li").length > 0) {
1893 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1894 $(".suggest-card.distribute").show(300);
1895 } else {
1896 $('.suggest-card.distribute').hide(300);
1897 }
1898 if ($(".child-card.guides li").length > 0) {
1899 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1900 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1901 }
1902 if ($(".child-card.training li").length > 0) {
1903 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1904 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1905 }
Scott Main719acb42013-12-05 16:05:09 -08001906 if ($(".child-card.samples li").length > 0) {
1907 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1908 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1909 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001910
1911 if ($(".suggest-card.develop li").length > 0) {
1912 $(".suggest-card.develop").show(300);
1913 } else {
1914 $('.suggest-card.develop').hide(300);
1915 }
1916
1917 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001918 $('.suggest-card:not(.reference)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001919 }
1920}
1921
Scott Main0e76e7e2013-03-12 10:24:07 -07001922/** Called by the search input's onkeydown and onkeyup events.
1923 * Handles navigation with keyboard arrows, Enter key to invoke search,
1924 * otherwise invokes search suggestions on key-up event.
1925 * @param e The JS event
1926 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001927 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001928 * @returns True if the event should bubble up
1929 */
Scott Mainf5089842012-08-14 16:31:07 -07001930function search_changed(e, kd, toroot)
1931{
Scott Main719acb42013-12-05 16:05:09 -08001932 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001933 var search = document.getElementById("search_autocomplete");
1934 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001935 // get the ul hosting the currently selected item
1936 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1937 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1938 var $selectedUl = $columns[gSelectedColumn];
1939
Scott Mainf5089842012-08-14 16:31:07 -07001940 // show/hide the close button
1941 if (text != '') {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001942 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07001943 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001944 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07001945 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001946 // 27 = esc
1947 if (e.keyCode == 27) {
1948 // close all search results
Dirk Dougherty29e93432015-05-05 18:17:13 -07001949 if (kd) $('#search-close').trigger('click');
Scott Main0e76e7e2013-03-12 10:24:07 -07001950 return true;
1951 }
Scott Mainf5089842012-08-14 16:31:07 -07001952 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001953 else if (e.keyCode == 13) {
1954 if (gSelectedIndex < 0) {
1955 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001956 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1957 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001958 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001959 return true;
1960 } else {
1961 // otherwise, results are already showing, so allow ajax to auto refresh the results
1962 // and ignore this Enter press to avoid the reload.
1963 return false;
1964 }
1965 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001966 // click the link corresponding to selected item
1967 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001968 return false;
1969 }
1970 }
Scott Mainb16376f2014-05-21 20:35:47 -07001971 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001972 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001973 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001974 if ((sticky ) && (search.value != "")) {
1975 $('body,html').animate({scrollTop:0}, '500', 'swing');
1976 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001977 return true;
1978 }
1979 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001980 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001981 // if the next item is a header, skip it
1982 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001983 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001984 }
1985 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001986 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001987 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001988 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1989 // If user reaches top, reset selected column
1990 if (gSelectedIndex < 0) {
1991 gSelectedColumn = -1;
1992 }
Scott Mainf5089842012-08-14 16:31:07 -07001993 }
1994 return false;
1995 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001996 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001997 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001998 // if the next item is a header, skip it
1999 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07002000 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08002001 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002002 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
2003 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
2004 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08002005 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07002006 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07002007 }
2008 return false;
2009 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002010 // Consider left/right arrow navigation
2011 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
2012 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
2013 // 37 LEFT ARROW
2014 // go left only if current column is not left-most column (last column)
2015 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
2016 $('li', $selectedUl).removeClass('jd-selected');
2017 gSelectedColumn++;
2018 $selectedUl = $columns[gSelectedColumn];
2019 // keep or reset the selected item to last item as appropriate
2020 gSelectedIndex = gSelectedIndex >
2021 $("li", $selectedUl).length-1 ?
2022 $("li", $selectedUl).length-1 : gSelectedIndex;
2023 // if the corresponding item is a header, move down
2024 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2025 gSelectedIndex++;
2026 }
Scott Main3b90aff2013-08-01 18:09:35 -07002027 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002028 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2029 return false;
2030 }
2031 // 39 RIGHT ARROW
2032 // go right only if current column is not the right-most column (first column)
2033 else if (e.keyCode == 39 && gSelectedColumn > 0) {
2034 $('li', $selectedUl).removeClass('jd-selected');
2035 gSelectedColumn--;
2036 $selectedUl = $columns[gSelectedColumn];
2037 // keep or reset the selected item to last item as appropriate
2038 gSelectedIndex = gSelectedIndex >
2039 $("li", $selectedUl).length-1 ?
2040 $("li", $selectedUl).length-1 : gSelectedIndex;
2041 // if the corresponding item is a header, move down
2042 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2043 gSelectedIndex++;
2044 }
Scott Main3b90aff2013-08-01 18:09:35 -07002045 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002046 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2047 return false;
2048 }
2049 }
2050
Scott Main719acb42013-12-05 16:05:09 -08002051 // if key-up event and not arrow down/up/left/right,
2052 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07002053 else if (!kd && (e.keyCode != 40)
2054 && (e.keyCode != 38)
2055 && (e.keyCode != 37)
2056 && (e.keyCode != 39)) {
2057 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07002058 gMatches = new Array();
2059 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002060 gGoogleMatches = new Array();
2061 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07002062 gDocsMatches = new Array();
2063 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002064
2065 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07002066 for (var i=0; i<DATA.length; i++) {
2067 var s = DATA[i];
2068 if (text.length != 0 &&
2069 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2070 gMatches[matchedCount] = s;
2071 matchedCount++;
2072 }
2073 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002074 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07002075 for (var i=0; i<gMatches.length; i++) {
2076 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08002077 }
2078
2079
2080 // Search for Google matches
2081 for (var i=0; i<GOOGLE_DATA.length; i++) {
2082 var s = GOOGLE_DATA[i];
2083 if (text.length != 0 &&
2084 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2085 gGoogleMatches[matchedCountGoogle] = s;
2086 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07002087 }
2088 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002089 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08002090 for (var i=0; i<gGoogleMatches.length; i++) {
2091 var s = gGoogleMatches[i];
2092 }
2093
Scott Mainf5089842012-08-14 16:31:07 -07002094 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07002095
2096
2097
Scott Main719acb42013-12-05 16:05:09 -08002098 // Search for matching JD docs
Dirk Dougherty9b7f8f22014-11-01 17:08:56 -07002099 if (text.length >= 2) {
Scott Main719acb42013-12-05 16:05:09 -08002100 // Regex to match only the beginning of a word
2101 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
2102
2103
2104 // Search for Training classes
2105 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002106 // current search comparison, with counters for tag and title,
2107 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08002108 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07002109 s.matched_tag = 0;
2110 s.matched_title = 0;
2111 var matched = false;
2112
2113 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08002114 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002115 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08002116 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002117 matched = true;
2118 s.matched_tag = j + 1; // add 1 to index position
2119 }
2120 }
Scott Main719acb42013-12-05 16:05:09 -08002121 // Don't consider doc title for lessons (only for class landing pages),
2122 // unless the lesson has a tag that already matches
2123 if ((s.lang == currentLang) &&
2124 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002125 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08002126 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002127 matched = true;
2128 s.matched_title = 1;
2129 }
2130 }
2131 if (matched) {
2132 gDocsMatches[matchedCountDocs] = s;
2133 matchedCountDocs++;
2134 }
2135 }
Scott Main719acb42013-12-05 16:05:09 -08002136
2137
2138 // Search for API Guides
2139 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2140 // current search comparison, with counters for tag and title,
2141 // used later to improve ranking
2142 var s = GUIDE_RESOURCES[i];
2143 s.matched_tag = 0;
2144 s.matched_title = 0;
2145 var matched = false;
2146
2147 // Check if query matches any tags; work backwards toward 1 to assist ranking
2148 for (var j = s.keywords.length - 1; j >= 0; j--) {
2149 // it matches a tag
2150 if (s.keywords[j].toLowerCase().match(textRegex)) {
2151 matched = true;
2152 s.matched_tag = j + 1; // add 1 to index position
2153 }
2154 }
2155 // Check if query matches the doc title, but only for current language
2156 if (s.lang == currentLang) {
2157 // if query matches the doc title
2158 if (s.title.toLowerCase().match(textRegex)) {
2159 matched = true;
2160 s.matched_title = 1;
2161 }
2162 }
2163 if (matched) {
2164 gDocsMatches[matchedCountDocs] = s;
2165 matchedCountDocs++;
2166 }
2167 }
2168
2169
2170 // Search for Tools Guides
2171 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2172 // current search comparison, with counters for tag and title,
2173 // used later to improve ranking
2174 var s = TOOLS_RESOURCES[i];
2175 s.matched_tag = 0;
2176 s.matched_title = 0;
2177 var matched = false;
2178
2179 // Check if query matches any tags; work backwards toward 1 to assist ranking
2180 for (var j = s.keywords.length - 1; j >= 0; j--) {
2181 // it matches a tag
2182 if (s.keywords[j].toLowerCase().match(textRegex)) {
2183 matched = true;
2184 s.matched_tag = j + 1; // add 1 to index position
2185 }
2186 }
2187 // Check if query matches the doc title, but only for current language
2188 if (s.lang == currentLang) {
2189 // if query matches the doc title
2190 if (s.title.toLowerCase().match(textRegex)) {
2191 matched = true;
2192 s.matched_title = 1;
2193 }
2194 }
2195 if (matched) {
2196 gDocsMatches[matchedCountDocs] = s;
2197 matchedCountDocs++;
2198 }
2199 }
2200
2201
2202 // Search for About docs
2203 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2204 // current search comparison, with counters for tag and title,
2205 // used later to improve ranking
2206 var s = ABOUT_RESOURCES[i];
2207 s.matched_tag = 0;
2208 s.matched_title = 0;
2209 var matched = false;
2210
2211 // Check if query matches any tags; work backwards toward 1 to assist ranking
2212 for (var j = s.keywords.length - 1; j >= 0; j--) {
2213 // it matches a tag
2214 if (s.keywords[j].toLowerCase().match(textRegex)) {
2215 matched = true;
2216 s.matched_tag = j + 1; // add 1 to index position
2217 }
2218 }
2219 // Check if query matches the doc title, but only for current language
2220 if (s.lang == currentLang) {
2221 // if query matches the doc title
2222 if (s.title.toLowerCase().match(textRegex)) {
2223 matched = true;
2224 s.matched_title = 1;
2225 }
2226 }
2227 if (matched) {
2228 gDocsMatches[matchedCountDocs] = s;
2229 matchedCountDocs++;
2230 }
2231 }
2232
2233
2234 // Search for Design guides
2235 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2236 // current search comparison, with counters for tag and title,
2237 // used later to improve ranking
2238 var s = DESIGN_RESOURCES[i];
2239 s.matched_tag = 0;
2240 s.matched_title = 0;
2241 var matched = false;
2242
2243 // Check if query matches any tags; work backwards toward 1 to assist ranking
2244 for (var j = s.keywords.length - 1; j >= 0; j--) {
2245 // it matches a tag
2246 if (s.keywords[j].toLowerCase().match(textRegex)) {
2247 matched = true;
2248 s.matched_tag = j + 1; // add 1 to index position
2249 }
2250 }
2251 // Check if query matches the doc title, but only for current language
2252 if (s.lang == currentLang) {
2253 // if query matches the doc title
2254 if (s.title.toLowerCase().match(textRegex)) {
2255 matched = true;
2256 s.matched_title = 1;
2257 }
2258 }
2259 if (matched) {
2260 gDocsMatches[matchedCountDocs] = s;
2261 matchedCountDocs++;
2262 }
2263 }
2264
2265
2266 // Search for Distribute guides
2267 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2268 // current search comparison, with counters for tag and title,
2269 // used later to improve ranking
2270 var s = DISTRIBUTE_RESOURCES[i];
2271 s.matched_tag = 0;
2272 s.matched_title = 0;
2273 var matched = false;
2274
2275 // Check if query matches any tags; work backwards toward 1 to assist ranking
2276 for (var j = s.keywords.length - 1; j >= 0; j--) {
2277 // it matches a tag
2278 if (s.keywords[j].toLowerCase().match(textRegex)) {
2279 matched = true;
2280 s.matched_tag = j + 1; // add 1 to index position
2281 }
2282 }
2283 // Check if query matches the doc title, but only for current language
2284 if (s.lang == currentLang) {
2285 // if query matches the doc title
2286 if (s.title.toLowerCase().match(textRegex)) {
2287 matched = true;
2288 s.matched_title = 1;
2289 }
2290 }
2291 if (matched) {
2292 gDocsMatches[matchedCountDocs] = s;
2293 matchedCountDocs++;
2294 }
2295 }
2296
2297
2298 // Search for Google guides
2299 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2300 // current search comparison, with counters for tag and title,
2301 // used later to improve ranking
2302 var s = GOOGLE_RESOURCES[i];
2303 s.matched_tag = 0;
2304 s.matched_title = 0;
2305 var matched = false;
2306
2307 // Check if query matches any tags; work backwards toward 1 to assist ranking
2308 for (var j = s.keywords.length - 1; j >= 0; j--) {
2309 // it matches a tag
2310 if (s.keywords[j].toLowerCase().match(textRegex)) {
2311 matched = true;
2312 s.matched_tag = j + 1; // add 1 to index position
2313 }
2314 }
2315 // Check if query matches the doc title, but only for current language
2316 if (s.lang == currentLang) {
2317 // if query matches the doc title
2318 if (s.title.toLowerCase().match(textRegex)) {
2319 matched = true;
2320 s.matched_title = 1;
2321 }
2322 }
2323 if (matched) {
2324 gDocsMatches[matchedCountDocs] = s;
2325 matchedCountDocs++;
2326 }
2327 }
2328
2329
2330 // Search for Samples
2331 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2332 // current search comparison, with counters for tag and title,
2333 // used later to improve ranking
2334 var s = SAMPLES_RESOURCES[i];
2335 s.matched_tag = 0;
2336 s.matched_title = 0;
2337 var matched = false;
2338 // Check if query matches any tags; work backwards toward 1 to assist ranking
2339 for (var j = s.keywords.length - 1; j >= 0; j--) {
2340 // it matches a tag
2341 if (s.keywords[j].toLowerCase().match(textRegex)) {
2342 matched = true;
2343 s.matched_tag = j + 1; // add 1 to index position
2344 }
2345 }
2346 // Check if query matches the doc title, but only for current language
2347 if (s.lang == currentLang) {
2348 // if query matches the doc title.t
2349 if (s.title.toLowerCase().match(textRegex)) {
2350 matched = true;
2351 s.matched_title = 1;
2352 }
2353 }
2354 if (matched) {
2355 gDocsMatches[matchedCountDocs] = s;
2356 matchedCountDocs++;
2357 }
2358 }
2359
2360 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002361 rank_autocomplete_doc_results(text, gDocsMatches);
2362 }
2363
2364 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002365 sync_selection_table(toroot);
2366 return true; // allow the event to bubble up to the search api
2367 }
2368}
2369
Scott Main0e76e7e2013-03-12 10:24:07 -07002370/* Order the jd doc result list based on match quality */
2371function rank_autocomplete_doc_results(query, matches) {
2372 query = query || '';
2373 if (!matches || !matches.length)
2374 return;
2375
2376 var _resultScoreFn = function(match) {
2377 var score = 1.0;
2378
2379 // if the query matched a tag
2380 if (match.matched_tag > 0) {
2381 // multiply score by factor relative to position in tags list (max of 3)
2382 score *= 3 / match.matched_tag;
2383
2384 // if it also matched the title
2385 if (match.matched_title > 0) {
2386 score *= 2;
2387 }
2388 } else if (match.matched_title > 0) {
2389 score *= 3;
2390 }
2391
2392 return score;
2393 };
2394
2395 for (var i=0; i<matches.length; i++) {
2396 matches[i].__resultScore = _resultScoreFn(matches[i]);
2397 }
2398
2399 matches.sort(function(a,b){
2400 var n = b.__resultScore - a.__resultScore;
2401 if (n == 0) // lexicographical sort if scores are the same
2402 n = (a.label < b.label) ? -1 : 1;
2403 return n;
2404 });
2405}
2406
Scott Main7e447ed2013-02-19 17:22:37 -08002407/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002408function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002409 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002410 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002411 return;
2412
2413 // helper function that gets the last occurence index of the given regex
2414 // in the given string, or -1 if not found
2415 var _lastSearch = function(s, re) {
2416 if (s == '')
2417 return -1;
2418 var l = -1;
2419 var tmp;
2420 while ((tmp = s.search(re)) >= 0) {
2421 if (l < 0) l = 0;
2422 l += tmp;
2423 s = s.substr(tmp + 1);
2424 }
2425 return l;
2426 };
2427
2428 // helper function that counts the occurrences of a given character in
2429 // a given string
2430 var _countChar = function(s, c) {
2431 var n = 0;
2432 for (var i=0; i<s.length; i++)
2433 if (s.charAt(i) == c) ++n;
2434 return n;
2435 };
2436
2437 var queryLower = query.toLowerCase();
2438 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2439 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2440 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2441
2442 var _resultScoreFn = function(result) {
2443 // scores are calculated based on exact and prefix matches,
2444 // and then number of path separators (dots) from the last
2445 // match (i.e. favoring classes and deep package names)
2446 var score = 1.0;
2447 var labelLower = result.label.toLowerCase();
2448 var t;
2449 t = _lastSearch(labelLower, partExactAlnumRE);
2450 if (t >= 0) {
2451 // exact part match
2452 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2453 score *= 200 / (partsAfter + 1);
2454 } else {
2455 t = _lastSearch(labelLower, partPrefixAlnumRE);
2456 if (t >= 0) {
2457 // part prefix match
2458 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2459 score *= 20 / (partsAfter + 1);
2460 }
2461 }
2462
2463 return score;
2464 };
2465
Scott Main7e447ed2013-02-19 17:22:37 -08002466 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002467 // if the API is deprecated, default score is 0; otherwise, perform scoring
2468 if (matches[i].deprecated == "true") {
2469 matches[i].__resultScore = 0;
2470 } else {
2471 matches[i].__resultScore = _resultScoreFn(matches[i]);
2472 }
Scott Mainf5089842012-08-14 16:31:07 -07002473 }
2474
Scott Main7e447ed2013-02-19 17:22:37 -08002475 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002476 var n = b.__resultScore - a.__resultScore;
2477 if (n == 0) // lexicographical sort if scores are the same
2478 n = (a.label < b.label) ? -1 : 1;
2479 return n;
2480 });
2481}
2482
Scott Main7e447ed2013-02-19 17:22:37 -08002483/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002484function highlight_autocomplete_result_labels(query) {
2485 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002486 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002487 return;
2488
2489 var queryLower = query.toLowerCase();
2490 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2491 var queryRE = new RegExp(
2492 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2493 for (var i=0; i<gMatches.length; i++) {
2494 gMatches[i].__hilabel = gMatches[i].label.replace(
2495 queryRE, '<b>$1</b>');
2496 }
Scott Main7e447ed2013-02-19 17:22:37 -08002497 for (var i=0; i<gGoogleMatches.length; i++) {
2498 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2499 queryRE, '<b>$1</b>');
2500 }
Scott Mainf5089842012-08-14 16:31:07 -07002501}
2502
2503function search_focus_changed(obj, focused)
2504{
Scott Main3b90aff2013-08-01 18:09:35 -07002505 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002506 if(obj.value == ""){
Dirk Dougherty29e93432015-05-05 18:17:13 -07002507 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002508 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002509 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002510 }
2511}
2512
2513function submit_search() {
2514 var query = document.getElementById('search_autocomplete').value;
2515 location.hash = 'q=' + query;
2516 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002517 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002518 return false;
2519}
2520
2521
2522function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002523 $("#searchResults").slideUp('fast', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002524 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002525 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002526
Scott Mainf5089842012-08-14 16:31:07 -07002527 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002528
Scott Mainf5089842012-08-14 16:31:07 -07002529 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2530 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002531
2532 // forcefully regain key-up event control (previously jacked by search api)
2533 $("#search_autocomplete").keyup(function(event) {
2534 return search_changed(event, false, toRoot);
2535 });
2536
Scott Mainf5089842012-08-14 16:31:07 -07002537 return false;
2538}
2539
2540
2541
2542/* ########################################################## */
2543/* ################ CUSTOM SEARCH ENGINE ################## */
2544/* ########################################################## */
2545
Scott Mainf5089842012-08-14 16:31:07 -07002546var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002547google.load('search', '1', {"callback" : function() {
2548 searchControl = new google.search.SearchControl();
2549 } });
Scott Mainf5089842012-08-14 16:31:07 -07002550
2551function loadSearchResults() {
2552 document.getElementById("search_autocomplete").style.color = "#000";
2553
Scott Mainf5089842012-08-14 16:31:07 -07002554 searchControl = new google.search.SearchControl();
2555
2556 // use our existing search form and use tabs when multiple searchers are used
2557 drawOptions = new google.search.DrawOptions();
2558 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2559 drawOptions.setInput(document.getElementById("search_autocomplete"));
2560
2561 // configure search result options
2562 searchOptions = new google.search.SearcherOptions();
2563 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2564
2565 // configure each of the searchers, for each tab
2566 devSiteSearcher = new google.search.WebSearch();
2567 devSiteSearcher.setUserDefinedLabel("All");
2568 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2569
2570 designSearcher = new google.search.WebSearch();
2571 designSearcher.setUserDefinedLabel("Design");
2572 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2573
2574 trainingSearcher = new google.search.WebSearch();
2575 trainingSearcher.setUserDefinedLabel("Training");
2576 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2577
2578 guidesSearcher = new google.search.WebSearch();
2579 guidesSearcher.setUserDefinedLabel("Guides");
2580 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2581
2582 referenceSearcher = new google.search.WebSearch();
2583 referenceSearcher.setUserDefinedLabel("Reference");
2584 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2585
Scott Maindf08ada2012-12-03 08:54:37 -08002586 googleSearcher = new google.search.WebSearch();
2587 googleSearcher.setUserDefinedLabel("Google Services");
2588 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2589
Scott Mainf5089842012-08-14 16:31:07 -07002590 blogSearcher = new google.search.WebSearch();
2591 blogSearcher.setUserDefinedLabel("Blog");
2592 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2593
2594 // add each searcher to the search control
2595 searchControl.addSearcher(devSiteSearcher, searchOptions);
2596 searchControl.addSearcher(designSearcher, searchOptions);
2597 searchControl.addSearcher(trainingSearcher, searchOptions);
2598 searchControl.addSearcher(guidesSearcher, searchOptions);
2599 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002600 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002601 searchControl.addSearcher(blogSearcher, searchOptions);
2602
2603 // configure result options
2604 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2605 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2606 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2607 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2608
2609 // upon ajax search, refresh the url and search title
2610 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2611 updateResultTitle(query);
2612 var query = document.getElementById('search_autocomplete').value;
2613 location.hash = 'q=' + query;
2614 });
2615
Scott Mainde295272013-03-25 15:48:35 -07002616 // once search results load, set up click listeners
2617 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2618 addResultClickListeners();
2619 });
2620
Scott Mainf5089842012-08-14 16:31:07 -07002621 // draw the search results box
2622 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2623
2624 // get query and execute the search
2625 searchControl.execute(decodeURI(getQuery(location.hash)));
2626
2627 document.getElementById("search_autocomplete").focus();
2628 addTabListeners();
2629}
2630// End of loadSearchResults
2631
2632
2633google.setOnLoadCallback(function(){
2634 if (location.hash.indexOf("q=") == -1) {
2635 // if there's no query in the url, don't search and make sure results are hidden
2636 $('#searchResults').hide();
2637 return;
2638 } else {
2639 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002640 $('#searchResults').slideDown('slow', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002641 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002642 loadSearchResults();
2643 }
2644}, true);
2645
smain@google.com9a818f52014-10-03 09:25:59 -07002646/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2647 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002648function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002649 // Ignore if there's no search bar (some special pages have no header)
2650 if ($("#search-container").length < 1) return;
2651
smain@google.com3b77ab52014-06-17 11:57:27 -07002652 var hash = escape(location.hash.substr(1));
2653 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002654 // Sanity check that there's an element with that ID on the page
2655 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002656 // If the position of the target element is near the top of the page (<20px, where we expect it
2657 // to be because we need to move it down 60px to become in view), then move it down 60px
2658 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2659 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002660 }
2661 }
2662}
2663
Scott Mainf5089842012-08-14 16:31:07 -07002664// when an event on the browser history occurs (back, forward, load) requery hash and do search
2665$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002666 // Ignore if there's no search bar (some special pages have no header)
2667 if ($("#search-container").length < 1) return;
2668
Dirk Doughertyc3921652014-05-13 16:55:26 -07002669 // If the hash isn't a search query or there's an error in the query,
2670 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002671 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2672 // If the results pane is open, close it.
2673 if (!$("#searchResults").is(":hidden")) {
2674 hideResults();
2675 }
Scott Mainb16376f2014-05-21 20:35:47 -07002676 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002677 return;
2678 }
2679
2680 // Otherwise, we have a search to do
2681 var query = decodeURI(getQuery(location.hash));
2682 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002683 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002684 $("#search_autocomplete").focus();
Dirk Dougherty29e93432015-05-05 18:17:13 -07002685 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002686
2687 updateResultTitle(query);
2688});
2689
2690function updateResultTitle(query) {
2691 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2692}
2693
2694// forcefully regain key-up event control (previously jacked by search api)
2695$("#search_autocomplete").keyup(function(event) {
2696 return search_changed(event, false, toRoot);
2697});
2698
2699// add event listeners to each tab so we can track the browser history
2700function addTabListeners() {
2701 var tabHeaders = $(".gsc-tabHeader");
2702 for (var i = 0; i < tabHeaders.length; i++) {
2703 $(tabHeaders[i]).attr("id",i).click(function() {
2704 /*
2705 // make a copy of the page numbers for the search left pane
2706 setTimeout(function() {
2707 // remove any residual page numbers
2708 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002709 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002710 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002711 // and because we're going to remove it (previous line),
2712 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002713 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2714 .clone().appendTo('#searchResults .gsc-tabsArea');
2715 }, 200);
2716 */
2717 });
2718 }
2719 setTimeout(function(){$(tabHeaders[0]).click()},200);
2720}
2721
Scott Mainde295272013-03-25 15:48:35 -07002722// add analytics tracking events to each result link
2723function addResultClickListeners() {
2724 $("#searchResults a.gs-title").each(function(index, link) {
2725 // When user clicks enter for Google search results, track it
2726 $(link).click(function() {
smain@google.comed677d72014-12-12 10:19:17 -08002727 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'),
2728 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07002729 });
2730 });
2731}
2732
Scott Mainf5089842012-08-14 16:31:07 -07002733
2734function getQuery(hash) {
2735 var queryParts = hash.split('=');
2736 return queryParts[1];
2737}
2738
2739/* returns the given string with all HTML brackets converted to entities
2740 TODO: move this to the site's JS library */
2741function escapeHTML(string) {
2742 return string.replace(/</g,"&lt;")
2743 .replace(/>/g,"&gt;");
2744}
2745
2746
2747
2748
2749
2750
2751
2752/* ######################################################## */
2753/* ################# JAVADOC REFERENCE ################### */
2754/* ######################################################## */
2755
Scott Main65511c02012-09-07 15:51:32 -07002756/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002757if (location.pathname.indexOf("/reference") == 0) {
2758 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2759 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2760 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002761 $(document).ready(function() {
2762 // init available apis based on user pref
2763 changeApiLevel();
2764 initSidenavHeightResize()
2765 });
2766 }
Scott Main65511c02012-09-07 15:51:32 -07002767}
Scott Mainf5089842012-08-14 16:31:07 -07002768
2769var API_LEVEL_COOKIE = "api_level";
2770var minLevel = 1;
2771var maxLevel = 1;
2772
2773/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002774
Scott Mainf5089842012-08-14 16:31:07 -07002775 function initSidenavHeightResize() {
2776 // Change the drag bar size to nicely fit the scrollbar positions
2777 var $dragBar = $(".ui-resizable-s");
2778 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002779
2780 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002781 containment: "#nav-panels",
2782 handles: "s",
2783 alsoResize: "#packages-nav",
2784 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2785 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2786 });
Scott Main3b90aff2013-08-01 18:09:35 -07002787
Scott Mainf5089842012-08-14 16:31:07 -07002788 }
Scott Main3b90aff2013-08-01 18:09:35 -07002789
Scott Mainf5089842012-08-14 16:31:07 -07002790function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002791 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002792 $('#devdoc-nav').css({
2793 'width' : $('#side-nav').css('width'),
2794 'margin' : $('#side-nav').css('margin')
2795 });
2796 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002797
Scott Mainf5089842012-08-14 16:31:07 -07002798 initSidenavHeightResize();
2799}
2800
2801function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002802 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002803 $('#devdoc-nav').css({
2804 'width' : $('#side-nav').css('width'),
2805 'margin' : $('#side-nav').css('margin')
2806 });
2807 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002808
Scott Mainf5089842012-08-14 16:31:07 -07002809 initSidenavHeightResize();
2810}
2811
2812function buildApiLevelSelector() {
2813 maxLevel = SINCE_DATA.length;
2814 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2815 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2816
2817 minLevel = parseInt($("#doc-api-level").attr("class"));
2818 // Handle provisional api levels; the provisional level will always be the highest possible level
2819 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2820 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2821 if (isNaN(minLevel) && minLevel.length) {
2822 minLevel = maxLevel;
2823 }
2824 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2825 for (var i = maxLevel-1; i >= 0; i--) {
2826 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2827 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2828 select.append(option);
2829 }
2830
2831 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2832 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2833 selectedLevelItem.setAttribute('selected',true);
2834}
2835
2836function changeApiLevel() {
2837 maxLevel = SINCE_DATA.length;
2838 var selectedLevel = maxLevel;
2839
2840 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2841 toggleVisisbleApis(selectedLevel, "body");
2842
smain@google.com6bdcb982014-11-14 11:53:07 -08002843 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002844
2845 if (selectedLevel < minLevel) {
2846 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002847 $("#naMessage").show().html("<div><p><strong>This " + thing
2848 + " requires API level " + minLevel + " or higher.</strong></p>"
2849 + "<p>This document is hidden because your selected API level for the documentation is "
2850 + selectedLevel + ". You can change the documentation API level with the selector "
2851 + "above the left navigation.</p>"
2852 + "<p>For more information about specifying the API level your app requires, "
2853 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2854 + ">Supporting Different Platform Versions</a>.</p>"
2855 + "<input type='button' value='OK, make this page visible' "
2856 + "title='Change the API level to " + minLevel + "' "
2857 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2858 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002859 } else {
2860 $("#naMessage").hide();
2861 }
2862}
2863
2864function toggleVisisbleApis(selectedLevel, context) {
2865 var apis = $(".api",context);
2866 apis.each(function(i) {
2867 var obj = $(this);
2868 var className = obj.attr("class");
2869 var apiLevelIndex = className.lastIndexOf("-")+1;
2870 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2871 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2872 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2873 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2874 return;
2875 }
2876 apiLevel = parseInt(apiLevel);
2877
2878 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2879 var selectedLevelNum = parseInt(selectedLevel)
2880 var apiLevelNum = parseInt(apiLevel);
2881 if (isNaN(apiLevelNum)) {
2882 apiLevelNum = maxLevel;
2883 }
2884
2885 // Grey things out that aren't available and give a tooltip title
2886 if (apiLevelNum > selectedLevelNum) {
2887 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002888 + apiLevel + "\" or higher. To reveal, change the target API level "
2889 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002890 }
Scott Mainf5089842012-08-14 16:31:07 -07002891 else obj.removeClass("absent").removeAttr("title");
2892 });
2893}
2894
2895
2896
2897
2898/* ################# SIDENAV TREE VIEW ################### */
2899
2900function new_node(me, mom, text, link, children_data, api_level)
2901{
2902 var node = new Object();
2903 node.children = Array();
2904 node.children_data = children_data;
2905 node.depth = mom.depth + 1;
2906
2907 node.li = document.createElement("li");
2908 mom.get_children_ul().appendChild(node.li);
2909
2910 node.label_div = document.createElement("div");
2911 node.label_div.className = "label";
2912 if (api_level != null) {
2913 $(node.label_div).addClass("api");
2914 $(node.label_div).addClass("api-level-"+api_level);
2915 }
2916 node.li.appendChild(node.label_div);
2917
2918 if (children_data != null) {
2919 node.expand_toggle = document.createElement("a");
2920 node.expand_toggle.href = "javascript:void(0)";
2921 node.expand_toggle.onclick = function() {
2922 if (node.expanded) {
2923 $(node.get_children_ul()).slideUp("fast");
2924 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2925 node.expanded = false;
2926 } else {
2927 expand_node(me, node);
2928 }
2929 };
2930 node.label_div.appendChild(node.expand_toggle);
2931
2932 node.plus_img = document.createElement("img");
2933 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2934 node.plus_img.className = "plus";
2935 node.plus_img.width = "8";
2936 node.plus_img.border = "0";
2937 node.expand_toggle.appendChild(node.plus_img);
2938
2939 node.expanded = false;
2940 }
2941
2942 var a = document.createElement("a");
2943 node.label_div.appendChild(a);
2944 node.label = document.createTextNode(text);
2945 a.appendChild(node.label);
2946 if (link) {
2947 a.href = me.toroot + link;
2948 } else {
2949 if (children_data != null) {
2950 a.className = "nolink";
2951 a.href = "javascript:void(0)";
2952 a.onclick = node.expand_toggle.onclick;
2953 // This next line shouldn't be necessary. I'll buy a beer for the first
2954 // person who figures out how to remove this line and have the link
2955 // toggle shut on the first try. --joeo@android.com
2956 node.expanded = false;
2957 }
2958 }
Scott Main3b90aff2013-08-01 18:09:35 -07002959
Scott Mainf5089842012-08-14 16:31:07 -07002960
2961 node.children_ul = null;
2962 node.get_children_ul = function() {
2963 if (!node.children_ul) {
2964 node.children_ul = document.createElement("ul");
2965 node.children_ul.className = "children_ul";
2966 node.children_ul.style.display = "none";
2967 node.li.appendChild(node.children_ul);
2968 }
2969 return node.children_ul;
2970 };
2971
2972 return node;
2973}
2974
Robert Lyd2dd6e52012-11-29 21:28:48 -08002975
2976
2977
Scott Mainf5089842012-08-14 16:31:07 -07002978function expand_node(me, node)
2979{
2980 if (node.children_data && !node.expanded) {
2981 if (node.children_visited) {
2982 $(node.get_children_ul()).slideDown("fast");
2983 } else {
2984 get_node(me, node);
2985 if ($(node.label_div).hasClass("absent")) {
2986 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07002987 }
Scott Mainf5089842012-08-14 16:31:07 -07002988 $(node.get_children_ul()).slideDown("fast");
2989 }
2990 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2991 node.expanded = true;
2992
2993 // perform api level toggling because new nodes are new to the DOM
2994 var selectedLevel = $("#apiLevelSelector option:selected").val();
2995 toggleVisisbleApis(selectedLevel, "#side-nav");
2996 }
2997}
2998
2999function get_node(me, mom)
3000{
3001 mom.children_visited = true;
3002 for (var i in mom.children_data) {
3003 var node_data = mom.children_data[i];
3004 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
3005 node_data[2], node_data[3]);
3006 }
3007}
3008
3009function this_page_relative(toroot)
3010{
3011 var full = document.location.pathname;
3012 var file = "";
3013 if (toroot.substr(0, 1) == "/") {
3014 if (full.substr(0, toroot.length) == toroot) {
3015 return full.substr(toroot.length);
3016 } else {
3017 // the file isn't under toroot. Fail.
3018 return null;
3019 }
3020 } else {
3021 if (toroot != "./") {
3022 toroot = "./" + toroot;
3023 }
3024 do {
3025 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3026 var pos = full.lastIndexOf("/");
3027 file = full.substr(pos) + file;
3028 full = full.substr(0, pos);
3029 toroot = toroot.substr(0, toroot.length-3);
3030 }
3031 } while (toroot != "" && toroot != "/");
3032 return file.substr(1);
3033 }
3034}
3035
3036function find_page(url, data)
3037{
3038 var nodes = data;
3039 var result = null;
3040 for (var i in nodes) {
3041 var d = nodes[i];
3042 if (d[1] == url) {
3043 return new Array(i);
3044 }
3045 else if (d[2] != null) {
3046 result = find_page(url, d[2]);
3047 if (result != null) {
3048 return (new Array(i).concat(result));
3049 }
3050 }
3051 }
3052 return null;
3053}
3054
Scott Mainf5089842012-08-14 16:31:07 -07003055function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07003056 // load json file for navtree data
3057 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3058 // when the file is loaded, initialize the tree
3059 if(jqxhr.status === 200) {
3060 init_navtree("tree-list", toroot, NAVTREE_DATA);
3061 }
3062 });
Scott Main3b90aff2013-08-01 18:09:35 -07003063
Scott Mainf5089842012-08-14 16:31:07 -07003064 // perform api level toggling because because the whole tree is new to the DOM
3065 var selectedLevel = $("#apiLevelSelector option:selected").val();
3066 toggleVisisbleApis(selectedLevel, "#side-nav");
3067}
3068
3069function init_navtree(navtree_id, toroot, root_nodes)
3070{
3071 var me = new Object();
3072 me.toroot = toroot;
3073 me.node = new Object();
3074
3075 me.node.li = document.getElementById(navtree_id);
3076 me.node.children_data = root_nodes;
3077 me.node.children = new Array();
3078 me.node.children_ul = document.createElement("ul");
3079 me.node.get_children_ul = function() { return me.node.children_ul; };
3080 //me.node.children_ul.className = "children_ul";
3081 me.node.li.appendChild(me.node.children_ul);
3082 me.node.depth = 0;
3083
3084 get_node(me, me.node);
3085
3086 me.this_page = this_page_relative(toroot);
3087 me.breadcrumbs = find_page(me.this_page, root_nodes);
3088 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3089 var mom = me.node;
3090 for (var i in me.breadcrumbs) {
3091 var j = me.breadcrumbs[i];
3092 mom = mom.children[j];
3093 expand_node(me, mom);
3094 }
3095 mom.label_div.className = mom.label_div.className + " selected";
3096 addLoadEvent(function() {
3097 scrollIntoView("nav-tree");
3098 });
3099 }
3100}
3101
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003102
3103
3104
3105
3106
3107
3108
Robert Lyd2dd6e52012-11-29 21:28:48 -08003109/* TODO: eliminate redundancy with non-google functions */
3110function init_google_navtree(navtree_id, toroot, root_nodes)
3111{
3112 var me = new Object();
3113 me.toroot = toroot;
3114 me.node = new Object();
3115
3116 me.node.li = document.getElementById(navtree_id);
3117 me.node.children_data = root_nodes;
3118 me.node.children = new Array();
3119 me.node.children_ul = document.createElement("ul");
3120 me.node.get_children_ul = function() { return me.node.children_ul; };
3121 //me.node.children_ul.className = "children_ul";
3122 me.node.li.appendChild(me.node.children_ul);
3123 me.node.depth = 0;
3124
3125 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003126}
3127
3128function new_google_node(me, mom, text, link, children_data, api_level)
3129{
3130 var node = new Object();
3131 var child;
3132 node.children = Array();
3133 node.children_data = children_data;
3134 node.depth = mom.depth + 1;
3135 node.get_children_ul = function() {
3136 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003137 node.children_ul = document.createElement("ul");
3138 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003139 node.li.appendChild(node.children_ul);
3140 }
3141 return node.children_ul;
3142 };
3143 node.li = document.createElement("li");
3144
3145 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003146
3147
Robert Lyd2dd6e52012-11-29 21:28:48 -08003148 if(link) {
3149 child = document.createElement("a");
3150
3151 }
3152 else {
3153 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003154 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003155
3156 }
3157 if (children_data != null) {
3158 node.li.className="nav-section";
3159 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003160 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003161 node.li.appendChild(node.label_div);
3162 get_google_node(me, node);
3163 node.label_div.appendChild(child);
3164 }
3165 else {
3166 node.li.appendChild(child);
3167 }
3168 if(link) {
3169 child.href = me.toroot + link;
3170 }
3171 node.label = document.createTextNode(text);
3172 child.appendChild(node.label);
3173
3174 node.children_ul = null;
3175
3176 return node;
3177}
3178
3179function get_google_node(me, mom)
3180{
3181 mom.children_visited = true;
3182 var linkText;
3183 for (var i in mom.children_data) {
3184 var node_data = mom.children_data[i];
3185 linkText = node_data[0];
3186
3187 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3188 linkText = linkText.substr(19, linkText.length);
3189 }
3190 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3191 node_data[2], node_data[3]);
3192 }
3193}
Scott Mainad08f072013-08-20 16:49:57 -07003194
3195
3196
3197
3198
3199
3200/****** NEW version of script to build google and sample navs dynamically ******/
3201// TODO: update Google reference docs to tolerate this new implementation
3202
Scott Maine624b3f2013-09-12 12:56:41 -07003203var NODE_NAME = 0;
3204var NODE_HREF = 1;
3205var NODE_GROUP = 2;
3206var NODE_TAGS = 3;
3207var NODE_CHILDREN = 4;
3208
Scott Mainad08f072013-08-20 16:49:57 -07003209function init_google_navtree2(navtree_id, data)
3210{
3211 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003212 for (var i in data) {
3213 var node_data = data[i];
3214 $containerUl.append(new_google_node2(node_data));
3215 }
3216
Scott Main70557ee2013-10-30 14:47:40 -07003217 // Make all third-generation list items 'sticky' to prevent them from collapsing
3218 $containerUl.find('li li li.nav-section').addClass('sticky');
3219
Scott Mainad08f072013-08-20 16:49:57 -07003220 initExpandableNavItems("#"+navtree_id);
3221}
3222
3223function new_google_node2(node_data)
3224{
Scott Maine624b3f2013-09-12 12:56:41 -07003225 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003226 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3227 linkText = linkText.substr(19, linkText.length);
3228 }
3229 var $li = $('<li>');
3230 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003231 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003232 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3233 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003234 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003235 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3236 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003237 }
3238 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003239 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003240 $li.addClass("nav-section");
3241 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003242 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003243
Scott Maine624b3f2013-09-12 12:56:41 -07003244 for (var i in node_data[NODE_CHILDREN]) {
3245 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003246 $childUl.append(new_google_node2(child_node_data));
3247 }
3248 $li.append($childUl);
3249 }
3250 $li.prepend($a);
3251
3252 return $li;
3253}
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
Robert Lyd2dd6e52012-11-29 21:28:48 -08003265function showGoogleRefTree() {
3266 init_default_google_navtree(toRoot);
3267 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003268}
3269
3270function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003271 // load json file for navtree data
3272 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3273 // when the file is loaded, initialize the tree
3274 if(jqxhr.status === 200) {
3275 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3276 highlightSidenav();
3277 resizeNav();
3278 }
3279 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003280}
3281
3282function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003283 // load json file for navtree data
3284 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3285 // when the file is loaded, initialize the tree
3286 if(jqxhr.status === 200) {
3287 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3288 highlightSidenav();
3289 resizeNav();
3290 }
3291 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003292}
3293
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003294function showSamplesRefTree() {
3295 init_default_samples_navtree(toRoot);
3296}
3297
3298function init_default_samples_navtree(toroot) {
3299 // load json file for navtree data
3300 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3301 // when the file is loaded, initialize the tree
3302 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003303 // hack to remove the "about the samples" link then put it back in
3304 // after we nuke the list to remove the dummy static list of samples
3305 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3306 $("#nav.samples-nav").empty();
3307 $("#nav.samples-nav").append($firstLi);
3308
Scott Mainad08f072013-08-20 16:49:57 -07003309 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003310 highlightSidenav();
3311 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003312 if ($("#jd-content #samples").length) {
3313 showSamples();
3314 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003315 }
3316 });
3317}
3318
Scott Mainf5089842012-08-14 16:31:07 -07003319/* TOGGLE INHERITED MEMBERS */
3320
3321/* Toggle an inherited class (arrow toggle)
3322 * @param linkObj The link that was clicked.
3323 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3324 * 'null' to simply toggle.
3325 */
3326function toggleInherited(linkObj, expand) {
3327 var base = linkObj.getAttribute("id");
3328 var list = document.getElementById(base + "-list");
3329 var summary = document.getElementById(base + "-summary");
3330 var trigger = document.getElementById(base + "-trigger");
3331 var a = $(linkObj);
3332 if ( (expand == null && a.hasClass("closed")) || expand ) {
3333 list.style.display = "none";
3334 summary.style.display = "block";
3335 trigger.src = toRoot + "assets/images/triangle-opened.png";
3336 a.removeClass("closed");
3337 a.addClass("opened");
3338 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3339 list.style.display = "block";
3340 summary.style.display = "none";
3341 trigger.src = toRoot + "assets/images/triangle-closed.png";
3342 a.removeClass("opened");
3343 a.addClass("closed");
3344 }
3345 return false;
3346}
3347
3348/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3349 * @param linkObj The link that was clicked.
3350 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3351 * 'null' to simply toggle.
3352 */
3353function toggleAllInherited(linkObj, expand) {
3354 var a = $(linkObj);
3355 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3356 var expandos = $(".jd-expando-trigger", table);
3357 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3358 expandos.each(function(i) {
3359 toggleInherited(this, true);
3360 });
3361 a.text("[Collapse]");
3362 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3363 expandos.each(function(i) {
3364 toggleInherited(this, false);
3365 });
3366 a.text("[Expand]");
3367 }
3368 return false;
3369}
3370
3371/* Toggle all inherited members in the class (link in the class title)
3372 */
3373function toggleAllClassInherited() {
3374 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3375 var toggles = $(".toggle-all", $("#body-content"));
3376 if (a.text() == "[Expand All]") {
3377 toggles.each(function(i) {
3378 toggleAllInherited(this, true);
3379 });
3380 a.text("[Collapse All]");
3381 } else {
3382 toggles.each(function(i) {
3383 toggleAllInherited(this, false);
3384 });
3385 a.text("[Expand All]");
3386 }
3387 return false;
3388}
3389
3390/* Expand all inherited members in the class. Used when initiating page search */
3391function ensureAllInheritedExpanded() {
3392 var toggles = $(".toggle-all", $("#body-content"));
3393 toggles.each(function(i) {
3394 toggleAllInherited(this, true);
3395 });
3396 $("#toggleAllClassInherited").text("[Collapse All]");
3397}
3398
3399
3400/* HANDLE KEY EVENTS
3401 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3402 */
3403var agent = navigator['userAgent'].toLowerCase();
3404var mac = agent.indexOf("macintosh") != -1;
3405
3406$(document).keydown( function(e) {
3407var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3408 if (control && e.which == 70) { // 70 is "F"
3409 ensureAllInheritedExpanded();
3410 }
3411});
Scott Main498d7102013-08-21 15:47:38 -07003412
3413
3414
3415
3416
3417
3418/* On-demand functions */
3419
3420/** Move sample code line numbers out of PRE block and into non-copyable column */
3421function initCodeLineNumbers() {
3422 var numbers = $("#codesample-block a.number");
3423 if (numbers.length) {
3424 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3425 }
3426
3427 $(document).ready(function() {
3428 // select entire line when clicked
3429 $("span.code-line").click(function() {
3430 if (!shifted) {
3431 selectText(this);
3432 }
3433 });
3434 // invoke line link on double click
3435 $(".code-line").dblclick(function() {
3436 document.location.hash = $(this).attr('id');
3437 });
3438 // highlight the line when hovering on the number
3439 $("#codesample-line-numbers a.number").mouseover(function() {
3440 var id = $(this).attr('href');
3441 $(id).css('background','#e7e7e7');
3442 });
3443 $("#codesample-line-numbers a.number").mouseout(function() {
3444 var id = $(this).attr('href');
3445 $(id).css('background','none');
3446 });
3447 });
3448}
3449
3450// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3451var shifted = false;
3452$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3453
3454// courtesy of jasonedelman.com
3455function selectText(element) {
3456 var doc = document
3457 , range, selection
3458 ;
3459 if (doc.body.createTextRange) { //ms
3460 range = doc.body.createTextRange();
3461 range.moveToElementText(element);
3462 range.select();
3463 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003464 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003465 range = doc.createRange();
3466 range.selectNodeContents(element);
3467 selection.removeAllRanges();
3468 selection.addRange(range);
3469 }
Scott Main285f0772013-08-22 23:22:09 +00003470}
Scott Main03aca9a2013-10-31 07:20:55 -07003471
3472
3473
3474
3475/** Display links and other information about samples that match the
3476 group specified by the URL */
3477function showSamples() {
3478 var group = $("#samples").attr('class');
3479 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3480
3481 var $ul = $("<ul>");
3482 $selectedLi = $("#nav li.selected");
3483
3484 $selectedLi.children("ul").children("li").each(function() {
3485 var $li = $("<li>").append($(this).find("a").first().clone());
3486 $ul.append($li);
3487 });
3488
3489 $("#samples").append($ul);
3490
3491}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003492
3493
3494
3495/* ########################################################## */
3496/* ################### RESOURCE CARDS ##################### */
3497/* ########################################################## */
3498
3499/** Handle resource queries, collections, and grids (sections). Requires
3500 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3501
3502(function() {
3503 // Prevent the same resource from being loaded more than once per page.
3504 var addedPageResources = {};
3505
3506 $(document).ready(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003507 // Need to initialize hero carousel before other sections for dedupe
3508 // to work correctly.
3509 $('[data-carousel-query]').dacCarouselQuery();
3510
Dirk Doughertyc3921652014-05-13 16:55:26 -07003511 $('.resource-widget').each(function() {
3512 initResourceWidget(this);
3513 });
3514
3515 /* Pass the line height to ellipsisfade() to adjust the height of the
3516 text container to show the max number of lines possible, without
3517 showing lines that are cut off. This works with the css ellipsis
3518 classes to fade last text line and apply an ellipsis char. */
3519
Dirk Dougherty29e93432015-05-05 18:17:13 -07003520 //card text currently uses 20px line height.
3521 var lineHeight = 20;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003522 $('.card-info .text').ellipsisfade(lineHeight);
3523 });
3524
3525 /*
3526 Three types of resource layouts:
3527 Flow - Uses a fixed row-height flow using float left style.
3528 Carousel - Single card slideshow all same dimension absolute.
3529 Stack - Uses fixed columns and flexible element height.
3530 */
3531 function initResourceWidget(widget) {
3532 var $widget = $(widget);
3533 var isFlow = $widget.hasClass('resource-flow-layout'),
3534 isCarousel = $widget.hasClass('resource-carousel-layout'),
3535 isStack = $widget.hasClass('resource-stack-layout');
3536
Dirk Dougherty29e93432015-05-05 18:17:13 -07003537 // remove illegal col-x class which is not relevant anymore thanks to responsive styles.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003538 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
Dirk Dougherty29e93432015-05-05 18:17:13 -07003539 if (m && !$widget.is('.cols > *')) {
3540 $widget.removeClass('col-' + m[1]);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003541 }
3542
3543 var opts = {
3544 cardSizes: ($widget.data('cardsizes') || '').split(','),
3545 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3546 itemsPerPage: $widget.data('itemsperpage'),
3547 sortOrder: $widget.data('sortorder'),
3548 query: $widget.data('query'),
3549 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003550 /* Added by LFL 6/6/14 */
3551 resourceStyle: $widget.data('resourcestyle') || 'card',
3552 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003553 };
3554
3555 // run the search for the set of resources to show
3556
3557 var resources = buildResourceList(opts);
3558
3559 if (isFlow) {
3560 drawResourcesFlowWidget($widget, opts, resources);
3561 } else if (isCarousel) {
3562 drawResourcesCarouselWidget($widget, opts, resources);
3563 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003564 /* Looks like this got removed and is not used, so repurposing for the
3565 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003566 Modified by LFL 6/6/14
3567 */
3568 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003569 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003570 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003571 }
3572 }
3573
3574 /* Initializes a Resource Carousel Widget */
3575 function drawResourcesCarouselWidget($widget, opts, resources) {
3576 $widget.empty();
Dirk Dougherty29e93432015-05-05 18:17:13 -07003577 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003578
3579 $widget.addClass('resource-card slideshow-container')
3580 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3581 .append($('<a>').addClass('slideshow-next').text('Next'));
3582
3583 var css = { 'width': $widget.width() + 'px',
3584 'height': $widget.height() + 'px' };
3585
3586 var $ul = $('<ul>');
3587
3588 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003589 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003590 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003591 .decorateResourceCard(resources[i],plusone);
3592
3593 $('<li>').css(css)
3594 .append($card)
3595 .appendTo($ul);
3596 }
3597
3598 $('<div>').addClass('frame')
3599 .append($ul)
3600 .appendTo($widget);
3601
3602 $widget.dacSlideshow({
3603 auto: true,
3604 btnPrev: '.slideshow-prev',
3605 btnNext: '.slideshow-next'
3606 });
3607 };
3608
Robert Lye7eeb402014-06-03 19:35:24 -07003609 /* Initializes a Resource Card Stack Widget (column-based layout)
3610 Modified by LFL 6/6/14
3611 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003612 function drawResourcesStackWidget($widget, opts, resources, sections) {
3613 // Don't empty widget, grab all items inside since they will be the first
3614 // items stacked, followed by the resource query
Dirk Dougherty29e93432015-05-05 18:17:13 -07003615 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003616 var cards = $widget.find('.resource-card').detach().toArray();
3617 var numStacks = opts.numStacks || 1;
3618 var $stacks = [];
3619 var urlString;
3620
3621 for (var i = 0; i < numStacks; ++i) {
3622 $stacks[i] = $('<div>').addClass('resource-card-stack')
3623 .appendTo($widget);
3624 }
3625
3626 var sectionResources = [];
3627
3628 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003629 if (sections) {
3630 for (var i = 0; i < sections.length; ++i) {
3631 if (!sections[i].sections || !sections[i].sections.length) {
3632 // Render it as a resource card
3633 sectionResources.push(
3634 $('<a>')
3635 .addClass('resource-card section-card')
3636 .attr('href', cleanUrl(sections[i].resource.url))
3637 .decorateResourceCard(sections[i].resource,plusone)[0]
3638 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003639
Robert Lye7eeb402014-06-03 19:35:24 -07003640 } else {
3641 cards.push(
3642 $('<div>')
3643 .addClass('resource-card section-card-menu')
3644 .decorateResourceSection(sections[i],plusone)[0]
3645 );
3646 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003647 }
3648 }
3649
3650 cards = cards.concat(sectionResources);
3651
3652 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003653 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003654
Robert Lye7eeb402014-06-03 19:35:24 -07003655 if (opts.resourceStyle.indexOf('related') > -1) {
3656 $card.addClass('related-card');
3657 }
smain@google.com95948b82014-06-16 19:24:25 -07003658
Dirk Doughertyc3921652014-05-13 16:55:26 -07003659 cards.push($card[0]);
3660 }
3661
Robert Lye7eeb402014-06-03 19:35:24 -07003662 if (opts.stackSort != 'false') {
3663 for (var i = 0; i < cards.length; ++i) {
3664 // Find the stack with the shortest height, but give preference to
3665 // left to right order.
3666 var minHeight = $stacks[0].height();
3667 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003668
Robert Lye7eeb402014-06-03 19:35:24 -07003669 for (var j = 1; j < numStacks; ++j) {
3670 var height = $stacks[j].height();
3671 if (height < minHeight - 45) {
3672 minHeight = height;
3673 minIndex = j;
3674 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003675 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003676
Robert Lye7eeb402014-06-03 19:35:24 -07003677 $stacks[minIndex].append($(cards[i]));
3678 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003679 }
3680
3681 };
smain@google.com95948b82014-06-16 19:24:25 -07003682
3683 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003684 Create a resource card using the given resource object and a list of html
3685 configured options. Returns a jquery object containing the element.
3686 */
smain@google.com95948b82014-06-16 19:24:25 -07003687 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003688 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003689
Robert Lye7eeb402014-06-03 19:35:24 -07003690 // The difference here is that generic cards are not entirely clickable
3691 // so its a div instead of an a tag, also the generic one is not given
3692 // the resource-card class so it appears with a transparent background
3693 // and can be styled in whatever way the css setup.
3694 if (opts.resourceStyle == 'generic') {
3695 $el = $('<div>')
3696 .addClass('resource')
3697 .attr('href', cleanUrl(resource.url))
3698 .decorateResource(resource, opts);
3699 } else {
3700 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003701
Robert Lye7eeb402014-06-03 19:35:24 -07003702 $el = $('<a>')
3703 .addClass(cls)
3704 .attr('href', cleanUrl(resource.url))
3705 .decorateResourceCard(resource, plusone);
3706 }
smain@google.com95948b82014-06-16 19:24:25 -07003707
Robert Lye7eeb402014-06-03 19:35:24 -07003708 return $el;
3709 }
Dirk Dougherty29e93432015-05-05 18:17:13 -07003710
3711 function createResponsiveFlowColumn(cardSize) {
3712 var cardWidth = parseInt(cardSize.match(/(\d+)/)[1], 10);
3713 var column = $('<div>').addClass('col-' + (cardWidth / 3) + 'of6');
3714 if (cardWidth < 9) {
3715 column.addClass('col-tablet-1of2');
3716 } else if (cardWidth > 9 && cardWidth < 18) {
3717 column.addClass('col-tablet-1of1');
3718 }
3719 if (cardWidth < 18) {
3720 column.addClass('col-mobile-1of1')
3721 }
3722 return column;
3723 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003724
3725 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3726 function drawResourcesFlowWidget($widget, opts, resources) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003727 $widget.empty().addClass('cols');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003728 var cardSizes = opts.cardSizes || ['6x6'];
3729 var i = 0, j = 0;
Dirk Dougherty29e93432015-05-05 18:17:13 -07003730 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003731
3732 while (i < resources.length) {
3733 var cardSize = cardSizes[j++ % cardSizes.length];
3734 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Dougherty29e93432015-05-05 18:17:13 -07003735
3736 var column = createResponsiveFlowColumn(cardSize).appendTo($widget);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003737
3738 // A stack has a third dimension which is the number of stacked items
3739 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3740 var stackCount = 0;
3741 var $stackDiv = null;
3742
3743 if (isStack) {
3744 // Create a stack container which should have the dimensions defined
3745 // by the product of the items inside.
3746 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
Dirk Dougherty29e93432015-05-05 18:17:13 -07003747 + 'x' + isStack[2] * isStack[3]) .appendTo(column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003748 }
3749
3750 // Build each stack item or just a single item
3751 do {
3752 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003753
Robert Lye7eeb402014-06-03 19:35:24 -07003754 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003755
3756 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003757 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003758
Dirk Doughertyc3921652014-05-13 16:55:26 -07003759 if (isStack) {
3760 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3761 if (++stackCount == parseInt(isStack[3])) {
3762 $card.addClass('resource-card-row-stack-last');
3763 stackCount = 0;
3764 }
3765 } else {
3766 stackCount = 0;
3767 }
3768
Dirk Dougherty29e93432015-05-05 18:17:13 -07003769 $card.appendTo($stackDiv || column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003770
3771 } while (++i < resources.length && stackCount > 0);
3772 }
3773 }
3774
3775 /* Build a site map of resources using a section as a root. */
3776 function buildSectionList(opts) {
3777 if (opts.section && SECTION_BY_ID[opts.section]) {
3778 return SECTION_BY_ID[opts.section].sections || [];
3779 }
3780 return [];
3781 }
3782
3783 function buildResourceList(opts) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003784 return $.queryResources(opts);
3785 }
3786
3787 $.queryResources = function(opts) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003788 var maxResults = opts.maxResults || 100;
3789
3790 var query = opts.query || '';
3791 var expressions = parseResourceQuery(query);
3792 var addedResourceIndices = {};
3793 var results = [];
3794
3795 for (var i = 0; i < expressions.length; i++) {
3796 var clauses = expressions[i];
3797
3798 // build initial set of resources from first clause
3799 var firstClause = clauses[0];
3800 var resources = [];
3801 switch (firstClause.attr) {
3802 case 'type':
3803 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3804 break;
3805 case 'lang':
3806 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3807 break;
3808 case 'tag':
3809 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3810 break;
3811 case 'collection':
3812 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3813 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3814 break;
3815 case 'section':
3816 var urls = SITE_MAP[firstClause.value].sections || [];
3817 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3818 break;
3819 }
3820 // console.log(firstClause.attr + ':' + firstClause.value);
3821 resources = resources || [];
3822
3823 // use additional clauses to filter corpus
3824 if (clauses.length > 1) {
3825 var otherClauses = clauses.slice(1);
3826 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3827 }
3828
3829 // filter out resources already added
3830 if (i > 1) {
3831 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3832 }
3833
3834 // add to list of already added indices
3835 for (var j = 0; j < resources.length; j++) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003836 if (resources[j]) {
3837 addedResourceIndices[resources[j].index] = 1;
3838 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003839 }
3840
3841 // concat to final results list
3842 results = results.concat(resources);
3843 }
3844
3845 if (opts.sortOrder && results.length) {
3846 var attr = opts.sortOrder;
3847
3848 if (opts.sortOrder == 'random') {
3849 var i = results.length, j, temp;
3850 while (--i) {
3851 j = Math.floor(Math.random() * (i + 1));
3852 temp = results[i];
3853 results[i] = results[j];
3854 results[j] = temp;
3855 }
3856 } else {
3857 var desc = attr.charAt(0) == '-';
3858 if (desc) {
3859 attr = attr.substring(1);
3860 }
3861 results = results.sort(function(x,y) {
3862 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3863 });
3864 }
3865 }
3866
3867 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3868 results = results.slice(0, maxResults);
3869
3870 for (var j = 0; j < results.length; ++j) {
3871 addedPageResources[results[j].index] = 1;
3872 }
3873
3874 return results;
3875 }
3876
3877
3878 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3879 return function(resource) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003880 return resource && !addedResourceIndices[resource.index];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003881 };
3882 }
3883
3884
3885 function getResourceMatchesClausesFilter(clauses) {
3886 return function(resource) {
3887 return doesResourceMatchClauses(resource, clauses);
3888 };
3889 }
3890
3891
3892 function doesResourceMatchClauses(resource, clauses) {
3893 for (var i = 0; i < clauses.length; i++) {
3894 var map;
3895 switch (clauses[i].attr) {
3896 case 'type':
3897 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3898 break;
3899 case 'lang':
3900 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3901 break;
3902 case 'tag':
3903 map = IS_RESOURCE_TAGGED[clauses[i].value];
3904 break;
3905 }
3906
3907 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3908 return clauses[i].negative;
3909 }
3910 }
3911 return true;
3912 }
smain@google.com95948b82014-06-16 19:24:25 -07003913
Robert Lye7eeb402014-06-03 19:35:24 -07003914 function cleanUrl(url)
3915 {
3916 if (url && url.indexOf('//') === -1) {
3917 url = toRoot + url;
3918 }
smain@google.com95948b82014-06-16 19:24:25 -07003919
Robert Lye7eeb402014-06-03 19:35:24 -07003920 return url;
3921 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003922
3923
3924 function parseResourceQuery(query) {
3925 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3926 var expressions = [];
3927 var expressionStrs = query.split(',') || [];
3928 for (var i = 0; i < expressionStrs.length; i++) {
3929 var expr = expressionStrs[i] || '';
3930
3931 // Break expression into clauses (clause e.g. 'tag:foo')
3932 var clauses = [];
3933 var clauseStrs = expr.split(/(?=[\+\-])/);
3934 for (var j = 0; j < clauseStrs.length; j++) {
3935 var clauseStr = clauseStrs[j] || '';
3936
3937 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3938 var parts = clauseStr.split(':');
3939 var clause = {};
3940
3941 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3942 if (clause.attr) {
3943 if (clause.attr.charAt(0) == '+') {
3944 clause.attr = clause.attr.substring(1);
3945 } else if (clause.attr.charAt(0) == '-') {
3946 clause.negative = true;
3947 clause.attr = clause.attr.substring(1);
3948 }
3949 }
3950
3951 if (parts.length > 1) {
3952 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3953 }
3954
3955 clauses.push(clause);
3956 }
3957
3958 if (!clauses.length) {
3959 continue;
3960 }
3961
3962 expressions.push(clauses);
3963 }
3964
3965 return expressions;
3966 }
3967})();
3968
3969(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07003970
smain@google.com95948b82014-06-16 19:24:25 -07003971 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003972 Utility method for creating dom for the description area of a card.
3973 Used in decorateResourceCard and decorateResource.
3974 */
3975 function buildResourceCardDescription(resource, plusone) {
3976 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07003977
Robert Lye7eeb402014-06-03 19:35:24 -07003978 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07003979
Robert Lye7eeb402014-06-03 19:35:24 -07003980 if (resource.cta) {
3981 $description.append($('<a>').addClass('cta').html(resource.cta));
3982 }
smain@google.com95948b82014-06-16 19:24:25 -07003983
Robert Lye7eeb402014-06-03 19:35:24 -07003984 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07003985 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07003986 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07003987
Robert Lye7eeb402014-06-03 19:35:24 -07003988 $description.append($('<div>').addClass('util')
3989 .append($('<div>').addClass('g-plusone')
3990 .attr('data-size', 'small')
3991 .attr('data-align', 'right')
3992 .attr('data-href', plusurl)));
3993 }
smain@google.com95948b82014-06-16 19:24:25 -07003994
Robert Lye7eeb402014-06-03 19:35:24 -07003995 return $description;
3996 }
smain@google.com95948b82014-06-16 19:24:25 -07003997
3998
Dirk Doughertyc3921652014-05-13 16:55:26 -07003999 /* Simple jquery function to create dom for a standard resource card */
4000 $.fn.decorateResourceCard = function(resource,plusone) {
4001 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07004002 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004003 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07004004
Robert Lye7eeb402014-06-03 19:35:24 -07004005 if (imgUrl.indexOf('//') === -1) {
4006 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07004007 }
Robert Lye7eeb402014-06-03 19:35:24 -07004008
4009 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07004010 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07004011 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07004012 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07004013
Robert Lye7eeb402014-06-03 19:35:24 -07004014 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
4015 .append($('<div>').addClass('section').text(section))
4016 .append($('<div>').addClass('title').html(resource.title))
4017 .append(buildResourceCardDescription(resource, plusone))
4018 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07004019
4020 return this;
4021 };
4022
4023 /* Simple jquery function to create dom for a resource section card (menu) */
4024 $.fn.decorateResourceSection = function(section,plusone) {
4025 var resource = section.resource;
4026 //keep url clean for matching and offline mode handling
4027 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
4028 var $base = $('<a>')
4029 .addClass('card-bg')
4030 .attr('href', resource.url)
4031 .append($('<div>').addClass('card-section-icon')
4032 .append($('<div>').addClass('icon'))
4033 .append($('<div>').addClass('section').html(resource.title)))
4034 .appendTo(this);
4035
4036 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
4037
4038 if (section.sections && section.sections.length) {
4039 // Recurse the section sub-tree to find a resource image.
4040 var stack = [section];
4041
4042 while (stack.length) {
4043 if (stack[0].resource.image) {
4044 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4045 break;
4046 }
4047
4048 if (stack[0].sections) {
4049 stack = stack.concat(stack[0].sections);
4050 }
4051
4052 stack.shift();
4053 }
4054
4055 var $ul = $('<ul>')
4056 .appendTo($cardInfo);
4057
4058 var max = section.sections.length > 3 ? 3 : section.sections.length;
4059
4060 for (var i = 0; i < max; ++i) {
4061
4062 var subResource = section.sections[i];
4063 if (!plusone) {
4064 $('<li>')
4065 .append($('<a>').attr('href', subResource.url)
4066 .append($('<div>').addClass('title').html(subResource.title))
4067 .append($('<div>').addClass('description ellipsis')
4068 .append($('<div>').addClass('text').html(subResource.summary))
4069 .append($('<div>').addClass('util'))))
4070 .appendTo($ul);
4071 } else {
4072 $('<li>')
4073 .append($('<a>').attr('href', subResource.url)
4074 .append($('<div>').addClass('title').html(subResource.title))
4075 .append($('<div>').addClass('description ellipsis')
4076 .append($('<div>').addClass('text').html(subResource.summary))
4077 .append($('<div>').addClass('util')
4078 .append($('<div>').addClass('g-plusone')
4079 .attr('data-size', 'small')
4080 .attr('data-align', 'right')
4081 .attr('data-href', resource.url)))))
4082 .appendTo($ul);
4083 }
4084 }
4085
4086 // Add a more row
4087 if (max < section.sections.length) {
4088 $('<li>')
4089 .append($('<a>').attr('href', resource.url)
4090 .append($('<div>')
4091 .addClass('title')
4092 .text('More')))
4093 .appendTo($ul);
4094 }
4095 } else {
4096 // No sub-resources, just render description?
4097 }
4098
4099 return this;
4100 };
smain@google.com95948b82014-06-16 19:24:25 -07004101
4102
4103
4104
Robert Lye7eeb402014-06-03 19:35:24 -07004105 /* Render other types of resource styles that are not cards. */
4106 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07004107 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004108 'assets/images/resource-card-default-android.jpg';
4109 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004110
Robert Lye7eeb402014-06-03 19:35:24 -07004111 if (imgUrl.indexOf('//') === -1) {
4112 imgUrl = toRoot + imgUrl;
4113 }
smain@google.com95948b82014-06-16 19:24:25 -07004114
Robert Lye7eeb402014-06-03 19:35:24 -07004115 if (linkUrl && linkUrl.indexOf('//') === -1) {
4116 linkUrl = toRoot + linkUrl;
4117 }
4118
4119 $(this).append(
4120 $('<div>').addClass('image')
4121 .css('background-image', 'url(' + imgUrl + ')'),
4122 $('<div>').addClass('info').append(
4123 $('<h4>').addClass('title').html(resource.title),
4124 $('<p>').addClass('summary').html(resource.summary),
4125 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4126 )
4127 );
4128
4129 return this;
4130 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004131})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004132
4133
Dirk Doughertyc3921652014-05-13 16:55:26 -07004134/* Calculate the vertical area remaining */
4135(function($) {
4136 $.fn.ellipsisfade= function(lineHeight) {
4137 this.each(function() {
4138 // get element text
4139 var $this = $(this);
4140 var remainingHeight = $this.parent().parent().height();
4141 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004142 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004143 if ($(this).is(":visible")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004144 var h = $(this).outerHeight(true);
smain@google.comcda1a9a2014-06-19 17:07:46 -07004145 remainingHeight = remainingHeight - h;
4146 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004147 });
4148
4149 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4150 $this.parent().css({'height': adjustedRemainingHeight});
4151 $this.css({'height': "auto"});
4152 });
4153
4154 return this;
4155 };
4156}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004157
4158/*
4159 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004160
Robert Lye7eeb402014-06-03 19:35:24 -07004161 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004162 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004163 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004164
Robert Lye7eeb402014-06-03 19:35:24 -07004165 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004166
Robert Lye7eeb402014-06-03 19:35:24 -07004167 <div class="fullscreen-carousel">
4168 <div class="fullscreen-carousel-content">
4169 <!-- content here -->
4170 </div>
4171 <div class="fullscreen-carousel-content">
4172 <!-- content here -->
4173 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004174
Robert Lye7eeb402014-06-03 19:35:24 -07004175 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004176
Robert Lye7eeb402014-06-03 19:35:24 -07004177 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004178
Robert Lye7eeb402014-06-03 19:35:24 -07004179 Control over how the carousel takes over the screen can mostly be defined in
4180 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004181 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004182 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004183 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004184 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004185
Robert Lye7eeb402014-06-03 19:35:24 -07004186 There is limited functionality for having multiple sections since that request
4187 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4188 scroll between multiple content areas.
4189*/
4190
4191(function() {
4192 $(document).ready(function() {
4193 $('.fullscreen-carousel').each(function() {
4194 initWidget(this);
4195 });
4196 });
4197
4198 function initWidget(widget) {
4199 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004200
Robert Lye7eeb402014-06-03 19:35:24 -07004201 var topOffset = $widget.offset().top;
4202 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4203 var maxHeight = 0;
4204 var minHeight = 0;
4205 var $content = $widget.find('.fullscreen-carousel-content');
4206 var $nextArrow = $widget.find('.next-arrow');
4207 var $prevArrow = $widget.find('.prev-arrow');
4208 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004209
Robert Lye7eeb402014-06-03 19:35:24 -07004210 if ($content.length <= 1) {
4211 $nextArrow.hide();
4212 $prevArrow.hide();
4213 } else {
4214 $nextArrow.click(function() {
4215 var index = ($content.index($curSection) + 1);
4216 $curSection.hide();
4217 $curSection = $($content[index >= $content.length ? 0 : index]);
4218 $curSection.show();
4219 });
smain@google.com95948b82014-06-16 19:24:25 -07004220
Robert Lye7eeb402014-06-03 19:35:24 -07004221 $prevArrow.click(function() {
4222 var index = ($content.index($curSection) - 1);
4223 $curSection.hide();
4224 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4225 $curSection.show();
4226 });
4227 }
4228
4229 // Just hide all content sections except first.
4230 $content.each(function(index) {
4231 if ($(this).height() > minHeight) minHeight = $(this).height();
4232 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4233 });
4234
4235 // Register for changes to window size, and trigger.
4236 $(window).resize(resizeWidget);
4237 resizeWidget();
4238
4239 function resizeWidget() {
4240 var height = $(window).height() - topOffset - padBottom;
4241 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004242 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004243 (maxHeight && height > maxHeight ? maxHeight : height));
4244 }
smain@google.com95948b82014-06-16 19:24:25 -07004245 }
Robert Lye7eeb402014-06-03 19:35:24 -07004246})();
4247
4248
4249
4250
4251
4252/*
4253 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004254
Robert Lye7eeb402014-06-03 19:35:24 -07004255 The following allows tab widgets to be installed via the html below. Each
4256 tab content section should have a data-tab attribute matching one of the
4257 nav items'. Also each tab content section should have a width matching the
4258 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004259
Robert Lye7eeb402014-06-03 19:35:24 -07004260 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004261
Robert Lye7eeb402014-06-03 19:35:24 -07004262 <div class="tab-carousel">
4263 <ul class="tab-nav">
4264 <li><a href="#" data-tab="handsets">Handsets</a>
4265 <li><a href="#" data-tab="wearable">Wearable</a>
4266 <li><a href="#" data-tab="tv">TV</a>
4267 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004268
Robert Lye7eeb402014-06-03 19:35:24 -07004269 <div class="tab-carousel-content">
4270 <div data-tab="handsets">
4271 <!--Full width content here-->
4272 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004273
Robert Lye7eeb402014-06-03 19:35:24 -07004274 <div data-tab="wearable">
4275 <!--Full width content here-->
4276 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004277
Robert Lye7eeb402014-06-03 19:35:24 -07004278 <div data-tab="tv">
4279 <!--Full width content here-->
4280 </div>
4281 </div>
4282 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004283
Robert Lye7eeb402014-06-03 19:35:24 -07004284*/
4285(function() {
4286 $(document).ready(function() {
4287 $('.tab-carousel').each(function() {
4288 initWidget(this);
4289 });
4290 });
4291
4292 function initWidget(widget) {
4293 var $widget = $(widget);
4294 var $nav = $widget.find('.tab-nav');
4295 var $anchors = $nav.find('[data-tab]');
4296 var $li = $nav.find('li');
4297 var $contentContainer = $widget.find('.tab-carousel-content');
4298 var $tabs = $contentContainer.find('[data-tab]');
4299 var $curTab = $($tabs[0]); // Current tab is first tab.
4300 var width = $widget.width();
4301
4302 // Setup nav interactivity.
4303 $anchors.click(function(evt) {
4304 evt.preventDefault();
4305 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004306 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004307 });
smain@google.com95948b82014-06-16 19:24:25 -07004308
Robert Lye7eeb402014-06-03 19:35:24 -07004309 // Add highlight for navigation on first item.
4310 var $highlight = $('<div>').addClass('highlight')
4311 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4312 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004313
Robert Lye7eeb402014-06-03 19:35:24 -07004314 // Store height since we will change contents to absolute.
4315 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004316
Robert Lye7eeb402014-06-03 19:35:24 -07004317 // Absolutely position tabs so they're ready for transition.
4318 $tabs.each(function(index) {
4319 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4320 });
smain@google.com95948b82014-06-16 19:24:25 -07004321
Robert Lye7eeb402014-06-03 19:35:24 -07004322 function transitionWidget($toTab) {
4323 if (!$curTab.is($toTab)) {
4324 var curIndex = $tabs.index($curTab[0]);
4325 var toIndex = $tabs.index($toTab[0]);
4326 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004327
Robert Lye7eeb402014-06-03 19:35:24 -07004328 // Animate content sections.
4329 $toTab.css({left:(width * dir) + 'px'});
4330 $curTab.animate({left:(width * -dir) + 'px'});
4331 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004332
Robert Lye7eeb402014-06-03 19:35:24 -07004333 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004334 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004335 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004336
Robert Lye7eeb402014-06-03 19:35:24 -07004337 // Store new current section.
4338 $curTab = $toTab;
4339 }
4340 }
smain@google.com95948b82014-06-16 19:24:25 -07004341 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08004342})();
Dirk Dougherty29e93432015-05-05 18:17:13 -07004343
4344(function($) {
4345 'use strict';
4346
4347 /**
4348 * Toggle Floating Label state.
4349 * @param {HTMLElement} el - The DOM element.
4350 * @param options
4351 * @constructor
4352 */
4353 function FloatingLabel(el, options) {
4354 this.el = $(el);
4355 this.options = $.extend({}, FloatingLabel.DEFAULTS_, options);
4356 this.group = this.el.closest('.dac-form-input-group');
4357 this.input = this.group.find('.dac-form-input');
4358
4359 this.checkValue_ = this.checkValue_.bind(this);
4360 this.checkValue_();
4361
4362 this.input.on('focus', function() {
4363 this.group.addClass('dac-focused');
4364 }.bind(this));
4365 this.input.on('blur', function() {
4366 this.group.removeClass('dac-focused');
4367 this.checkValue_();
4368 }.bind(this));
4369 this.input.on('keyup', this.checkValue_);
4370 }
4371
4372 /**
4373 * The label is moved out of the textbox when it has a value.
4374 */
4375 FloatingLabel.prototype.checkValue_ = function() {
4376 if (this.input.val().length) {
4377 this.group.addClass('dac-has-value');
4378 } else {
4379 this.group.removeClass('dac-has-value');
4380 }
4381 };
4382
4383 /**
4384 * jQuery plugin
4385 * @param {object} options - Override default options.
4386 */
4387 $.fn.dacFloatingLabel = function(options) {
4388 return this.each(function() {
4389 new FloatingLabel(this, options);
4390 });
4391 };
4392
4393 $(document).on('ready.aranja', function() {
4394 $('.dac-form-floatlabel').each(function() {
4395 $(this).dacFloatingLabel($(this).data());
4396 });
4397 });
4398})(jQuery);
4399
4400/* global toRoot, CAROUSEL_OVERRIDE */
4401(function($) {
4402 // Ordering matters
4403 var TAG_MAP = [
4404 {from: 'developerstory', to: 'Android Developer Story'},
4405 {from: 'googleplay', to: 'Google Play'}
4406 ];
4407
4408 function DacCarouselQuery(el) {
4409 this.el = $(el);
4410
4411 var opts = this.el.data();
4412 opts.maxResults = parseInt(opts.maxResults || '100', 10);
4413 opts.query = opts.carouselQuery;
4414 var resources = $.queryResources(opts);
4415
4416 this.el.empty();
4417 $(resources).map(function() {
4418 var resource = $.extend({}, this, CAROUSEL_OVERRIDE[this.url]);
4419 var slide = $('<article class="dac-expand dac-hero">');
4420 var image = cleanUrl(resource.heroImage || resource.image);
4421 var fullBleed = image && !resource.heroColor;
4422
4423 // Configure background
4424 slide.css({
4425 backgroundImage: fullBleed ? 'url(' + image + ')' : '',
4426 backgroundColor: resource.heroColor || ''
4427 });
4428
4429 // Should copy be inverted
4430 slide.toggleClass('dac-invert', resource.heroInvert || fullBleed);
4431 slide.toggleClass('dac-darken', fullBleed);
4432
4433 var cols = $('<div class="cols dac-hero-content">');
4434
4435 // inline image column
4436 var rightCol = $('<div class="col-1of2 col-push-1of2 dac-hero-figure">')
4437 .appendTo(cols);
4438
4439 if (!fullBleed && image) {
4440 rightCol.append($('<img>').attr('src', image));
4441 }
4442
4443 // info column
4444 $('<div class="col-1of2 col-pull-1of2">')
4445 .append($('<div class="dac-hero-tag">').text(formatTag(resource)))
4446 .append($('<h1 class="dac-hero-title">').text(formatTitle(resource)))
4447 .append($('<p class="dac-hero-description">').text(resource.summary))
4448 .append($('<a class="dac-hero-cta">')
4449 .text(formatCTA(resource))
4450 .attr('href', cleanUrl(resource.url))
4451 .prepend($('<span class="dac-sprite dac-auto-chevron">'))
4452 )
4453 .appendTo(cols);
4454
4455 slide.append(cols.wrap('<div class="wrap">').parent());
4456 return slide[0];
4457 }).prependTo(this.el);
4458
4459 // Pagination element.
4460 this.el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>');
4461
4462 this.el.dacCarousel();
4463 }
4464
4465 function cleanUrl(url) {
4466 if (url && url.indexOf('//') === -1) {
4467 url = toRoot + url;
4468 }
4469 return url;
4470 }
4471
4472 function formatTag(resource) {
4473 // Hmm, need a better more scalable solution for this.
4474 for (var i = 0, mapping; mapping = TAG_MAP[i]; i++) {
4475 if (resource.tags.indexOf(mapping.from) > -1) {
4476 return mapping.to;
4477 }
4478 }
4479 return resource.type;
4480 }
4481
4482 function formatTitle(resource) {
4483 return resource.title.replace(/android developer story: /i, '');
4484 }
4485
4486 function formatCTA(resource) {
4487 return resource.type === 'youtube' ? 'Watch the video' : 'Learn more';
4488 }
4489
4490 // jQuery plugin
4491 $.fn.dacCarouselQuery = function() {
4492 return this.each(function() {
4493 var el = $(this);
4494 var data = el.data('dac.carouselQuery');
4495
4496 if (!data) { el.data('dac.carouselQuery', (data = new DacCarouselQuery(el))); }
4497 });
4498 };
4499
4500 // Data API
4501 $(function() {
4502 $('[data-carousel-query]').dacCarouselQuery();
4503 });
4504})(jQuery);
4505
4506(function($) {
4507 /**
4508 * A CSS based carousel, inspired by SequenceJS.
4509 * @param {jQuery} el
4510 * @param {object} options
4511 * @constructor
4512 */
4513 function DacCarousel(el, options) {
4514 this.el = $(el);
4515 this.options = options = $.extend({}, DacCarousel.OPTIONS, this.el.data(), options || {});
4516 this.frames = this.el.find(options.frameSelector);
4517 this.count = this.frames.size();
4518 this.current = options.start;
4519
4520 this.initPagination();
4521 this.initEvents();
4522 this.initFrame();
4523 }
4524
4525 DacCarousel.OPTIONS = {
4526 auto: true,
4527 autoTime: 10000,
4528 autoMinTime: 5000,
4529 btnPrev: '[data-carousel-prev]',
4530 btnNext: '[data-carousel-next]',
4531 frameSelector: 'article',
4532 loop: true,
4533 start: 0,
4534 pagination: '[data-carousel-pagination]'
4535 };
4536
4537 DacCarousel.prototype.initPagination = function() {
4538 this.pagination = $([]);
4539 if (!this.options.pagination) { return; }
4540
4541 var pagination = $('<ul class="dac-pagination">');
4542 var parent = this.el;
4543 if (typeof this.options.pagination === 'string') { parent = this.el.find(this.options.pagination); }
4544
4545 if (this.count > 1) {
4546 for (var i = 0; i < this.count; i++) {
4547 var li = $('<li class="dac-pagination-item">').text(i);
4548 if (i === this.options.start) { li.addClass('active'); }
4549 li.click(this.go.bind(this, i));
4550
4551 pagination.append(li);
4552 }
4553 this.pagination = pagination.children();
4554 parent.append(pagination);
4555 }
4556 };
4557
4558 DacCarousel.prototype.initEvents = function() {
4559 var that = this;
4560
4561 this.el.hover(function() {
4562 that.pauseRotateTimer();
4563 }, function() {
4564 that.startRotateTimer();
4565 });
4566
4567 $(this.options.btnPrev).click(function(e) {
4568 e.preventDefault();
4569 that.prev();
4570 });
4571
4572 $(this.options.btnNext).click(function(e) {
4573 e.preventDefault();
4574 that.next();
4575 });
4576 };
4577
4578 DacCarousel.prototype.initFrame = function() {
4579 this.frames.removeClass('active').eq(this.options.start).addClass('active');
4580 };
4581
4582 DacCarousel.prototype.startRotateTimer = function() {
4583 if (!this.options.auto || this.rotateTimer) { return; }
4584 this.rotateTimer = setTimeout(this.next.bind(this), this.options.autoTime);
4585 };
4586
4587 DacCarousel.prototype.pauseRotateTimer = function() {
4588 clearTimeout(this.rotateTimer);
4589 this.rotateTimer = null;
4590 };
4591
4592 DacCarousel.prototype.prev = function() {
4593 this.go(this.current - 1);
4594 };
4595
4596 DacCarousel.prototype.next = function() {
4597 this.go(this.current + 1);
4598 };
4599
4600 DacCarousel.prototype.go = function(next) {
4601 // Figure out what the next slide is.
4602 while (this.count > 0 && next >= this.count) { next -= this.count; }
4603 while (next < 0) { next += this.count; }
4604
4605 // Cancel if we're already on that slide.
4606 if (next === this.current) { return; }
4607
4608 // Prepare next slide.
4609 this.frames.eq(next).removeClass('out');
4610
4611 // Recalculate styles before starting slide transition.
4612 var that = this;
4613 resolveStyles(this.el[0], function() {
4614 // Update pagination
4615 that.pagination.removeClass('active').eq(next).addClass('active');
4616
4617 // Transition out current frame
4618 that.frames.eq(that.current).toggleClass('active out');
4619
4620 // Transition in a new frame
4621 that.frames.eq(next).toggleClass('active');
4622
4623 that.current = next;
4624 });
4625 };
4626
4627 // Helper
4628 function resolveStyles(el, callback) {
4629 /*jshint expr:true*/
4630 el.offsetTop;
4631 callback();
4632 }
4633
4634 // jQuery plugin
4635 $.fn.dacCarousel = function() {
4636 this.each(function() {
4637 var $el = $(this);
4638 $el.data('dac-carousel', new DacCarousel(this));
4639 });
4640 return this;
4641 };
4642
4643 // Data API
4644 $(function() {
4645 $('[data-carousel]').dacCarousel();
4646 });
4647})(jQuery);
4648
4649(function($) {
4650 'use strict';
4651
4652 /**
4653 * Toggle the visabilty of the mobile navigation.
4654 * @param {HTMLElement} el - The DOM element.
4655 * @param options
4656 * @constructor
4657 */
4658 function ToggleModal(el, options) {
4659 this.el = $(el);
4660 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
4661 this.el.on('click', this.clickHandler_.bind(this));
4662 }
4663
4664 ToggleModal.DEFAULTS_ = {
4665 toggleClass: 'dac-modal-open'
4666 };
4667
4668 /**
4669 * The actual toggle logic.
4670 * @param event
4671 * @private
4672 */
4673 ToggleModal.prototype.clickHandler_ = function(event) {
4674 event.preventDefault();
4675 //TODO: Toggle a class on the modal itself
4676 $('body').toggleClass(this.options.toggleClass);
4677 $('.dac-modal-dimmer').toggleClass('dac-active');
4678 $('.dac-modal-window').toggleClass('dac-active');
4679 };
4680
4681 /**
4682 * jQuery plugin
4683 * @param {object} options - Override default options.
4684 */
4685 $.fn.dacToggleModal = function(options) {
4686 return this.each(function() {
4687 new ToggleModal(this, options);
4688 });
4689 };
4690
4691 /**
4692 * Data Attribute API
4693 */
4694 $(document).on('ready.aranja', function() {
4695 $('[data-modal-toogle]').each(function() {
4696 $(this).dacToggleModal($(this).data());
4697 });
4698 });
4699})(jQuery);
4700
4701(function($) {
4702 'use strict';
4703
4704 /**
4705 * Toggle the visabilty of the mobile navigation.
4706 * @param {HTMLElement} el - The DOM element.
4707 * @param options
4708 * @constructor
4709 */
4710 function ToggleNav(el, options) {
4711 this.el = $(el);
4712 this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
4713 this.options.target = [this.options.navigation];
4714
4715 if (this.options.body) {this.options.target.push('body')}
4716 if (this.options.dimmer) {this.options.target.push(this.options.dimmer)}
4717
4718 this.el.on('click', this.clickHandler_.bind(this));
4719 }
4720
4721 /**
4722 * ToggleNav Default Settings
4723 * @type {{body: boolean, dimmer: string, navigation: string, toggleClass: string}}
4724 * @private
4725 */
4726 ToggleNav.DEFAULTS_ = {
4727 body: true,
4728 dimmer: '.dac-nav-dimmer',
4729 navigation: '[data-dac-nav]',
4730 toggleClass: 'dac-nav-open'
4731 };
4732
4733 /**
4734 * The actual toggle logic.
4735 * @param event
4736 * @private
4737 */
4738 ToggleNav.prototype.clickHandler_ = function(event) {
4739 event.preventDefault();
4740 $(this.options.target.join(', ')).toggleClass(this.options.toggleClass);
4741 };
4742
4743 /**
4744 * jQuery plugin
4745 * @param {object} options - Override default options.
4746 */
4747 $.fn.dacToggleMobileNav = function(options) {
4748 return this.each(function() {
4749 new ToggleNav(this, options);
4750 });
4751 };
4752
4753 /**
4754 * Data Attribute API
4755 */
4756 $(window).on('load.aranja', function() {
4757 $('[data-dac-toggle-nav]').each(function() {
4758 $(this).dacToggleMobileNav($(this).data());
4759 });
4760 });
4761})(jQuery);
4762
4763(function($) {
4764 'use strict';
4765
4766 /**
4767 * Submit the newsletter form to a Google Form.
4768 * @param {HTMLElement} el - The Form DOM element.
4769 * @constructor
4770 */
4771 function NewsletterForm(el) {
4772 this.el = $(el);
4773 this.url = this.el.attr('action');
4774 this.el.on('submit', this.submitHandler_.bind(this));
4775 }
4776
4777 /**
4778 * Close the modal when the form is sent.
4779 * @private
4780 */
4781 NewsletterForm.prototype.submitHandler_ = function() {
4782 //TODO: Close the modal with an event and let modal.js handle this
4783 $('body').removeClass('dac-modal-open');
4784 $('.dac-modal-dimmer').removeClass('dac-active');
4785 $('.dac-modal-window').removeClass('dac-active');
4786 };
4787
4788 /**
4789 * jQuery plugin
4790 * @param {object} options - Override default options.
4791 */
4792 $.fn.dacNewsletterForm = function(options) {
4793 return this.each(function() {
4794 new NewsletterForm(this, options);
4795 });
4796 };
4797
4798 /**
4799 * Data Attribute API
4800 */
4801 $(document).on('ready.aranja', function() {
4802 $('[data-newsletter-form]').each(function() {
4803 $(this).dacNewsletterForm();
4804 });
4805 });
4806})(jQuery);