blob: 8baf3f4d50f5328bbe19e558cbe3391c32a89d26 [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");
Joe Fernandeza9d796a2015-05-05 22:07:42 -0700195 } else if ($("body").hasClass("preview")) {
196 subNavEl.find("li.preview > a").addClass("selected");
Dirk Dougherty29e93432015-05-05 18:17:13 -0700197 } else {
198 parentNavEl.removeClass('has-subnav').addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800199 }
Scott Mainac2aef52013-02-12 14:15:23 -0800200 // highlight Distribute tab
201 } else if ($("body").hasClass("distribute")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700202 parentNavEl = navEl.find("> li.distribute > a");
203 parentNavEl.addClass('has-subnav');
Dirk Doughertyc3921652014-05-13 16:55:26 -0700204
Dirk Dougherty29e93432015-05-05 18:17:13 -0700205 if (urlSegments[2] == "users") {
206 subNavEl.find("li.users > a").addClass("selected");
207 } else if (urlSegments[2] == "engage") {
208 subNavEl.find("li.engage > a").addClass("selected");
209 } else if (urlSegments[2] == "monetize") {
210 subNavEl.find("li.monetize > a").addClass("selected");
211 } else if (urlSegments[2] == "analyze") {
212 subNavEl.find("li.analyze > a").addClass("selected");
213 } else if (urlSegments[2] == "tools") {
214 subNavEl.find("li.disttools > a").addClass("selected");
215 } else if (urlSegments[2] == "stories") {
216 subNavEl.find("li.stories > a").addClass("selected");
217 } else if (urlSegments[2] == "essentials") {
218 subNavEl.find("li.essentials > a").addClass("selected");
219 } else if (urlSegments[2] == "googleplay") {
220 subNavEl.find("li.googleplay > a").addClass("selected");
221 } else {
222 parentNavEl.removeClass('has-subnav').addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700223 }
Scott Mainb16376f2014-05-21 20:35:47 -0700224 }
Scott Mainac2aef52013-02-12 14:15:23 -0800225
Scott Mainf6145542013-04-01 16:38:11 -0700226 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
227 // and highlight the sidenav
228 mPagePath = pagePath;
229 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700230 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800231
Scott Mainf6145542013-04-01 16:38:11 -0700232 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700233 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700234 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700235 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800236 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700237
238 // set up prev links
239 var $prevLink = [];
240 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700241
Scott Maine4d8f1b2012-06-21 18:03:05 -0700242 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
243false; // navigate across topic boundaries only in design docs
244 if ($prevListItem.length) {
smain@google.comc91ecb72014-06-23 10:22:23 -0700245 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Scott Main5a1123e2012-09-26 12:51:28 -0700246 // jump to last topic of previous section
247 $prevLink = $prevListItem.find('a:last');
248 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700249 // jump to previous topic in this section
250 $prevLink = $prevListItem.find('a:eq(0)');
251 }
252 } else {
253 // jump to this section's index page (if it exists)
254 var $parentListItem = $selListItem.parents('li');
255 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700256
Scott Maine4d8f1b2012-06-21 18:03:05 -0700257 // except if cross boundaries aren't allowed, and we're at the top of a section already
258 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700259 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700260 && $selListItem.hasClass('nav-section')) {
261 $prevLink = [];
262 }
263 }
264
Scott Maine4d8f1b2012-06-21 18:03:05 -0700265 // set up next links
266 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700267 var startClass = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700268 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700269
Scott Main1a00f7f2013-10-29 11:11:19 -0700270 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700271 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700272 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700273
274 // if there aren't any children, go to the next section (required for About pages)
275 if($nextLink.length == 0) {
276 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700277 } else if ($('.topic-start-link').length) {
278 // as long as there's a child link and there is a "topic start link" (we're on a landing)
279 // then set the landing page "start link" text to be the first doc title
280 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700281 }
Scott Main3b90aff2013-08-01 18:09:35 -0700282
Scott Main5a1123e2012-09-26 12:51:28 -0700283 // If the selected page has a description, then it's a class or article homepage
284 if ($selListItem.find('a[description]').length) {
285 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700286 startClass = true;
287 }
288 } else {
289 // jump to the next topic in this section (if it exists)
290 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700291 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700292 isCrossingBoundary = true;
293 // no more topics in this section, jump to the first topic in the next section
smain@google.comabf34112014-06-23 11:39:02 -0700294 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Scott Main5a1123e2012-09-26 12:51:28 -0700295 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
296 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700297 if ($nextLink.length == 0) {
298 // if that doesn't work, we're at the end of the list, so disable NEXT link
299 $('.next-page-link').attr('href','').addClass("disabled")
300 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700301 // and completely hide the one in the footer
302 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700303 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700304 }
305 }
306 }
Scott Main5a1123e2012-09-26 12:51:28 -0700307
308 if (startClass) {
309 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
310
Scott Main3b90aff2013-08-01 18:09:35 -0700311 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700312 // then we need to add a bottom border to button
313 if (!$("#tb").length) {
314 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700315 }
Scott Main5a1123e2012-09-26 12:51:28 -0700316 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
317 $('.content-footer.next-class').show();
318 $('.next-page-link').attr('href','')
319 .removeClass("hide").addClass("disabled")
320 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700321 // and completely hide the one in the footer
322 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700323 if ($nextLink.length) {
324 $('.next-class-link').attr('href',$nextLink.attr('href'))
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700325 .removeClass("hide")
326 .append(": " + $nextLink.html());
Scott Main1a00f7f2013-10-29 11:11:19 -0700327 $('.next-class-link').find('.new').empty();
328 }
Scott Main5a1123e2012-09-26 12:51:28 -0700329 } else {
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700330 $('.next-page-link').attr('href', $nextLink.attr('href'))
331 .removeClass("hide");
332 // for the footer link, also add the next page title
333 $('.content-footer .next-page-link').append(": " + $nextLink.html());
Scott Main5a1123e2012-09-26 12:51:28 -0700334 }
335
336 if (!startClass && $prevLink.length) {
337 var prevHref = $prevLink.attr('href');
338 if (prevHref == SITE_ROOT + 'index.html') {
339 // Don't show Previous when it leads to the homepage
340 } else {
341 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
342 }
Scott Main3b90aff2013-08-01 18:09:35 -0700343 }
Scott Main5a1123e2012-09-26 12:51:28 -0700344
Scott Maine4d8f1b2012-06-21 18:03:05 -0700345 }
Scott Main3b90aff2013-08-01 18:09:35 -0700346
347
348
Scott Main5a1123e2012-09-26 12:51:28 -0700349 // Set up the course landing pages for Training with class names and descriptions
350 if ($('body.trainingcourse').length) {
351 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700352
353 // create an array for all the class descriptions
354 var $classDescriptions = new Array($classLinks.length);
355 var lang = getLangPref();
356 $classLinks.each(function(index) {
357 var langDescr = $(this).attr(lang + "-description");
358 if (typeof langDescr !== 'undefined' && langDescr !== false) {
359 // if there's a class description in the selected language, use that
360 $classDescriptions[index] = langDescr;
361 } else {
362 // otherwise, use the default english description
363 $classDescriptions[index] = $(this).attr("description");
364 }
365 });
Scott Main3b90aff2013-08-01 18:09:35 -0700366
Scott Main5a1123e2012-09-26 12:51:28 -0700367 var $olClasses = $('<ol class="class-list"></ol>');
368 var $liClass;
Scott Main5a1123e2012-09-26 12:51:28 -0700369 var $h2Title;
370 var $pSummary;
371 var $olLessons;
372 var $liLesson;
373 $classLinks.each(function(index) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700374 $liClass = $('<li class="clearfix"></li>');
Scott Main5a1123e2012-09-26 12:51:28 -0700375 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700376 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700377
Scott Main5a1123e2012-09-26 12:51:28 -0700378 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700379
Scott Main5a1123e2012-09-26 12:51:28 -0700380 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700381
Scott Main5a1123e2012-09-26 12:51:28 -0700382 if ($lessons.length) {
Scott Main5a1123e2012-09-26 12:51:28 -0700383 $lessons.each(function(index) {
384 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
385 });
386 } else {
Scott Main5a1123e2012-09-26 12:51:28 -0700387 $pSummary.addClass('article');
388 }
389
Dirk Dougherty29e93432015-05-05 18:17:13 -0700390 $liClass.append($h2Title).append($pSummary).append($olLessons);
Scott Main5a1123e2012-09-26 12:51:28 -0700391 $olClasses.append($liClass);
392 });
393 $('.jd-descr').append($olClasses);
394 }
395
Scott Maine4d8f1b2012-06-21 18:03:05 -0700396 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700397 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700398
Scott Main3b90aff2013-08-01 18:09:35 -0700399
Scott Maine4d8f1b2012-06-21 18:03:05 -0700400 $(".scroll-pane").scroll(function(event) {
401 event.preventDefault();
402 return false;
403 });
404
405 /* Resize nav height when window height changes */
406 $(window).resize(function() {
407 if ($('#side-nav').length == 0) return;
408 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
409 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
410 // make sidenav behave when resizing the window and side-scolling is a concern
Scott Mainf5257812014-05-22 17:26:38 -0700411 if (sticky) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700412 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
413 updateSideNavPosition();
414 } else {
415 updateSidenavFullscreenWidth();
416 }
417 }
418 resizeNav();
419 });
420
421
Scott Maine4d8f1b2012-06-21 18:03:05 -0700422 var navBarLeftPos;
423 if ($('#devdoc-nav').length) {
424 setNavBarLeftPos();
425 }
426
427
Scott Maine4d8f1b2012-06-21 18:03:05 -0700428 // Set up play-on-hover <video> tags.
429 $('video.play-on-hover').bind('click', function(){
430 $(this).get(0).load(); // in case the video isn't seekable
431 $(this).get(0).play();
432 });
433
434 // Set up tooltips
435 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700436 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700437 var $target = $(this);
438 var $tooltip = $('<div>')
439 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700440 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700441 .hide()
442 .appendTo('body');
443 $target.removeAttr('title');
444
445 $target.hover(function() {
446 // in
447 var targetRect = $target.offset();
448 targetRect.width = $target.width();
449 targetRect.height = $target.height();
450
451 $tooltip.css({
452 left: targetRect.left,
453 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
454 });
455 $tooltip.addClass('below');
456 $tooltip.show();
457 }, function() {
458 // out
459 $tooltip.hide();
460 });
461 });
462
463 // Set up <h2> deeplinks
464 $('h2').click(function() {
465 var id = $(this).attr('id');
466 if (id) {
467 document.location.hash = id;
468 }
469 });
470
471 //Loads the +1 button
472 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
473 po.src = 'https://apis.google.com/js/plusone.js';
474 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
475
Scott Maine4d8f1b2012-06-21 18:03:05 -0700476 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700477
Scott Maine4d8f1b2012-06-21 18:03:05 -0700478 if ($(".scroll-pane").length > 1) {
479 // Check if there's a user preference for the panel heights
480 var cookieHeight = readCookie("reference_height");
481 if (cookieHeight) {
482 restoreHeight(cookieHeight);
483 }
484 }
Scott Main3b90aff2013-08-01 18:09:35 -0700485
Scott Main06f3f2c2014-05-30 11:23:00 -0700486 // Resize once loading is finished
Scott Maine4d8f1b2012-06-21 18:03:05 -0700487 resizeNav();
Scott Main06f3f2c2014-05-30 11:23:00 -0700488 // Check if there's an anchor that we need to scroll into view.
489 // A delay is needed, because some browsers do not immediately scroll down to the anchor
490 window.setTimeout(offsetScrollForSticky, 100);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700491
Scott Main015d6162013-01-29 09:01:52 -0800492 /* init the language selector based on user cookie for lang */
493 loadLangPref();
494 changeNavLang(getLangPref());
495
496 /* setup event handlers to ensure the overflow menu is visible while picking lang */
497 $("#language select")
498 .mousedown(function() {
499 $("div.morehover").addClass("hover"); })
500 .blur(function() {
501 $("div.morehover").removeClass("hover"); });
502
503 /* some global variable setup */
504 resizePackagesNav = $("#resize-packages-nav");
505 classesNav = $("#classes-nav");
506 devdocNav = $("#devdoc-nav");
507
508 var cookiePath = "";
509 if (location.href.indexOf("/reference/") != -1) {
510 cookiePath = "reference_";
511 } else if (location.href.indexOf("/guide/") != -1) {
512 cookiePath = "guide_";
513 } else if (location.href.indexOf("/tools/") != -1) {
514 cookiePath = "tools_";
515 } else if (location.href.indexOf("/training/") != -1) {
516 cookiePath = "training_";
517 } else if (location.href.indexOf("/design/") != -1) {
518 cookiePath = "design_";
519 } else if (location.href.indexOf("/distribute/") != -1) {
520 cookiePath = "distribute_";
521 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700522
smain@google.com698fff02014-11-20 20:39:33 -0800523
524 /* setup shadowbox for any videos that want it */
smain@google.comf75ee212014-11-24 09:42:59 -0800525 var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
smain@google.com698fff02014-11-20 20:39:33 -0800526 if ($videoLinks.length) {
527 // if there's at least one, add the shadowbox HTML to the body
528 $('body').prepend(
529'<div id="video-container">'+
530 '<div id="video-frame">'+
531 '<div class="video-close">'+
532 '<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
533 '</div>'+
534 '<div id="youTubePlayer"></div>'+
535 '</div>'+
536'</div>');
537
538 // loads the IFrame Player API code asynchronously.
539 $.getScript("https://www.youtube.com/iframe_api");
540
541 $videoLinks.each(function() {
542 var videoId = $(this).attr('href').split('?v=')[1];
543 $(this).click(function(event) {
544 event.preventDefault();
545 startYouTubePlayer(videoId);
546 });
547 });
smain@google.com698fff02014-11-20 20:39:33 -0800548 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700549
550 // Responsive testing
551 var responsiveParam = location.href.match(/[?&]responsive=?(|true|false)/);
552 if (responsiveParam) {
553 localStorage['test-responsive'] = ['', 'true'].indexOf(responsiveParam) > -1;
554 }
555 if (localStorage['test-responsive']) {
556 $(document.body).addClass('responsive');
557 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700558});
Scott Main7e447ed2013-02-19 17:22:37 -0800559// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700560
561
smain@google.com698fff02014-11-20 20:39:33 -0800562var youTubePlayer;
563function onYouTubeIframeAPIReady() {
564}
565
smain@google.com3de83c12014-12-12 19:06:52 -0800566/* Returns the height the shadowbox video should be. It's based on the current
567 height of the "video-frame" element, which is 100% height for the window.
568 Then minus the margin so the video isn't actually the full window height. */
569function getVideoHeight() {
570 var frameHeight = $("#video-frame").height();
571 var marginTop = $("#video-frame").css('margin-top').split('px')[0];
572 return frameHeight - (marginTop * 2);
573}
574
smain@google.comd162be52015-02-05 13:27:16 -0800575var mPlayerPaused = false;
576
smain@google.com698fff02014-11-20 20:39:33 -0800577function startYouTubePlayer(videoId) {
smain@google.com3de83c12014-12-12 19:06:52 -0800578 $("#video-container").show();
579 $("#video-frame").show();
smain@google.comd162be52015-02-05 13:27:16 -0800580 mPlayerPaused = false;
smain@google.com3de83c12014-12-12 19:06:52 -0800581
582 // compute the size of the player so it's centered in window
583 var maxWidth = 940; // the width of the web site content
584 var videoAspect = .5625; // based on 1280x720 resolution
585 var maxHeight = maxWidth * videoAspect;
586 var videoHeight = getVideoHeight();
587 var videoWidth = videoHeight / videoAspect;
588 if (videoWidth > maxWidth) {
589 videoWidth = maxWidth;
590 videoHeight = maxHeight;
smain@google.com570c2212014-11-25 19:01:40 -0800591 }
smain@google.com3de83c12014-12-12 19:06:52 -0800592 $("#video-frame").css('width', videoWidth);
593
594 // check if we've already created this player
smain@google.com698fff02014-11-20 20:39:33 -0800595 if (youTubePlayer == null) {
smain@google.com3de83c12014-12-12 19:06:52 -0800596 // check if there's a start time specified
597 var idAndHash = videoId.split("#");
598 var startTime = 0;
599 if (idAndHash.length > 1) {
600 startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0;
601 }
602 // enable localized player
603 var lang = getLangPref();
604 var captionsOn = lang == 'en' ? 0 : 1;
605
smain@google.com698fff02014-11-20 20:39:33 -0800606 youTubePlayer = new YT.Player('youTubePlayer', {
smain@google.com3de83c12014-12-12 19:06:52 -0800607 height: videoHeight,
608 width: videoWidth,
smain@google.com570c2212014-11-25 19:01:40 -0800609 videoId: idAndHash[0],
smain@google.com481f15c2014-12-12 11:57:27 -0800610 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
smain@google.com698fff02014-11-20 20:39:33 -0800611 events: {
smain@google.comf75ee212014-11-24 09:42:59 -0800612 'onReady': onPlayerReady,
613 'onStateChange': onPlayerStateChange
smain@google.com698fff02014-11-20 20:39:33 -0800614 }
615 });
616 } else {
smain@google.com3de83c12014-12-12 19:06:52 -0800617 // reset the size in case the user adjusted the window since last play
618 youTubePlayer.setSize(videoWidth, videoHeight);
smain@google.com94e0b532014-12-16 19:07:08 -0800619 // if a video different from the one already playing was requested, cue it up
smain@google.comd162be52015-02-05 13:27:16 -0800620 if (videoId != youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]) {
smain@google.com94e0b532014-12-16 19:07:08 -0800621 youTubePlayer.cueVideoById(videoId);
622 }
smain@google.com698fff02014-11-20 20:39:33 -0800623 youTubePlayer.playVideo();
624 }
smain@google.com698fff02014-11-20 20:39:33 -0800625}
626
627function onPlayerReady(event) {
628 event.target.playVideo();
smain@google.comd162be52015-02-05 13:27:16 -0800629 mPlayerPaused = false;
smain@google.com698fff02014-11-20 20:39:33 -0800630}
631
632function closeVideo() {
633 try {
smain@google.comf75ee212014-11-24 09:42:59 -0800634 youTubePlayer.pauseVideo();
smain@google.com698fff02014-11-20 20:39:33 -0800635 } catch(e) {
smain@google.com698fff02014-11-20 20:39:33 -0800636 }
smain@google.com3de83c12014-12-12 19:06:52 -0800637 $("#video-container").fadeOut(200);
smain@google.com698fff02014-11-20 20:39:33 -0800638}
639
smain@google.comf75ee212014-11-24 09:42:59 -0800640/* Track youtube playback for analytics */
641function onPlayerStateChange(event) {
642 // Video starts, send the video ID
643 if (event.data == YT.PlayerState.PLAYING) {
smain@google.comd162be52015-02-05 13:27:16 -0800644 if (mPlayerPaused) {
645 ga('send', 'event', 'Videos', 'Resume',
646 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]);
647 } else {
648 // track the start playing event so we know from which page the video was selected
649 ga('send', 'event', 'Videos', 'Start: ' +
650 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
651 'on: ' + document.location.href);
652 }
653 mPlayerPaused = false;
smain@google.comf75ee212014-11-24 09:42:59 -0800654 }
655 // Video paused, send video ID and video elapsed time
656 if (event.data == YT.PlayerState.PAUSED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800657 ga('send', 'event', 'Videos', 'Paused',
smain@google.comd162be52015-02-05 13:27:16 -0800658 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
659 youTubePlayer.getCurrentTime());
660 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800661 }
662 // Video finished, send video ID and video elapsed time
663 if (event.data == YT.PlayerState.ENDED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800664 ga('send', 'event', 'Videos', 'Finished',
smain@google.comd162be52015-02-05 13:27:16 -0800665 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
666 youTubePlayer.getCurrentTime());
667 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800668 }
669}
670
smain@google.com698fff02014-11-20 20:39:33 -0800671
672
Scott Mainad08f072013-08-20 16:49:57 -0700673function initExpandableNavItems(rootTag) {
674 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
675 var section = $(this).closest('li.nav-section');
676 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700677 /* hide me and descendants */
678 section.find('ul').slideUp(250, function() {
679 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700680 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700681 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700682 resizeNav();
683 });
684 } else {
685 /* show me */
686 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700687 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700688 $others.removeClass('expanded').children('ul').slideUp(250);
689
690 // now expand me
691 section.closest('li').addClass('expanded');
692 section.children('ul').slideDown(250, function() {
693 resizeNav();
694 });
695 }
696 });
Scott Mainf0093852013-08-22 11:37:11 -0700697
698 // Stop expand/collapse behavior when clicking on nav section links
699 // (since we're navigating away from the page)
700 // This selector captures the first instance of <a>, but not those with "#" as the href.
701 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
702 window.location.href = $(this).attr('href');
703 return false;
704 });
Scott Mainad08f072013-08-20 16:49:57 -0700705}
706
Dirk Doughertyc3921652014-05-13 16:55:26 -0700707
708/** Create the list of breadcrumb links in the sticky header */
709function buildBreadcrumbs() {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700710 var $breadcrumbUl = $(".dac-header-crumbs");
711 var primaryNavLink = ".dac-nav-list > .dac-nav-item > .dac-nav-link";
712
Dirk Doughertyc3921652014-05-13 16:55:26 -0700713 // Add the secondary horizontal nav item, if provided
Dirk Dougherty29e93432015-05-05 18:17:13 -0700714 var $selectedSecondNav = $(".dac-nav-secondary .dac-nav-link.selected").clone()
715 .attr('class', 'dac-header-crumbs-link');
716
Dirk Doughertyc3921652014-05-13 16:55:26 -0700717 if ($selectedSecondNav.length) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700718 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedSecondNav));
Dirk Doughertyc3921652014-05-13 16:55:26 -0700719 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700720
Dirk Doughertyc3921652014-05-13 16:55:26 -0700721 // Add the primary horizontal nav
Dirk Dougherty29e93432015-05-05 18:17:13 -0700722 var $selectedFirstNav = $(primaryNavLink + ".selected, " + primaryNavLink + ".has-subnav").clone()
723 .attr('class', 'dac-header-crumbs-link');
724
Dirk Doughertyc3921652014-05-13 16:55:26 -0700725 // If there's no header nav item, use the logo link and title from alt text
726 if ($selectedFirstNav.length < 1) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700727 $selectedFirstNav = $('<a class="dac-header-crumbs-link">')
Dirk Doughertyc3921652014-05-13 16:55:26 -0700728 .attr('href', $("div#header .logo a").attr('href'))
729 .text($("div#header .logo img").attr('alt'));
730 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700731 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedFirstNav));
Dirk Doughertyc3921652014-05-13 16:55:26 -0700732}
733
734
735
Scott Maine624b3f2013-09-12 12:56:41 -0700736/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700737function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700738 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
739 if ($("ul#nav li.selected").length) {
740 unHighlightSidenav();
741 }
742 // look for URL in sidenav, including the hash
743 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
744
745 // If the selNavLink is still empty, look for it without the hash
746 if ($selNavLink.length == 0) {
747 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
748 }
749
Scott Mainf6145542013-04-01 16:38:11 -0700750 var $selListItem;
751 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700752 // Find this page's <li> in sidenav and set selected
753 $selListItem = $selNavLink.closest('li');
754 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700755
Scott Mainf6145542013-04-01 16:38:11 -0700756 // Traverse up the tree and expand all parent nav-sections
757 $selNavLink.parents('li.nav-section').each(function() {
758 $(this).addClass('expanded');
759 $(this).children('ul').show();
760 });
761 }
762}
763
Scott Maine624b3f2013-09-12 12:56:41 -0700764function unHighlightSidenav() {
765 $("ul#nav li.selected").removeClass("selected");
766 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
767}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700768
769function toggleFullscreen(enable) {
770 var delay = 20;
771 var enabled = true;
772 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
773 if (enable) {
774 // Currently NOT USING fullscreen; enable fullscreen
775 stylesheet.removeAttr('disabled');
776 $('#nav-swap .fullscreen').removeClass('disabled');
777 $('#devdoc-nav').css({left:''});
778 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
779 enabled = true;
780 } else {
781 // Currently USING fullscreen; disable fullscreen
782 stylesheet.attr('disabled', 'disabled');
783 $('#nav-swap .fullscreen').addClass('disabled');
784 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
785 enabled = false;
786 }
smain@google.com6bdcb982014-11-14 11:53:07 -0800787 writeCookie("fullscreen", enabled, null);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700788 setNavBarLeftPos();
789 resizeNav(delay);
790 updateSideNavPosition();
791 setTimeout(initSidenavHeightResize,delay);
792}
793
794
795function setNavBarLeftPos() {
796 navBarLeftPos = $('#body-content').offset().left;
797}
798
799
800function updateSideNavPosition() {
801 var newLeft = $(window).scrollLeft() - navBarLeftPos;
802 $('#devdoc-nav').css({left: -newLeft});
Dirk Dougherty29e93432015-05-05 18:17:13 -0700803 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('padding-left')))});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700804}
Scott Main3b90aff2013-08-01 18:09:35 -0700805
Scott Maine4d8f1b2012-06-21 18:03:05 -0700806// TODO: use $(document).ready instead
807function addLoadEvent(newfun) {
808 var current = window.onload;
809 if (typeof window.onload != 'function') {
810 window.onload = newfun;
811 } else {
812 window.onload = function() {
813 current();
814 newfun();
815 }
816 }
817}
818
819var agent = navigator['userAgent'].toLowerCase();
820// If a mobile phone, set flag and do mobile setup
821if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
822 (agent.indexOf("blackberry") != -1) ||
823 (agent.indexOf("webos") != -1) ||
824 (agent.indexOf("mini") != -1)) { // opera mini browsers
825 isMobile = true;
826}
827
828
Scott Main498d7102013-08-21 15:47:38 -0700829$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700830 $("pre:not(.no-pretty-print)").addClass("prettyprint");
831 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700832});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700833
Scott Maine4d8f1b2012-06-21 18:03:05 -0700834
835
836
837/* ######### RESIZE THE SIDENAV HEIGHT ########## */
838
839function resizeNav(delay) {
840 var $nav = $("#devdoc-nav");
841 var $window = $(window);
842 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700843
Scott Maine4d8f1b2012-06-21 18:03:05 -0700844 // Get the height of entire window and the total header height.
845 // Then figure out based on scroll position whether the header is visible
846 var windowHeight = $window.height();
847 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700848 var headerHeight = $('#header-wrapper').outerHeight();
849 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700850
851 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700852 // Could be either margin or top position, depending on whether the nav is fixed.
Scott Main3b90aff2013-08-01 18:09:35 -0700853 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700854 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700855
Scott Maine4d8f1b2012-06-21 18:03:05 -0700856 // Depending on whether the header is visible, set the side nav's height.
857 if (headerVisible) {
858 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700859 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700860 } else {
861 // Once header is off screen, the nav height is almost full window height
862 navHeight = windowHeight - topMargin;
863 }
Scott Main3b90aff2013-08-01 18:09:35 -0700864
865
866
Scott Maine4d8f1b2012-06-21 18:03:05 -0700867 $scrollPanes = $(".scroll-pane");
868 if ($scrollPanes.length > 1) {
869 // subtract the height of the api level widget and nav swapper from the available nav height
870 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700871
Scott Maine4d8f1b2012-06-21 18:03:05 -0700872 $("#swapper").css({height:navHeight + "px"});
873 if ($("#nav-tree").is(":visible")) {
874 $("#nav-tree").css({height:navHeight});
875 }
Scott Main3b90aff2013-08-01 18:09:35 -0700876
877 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700878 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700879
880 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700881 // then the package panel should begin to shrink
882 if (parseInt(classesHeight) <= 0) {
883 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
884 $("#packages-nav").css({height:navHeight - 10});
885 }
Scott Main3b90aff2013-08-01 18:09:35 -0700886
Scott Maine4d8f1b2012-06-21 18:03:05 -0700887 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
888 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700889
890
Scott Maine4d8f1b2012-06-21 18:03:05 -0700891 } else {
892 $nav.height(navHeight);
893 }
Scott Main3b90aff2013-08-01 18:09:35 -0700894
Scott Maine4d8f1b2012-06-21 18:03:05 -0700895 if (delay) {
896 updateFromResize = true;
897 delayedReInitScrollbars(delay);
898 } else {
899 reInitScrollbars();
900 }
Scott Main3b90aff2013-08-01 18:09:35 -0700901
Scott Maine4d8f1b2012-06-21 18:03:05 -0700902}
903
904var updateScrollbars = false;
905var updateFromResize = false;
906
907/* Re-initialize the scrollbars to account for changed nav size.
908 * This method postpones the actual update by a 1/4 second in order to optimize the
909 * scroll performance while the header is still visible, because re-initializing the
910 * scroll panes is an intensive process.
911 */
912function delayedReInitScrollbars(delay) {
913 // If we're scheduled for an update, but have received another resize request
914 // before the scheduled resize has occured, just ignore the new request
915 // (and wait for the scheduled one).
916 if (updateScrollbars && updateFromResize) {
917 updateFromResize = false;
918 return;
919 }
Scott Main3b90aff2013-08-01 18:09:35 -0700920
Scott Maine4d8f1b2012-06-21 18:03:05 -0700921 // We're scheduled for an update and the update request came from this method's setTimeout
922 if (updateScrollbars && !updateFromResize) {
923 reInitScrollbars();
924 updateScrollbars = false;
925 } else {
926 updateScrollbars = true;
927 updateFromResize = false;
928 setTimeout('delayedReInitScrollbars()',delay);
929 }
930}
931
932/* Re-initialize the scrollbars to account for changed nav size. */
933function reInitScrollbars() {
934 var pane = $(".scroll-pane").each(function(){
935 var api = $(this).data('jsp');
936 if (!api) { setTimeout(reInitScrollbars,300); return;}
937 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700938 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700939 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
940}
941
942
943/* Resize the height of the nav panels in the reference,
944 * and save the new size to a cookie */
945function saveNavPanels() {
946 var basePath = getBaseUri(location.pathname);
947 var section = basePath.substring(1,basePath.indexOf("/",1));
smain@google.com6bdcb982014-11-14 11:53:07 -0800948 writeCookie("height", resizePackagesNav.css("height"), section);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700949}
950
951
952
953function restoreHeight(packageHeight) {
954 $("#resize-packages-nav").height(packageHeight);
955 $("#packages-nav").height(packageHeight);
956 // var classesHeight = navHeight - packageHeight;
957 // $("#classes-nav").css({height:classesHeight});
958 // $("#classes-nav .jspContainer").css({height:classesHeight});
959}
960
961
962
963/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
964
965
966
967
968
Scott Main3b90aff2013-08-01 18:09:35 -0700969/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700970 This is called when the page finished loading. */
971function scrollIntoView(nav) {
972 var $nav = $("#"+nav);
973 var element = $nav.jScrollPane({/* ...settings... */});
974 var api = element.data('jsp');
975
976 if ($nav.is(':visible')) {
977 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700978 if ($selected.length == 0) {
979 // If no selected item found, exit
980 return;
981 }
Scott Main52dd2062013-08-15 12:22:28 -0700982 // get the selected item's offset from its container nav by measuring the item's offset
983 // relative to the document then subtract the container nav's offset relative to the document
984 var selectedOffset = $selected.offset().top - $nav.offset().top;
985 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
986 // if it's more than 80% down the nav
987 // scroll the item up by an amount equal to 80% the container nav's height
988 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700989 }
990 }
991}
992
993
994
995
996
997
998/* Show popup dialogs */
999function showDialog(id) {
1000 $dialog = $("#"+id);
1001 $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>');
1002 $dialog.wrapInner('<div/>');
1003 $dialog.removeClass("hide");
1004}
1005
1006
1007
1008
1009
1010/* ######### COOKIES! ########## */
1011
1012function readCookie(cookie) {
1013 var myCookie = cookie_namespace+"_"+cookie+"=";
1014 if (document.cookie) {
1015 var index = document.cookie.indexOf(myCookie);
1016 if (index != -1) {
1017 var valStart = index + myCookie.length;
1018 var valEnd = document.cookie.indexOf(";", valStart);
1019 if (valEnd == -1) {
1020 valEnd = document.cookie.length;
1021 }
1022 var val = document.cookie.substring(valStart, valEnd);
1023 return val;
1024 }
1025 }
1026 return 0;
1027}
1028
smain@google.com6bdcb982014-11-14 11:53:07 -08001029function writeCookie(cookie, val, section) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001030 if (val==undefined) return;
1031 section = section == null ? "_" : "_"+section+"_";
smain@google.com6bdcb982014-11-14 11:53:07 -08001032 var age = 2*365*24*60*60; // set max-age to 2 years
Scott Main3b90aff2013-08-01 18:09:35 -07001033 var cookieValue = cookie_namespace + section + cookie + "=" + val
smain@google.com80e38f42014-11-03 10:47:12 -08001034 + "; max-age=" + age +"; path=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -07001035 document.cookie = cookieValue;
1036}
1037
1038/* ######### END COOKIES! ########## */
1039
1040
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001041var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -07001042var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001043var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -07001044/* Sets the vertical scoll position at which the sticky bar should appear.
1045 This method is called to reset the position when search results appear or hide */
1046function setStickyTop() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001047 stickyTop = $('#header-wrapper').outerHeight() - $('#header > .dac-header-inner').outerHeight();
Dirk Doughertyc3921652014-05-13 16:55:26 -07001048}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001049
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001050/*
Scott Mainb16376f2014-05-21 20:35:47 -07001051 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -07001052 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001053$(window).scroll(function(event) {
1054
1055 setStickyTop();
1056 var hiding = false;
Dirk Dougherty29e93432015-05-05 18:17:13 -07001057 var $headerEl = $('#header');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001058 // Exit if there's no sidenav
1059 if ($('#side-nav').length == 0) return;
1060 // Exit if the mouse target is a DIV, because that means the event is coming
1061 // from a scrollable div and so there's no need to make adjustments to our layout
1062 if ($(event.target).nodeName == "DIV") {
1063 return;
1064 }
1065
1066 var top = $(window).scrollTop();
1067 // we set the navbar fixed when the scroll position is beyond the height of the site header...
1068 var shouldBeSticky = top >= stickyTop;
1069 // ... except if the document content is shorter than the sidenav height.
1070 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1071 if ($("#doc-col").height() < $("#side-nav").height()) {
1072 shouldBeSticky = false;
1073 }
Scott Mainf5257812014-05-22 17:26:38 -07001074 // Account for horizontal scroll
1075 var scrollLeft = $(window).scrollLeft();
1076 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1077 if (sticky && (scrollLeft != prevScrollLeft)) {
1078 updateSideNavPosition();
1079 prevScrollLeft = scrollLeft;
1080 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001081
1082 // Don't continue if the header is sufficently far away
1083 // (to avoid intensive resizing that slows scrolling)
1084 if (sticky == shouldBeSticky) {
1085 return;
1086 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001087
1088 // If sticky header visible and position is now near top, hide sticky
1089 if (sticky && !shouldBeSticky) {
1090 sticky = false;
1091 hiding = true;
1092 // make the sidenav static again
1093 $('#devdoc-nav')
1094 .removeClass('fixed')
1095 .css({'width':'auto','margin':''})
1096 .prependTo('#side-nav');
1097 // delay hide the sticky
Dirk Dougherty29e93432015-05-05 18:17:13 -07001098 $headerEl.removeClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001099 hiding = false;
1100
1101 // update the sidenaav position for side scrolling
1102 updateSideNavPosition();
1103 } else if (!sticky && shouldBeSticky) {
1104 sticky = true;
Dirk Dougherty29e93432015-05-05 18:17:13 -07001105 $headerEl.addClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001106
1107 // make the sidenav fixed
1108 var width = $('#devdoc-nav').width();
1109 $('#devdoc-nav')
1110 .addClass('fixed')
1111 .css({'width':width+'px'})
1112 .prependTo('#body-content');
1113
1114 // update the sidenaav position for side scrolling
1115 updateSideNavPosition();
1116
1117 } else if (hiding && top < 15) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001118 $headerEl.removeClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001119 hiding = false;
1120 }
1121 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
1122});
1123
1124/*
1125 * Manages secion card states and nav resize to conclude loading
1126 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001127(function() {
1128 $(document).ready(function() {
1129
Dirk Doughertyc3921652014-05-13 16:55:26 -07001130 // Stack hover states
1131 $('.section-card-menu').each(function(index, el) {
1132 var height = $(el).height();
1133 $(el).css({height:height+'px', position:'relative'});
1134 var $cardInfo = $(el).find('.card-info');
1135
1136 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1137 });
1138
Dirk Doughertyc3921652014-05-13 16:55:26 -07001139 });
1140
1141})();
1142
Scott Maine4d8f1b2012-06-21 18:03:05 -07001143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
Scott Maind7026f72013-06-17 15:08:49 -07001156/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001157
1158
1159
1160
1161
1162function toggle(obj, slide) {
1163 var ul = $("ul:first", obj);
1164 var li = ul.parent();
1165 if (li.hasClass("closed")) {
1166 if (slide) {
1167 ul.slideDown("fast");
1168 } else {
1169 ul.show();
1170 }
1171 li.removeClass("closed");
1172 li.addClass("open");
1173 $(".toggle-img", li).attr("title", "hide pages");
1174 } else {
1175 ul.slideUp("fast");
1176 li.removeClass("open");
1177 li.addClass("closed");
1178 $(".toggle-img", li).attr("title", "show pages");
1179 }
1180}
1181
1182
Scott Maine4d8f1b2012-06-21 18:03:05 -07001183function buildToggleLists() {
1184 $(".toggle-list").each(
1185 function(i) {
1186 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1187 $(this).addClass("closed");
1188 });
1189}
1190
1191
1192
Scott Maind7026f72013-06-17 15:08:49 -07001193function hideNestedItems(list, toggle) {
1194 $list = $(list);
1195 // hide nested lists
1196 if($list.hasClass('showing')) {
1197 $("li ol", $list).hide('fast');
1198 $list.removeClass('showing');
1199 // show nested lists
1200 } else {
1201 $("li ol", $list).show('fast');
1202 $list.addClass('showing');
1203 }
1204 $(".more,.less",$(toggle)).toggle();
1205}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001206
1207
smain@google.com95948b82014-06-16 19:24:25 -07001208/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1209function setupIdeDocToggle() {
1210 $( "select.ide" ).change(function() {
1211 var selected = $(this).find("option:selected").attr("value");
1212 $(".select-ide").hide();
1213 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001214
smain@google.com95948b82014-06-16 19:24:25 -07001215 $("select.ide").val(selected);
1216 });
1217}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242/* REFERENCE NAV SWAP */
1243
1244
1245function getNavPref() {
1246 var v = readCookie('reference_nav');
1247 if (v != NAV_PREF_TREE) {
1248 v = NAV_PREF_PANELS;
1249 }
1250 return v;
1251}
1252
1253function chooseDefaultNav() {
1254 nav_pref = getNavPref();
1255 if (nav_pref == NAV_PREF_TREE) {
1256 $("#nav-panels").toggle();
1257 $("#panel-link").toggle();
1258 $("#nav-tree").toggle();
1259 $("#tree-link").toggle();
1260 }
1261}
1262
1263function swapNav() {
1264 if (nav_pref == NAV_PREF_TREE) {
1265 nav_pref = NAV_PREF_PANELS;
1266 } else {
1267 nav_pref = NAV_PREF_TREE;
1268 init_default_navtree(toRoot);
1269 }
smain@google.com6bdcb982014-11-14 11:53:07 -08001270 writeCookie("nav", nav_pref, "reference");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001271
1272 $("#nav-panels").toggle();
1273 $("#panel-link").toggle();
1274 $("#nav-tree").toggle();
1275 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001276
Scott Maine4d8f1b2012-06-21 18:03:05 -07001277 resizeNav();
1278
1279 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1280 $("#nav-tree .jspContainer:visible")
1281 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1282 // Another nasty hack to make the scrollbar appear now that we have height
1283 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001284
Scott Maine4d8f1b2012-06-21 18:03:05 -07001285 if ($("#nav-tree").is(':visible')) {
1286 scrollIntoView("nav-tree");
1287 } else {
1288 scrollIntoView("packages-nav");
1289 scrollIntoView("classes-nav");
1290 }
1291}
1292
1293
1294
Scott Mainf5089842012-08-14 16:31:07 -07001295/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001296/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001297/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001298
1299function getBaseUri(uri) {
1300 var intlUrl = (uri.substring(0,6) == "/intl/");
1301 if (intlUrl) {
1302 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1303 base = base.substring(base.indexOf('/')+1, base.length);
1304 //alert("intl, returning base url: /" + base);
1305 return ("/" + base);
1306 } else {
1307 //alert("not intl, returning uri as found.");
1308 return uri;
1309 }
1310}
1311
1312function requestAppendHL(uri) {
1313//append "?hl=<lang> to an outgoing request (such as to blog)
1314 var lang = getLangPref();
1315 if (lang) {
1316 var q = 'hl=' + lang;
1317 uri += '?' + q;
1318 window.location = uri;
1319 return false;
1320 } else {
1321 return true;
1322 }
1323}
1324
1325
Scott Maine4d8f1b2012-06-21 18:03:05 -07001326function changeNavLang(lang) {
Scott Main6eb95f12012-10-02 17:12:23 -07001327 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1328 $links.each(function(i){ // for each link with a translation
1329 var $link = $(this);
1330 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1331 // put the desired language from the attribute as the text
1332 $link.text($link.attr(lang+"-lang"))
Scott Maine4d8f1b2012-06-21 18:03:05 -07001333 }
Scott Main6eb95f12012-10-02 17:12:23 -07001334 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001335}
1336
Scott Main015d6162013-01-29 09:01:52 -08001337function changeLangPref(lang, submit) {
smain@google.com6bdcb982014-11-14 11:53:07 -08001338 writeCookie("pref_lang", lang, null);
Scott Main015d6162013-01-29 09:01:52 -08001339
1340 // ####### TODO: Remove this condition once we're stable on devsite #######
1341 // This condition is only needed if we still need to support legacy GAE server
1342 if (devsite) {
1343 // Switch language when on Devsite server
1344 if (submit) {
1345 $("#setlang").submit();
1346 }
1347 } else {
1348 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001349 if (submit) {
1350 window.location = getBaseUri(location.pathname);
1351 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001352 }
1353}
1354
1355function loadLangPref() {
1356 var lang = readCookie("pref_lang");
1357 if (lang != 0) {
1358 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1359 }
1360}
1361
1362function getLangPref() {
1363 var lang = $("#language").find(":selected").attr("value");
1364 if (!lang) {
1365 lang = readCookie("pref_lang");
1366 }
1367 return (lang != 0) ? lang : 'en';
1368}
1369
1370/* ########## END LOCALIZATION ############ */
1371
1372
1373
1374
1375
1376
1377/* Used to hide and reveal supplemental content, such as long code samples.
1378 See the companion CSS in android-developer-docs.css */
1379function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001380 var div = $(obj).closest(".toggle-content");
1381 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001382 if (div.hasClass("closed")) { // if it's closed, open it
1383 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001384 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001385 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001386 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001387 + "assets/images/triangle-opened.png");
1388 } else { // if it's open, close it
1389 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001390 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001391 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001392 div.find(".toggle-content").removeClass("open").addClass("closed")
1393 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001394 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001395 + "assets/images/triangle-closed.png");
1396 });
1397 }
1398 return false;
1399}
Scott Mainf5089842012-08-14 16:31:07 -07001400
1401
Scott Maindb3678b2012-10-23 14:13:41 -07001402/* New version of expandable content */
1403function toggleExpandable(link,id) {
1404 if($(id).is(':visible')) {
1405 $(id).slideUp();
1406 $(link).removeClass('expanded');
1407 } else {
1408 $(id).slideDown();
1409 $(link).addClass('expanded');
1410 }
1411}
1412
1413function hideExpandable(ids) {
1414 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001415 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001416}
1417
Scott Mainf5089842012-08-14 16:31:07 -07001418
1419
1420
1421
Scott Main3b90aff2013-08-01 18:09:35 -07001422/*
Scott Mainf5089842012-08-14 16:31:07 -07001423 * Slideshow 1.0
1424 * Used on /index.html and /develop/index.html for carousel
1425 *
1426 * Sample usage:
1427 * HTML -
1428 * <div class="slideshow-container">
1429 * <a href="" class="slideshow-prev">Prev</a>
1430 * <a href="" class="slideshow-next">Next</a>
1431 * <ul>
1432 * <li class="item"><img src="images/marquee1.jpg"></li>
1433 * <li class="item"><img src="images/marquee2.jpg"></li>
1434 * <li class="item"><img src="images/marquee3.jpg"></li>
1435 * <li class="item"><img src="images/marquee4.jpg"></li>
1436 * </ul>
1437 * </div>
1438 *
1439 * <script type="text/javascript">
1440 * $('.slideshow-container').dacSlideshow({
1441 * auto: true,
1442 * btnPrev: '.slideshow-prev',
1443 * btnNext: '.slideshow-next'
1444 * });
1445 * </script>
1446 *
1447 * Options:
1448 * btnPrev: optional identifier for previous button
1449 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001450 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001451 * auto: whether or not to auto-proceed
1452 * speed: animation speed
1453 * autoTime: time between auto-rotation
1454 * easing: easing function for transition
1455 * start: item to select by default
1456 * scroll: direction to scroll in
1457 * pagination: whether or not to include dotted pagination
1458 *
1459 */
1460
1461 (function($) {
1462 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001463
Scott Mainf5089842012-08-14 16:31:07 -07001464 //Options - see above
1465 o = $.extend({
1466 btnPrev: null,
1467 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001468 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001469 auto: true,
1470 speed: 500,
1471 autoTime: 12000,
1472 easing: null,
1473 start: 0,
1474 scroll: 1,
1475 pagination: true
1476
1477 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001478
1479 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001480 return this.each(function() {
1481
1482 var running = false;
1483 var animCss = o.vertical ? "top" : "left";
1484 var sizeCss = o.vertical ? "height" : "width";
1485 var div = $(this);
1486 var ul = $("ul", div);
1487 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001488 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001489 var timer = null;
1490
1491 var li = $("li", ul);
1492 var itemLength = li.size();
1493 var curr = o.start;
1494
1495 li.css({float: o.vertical ? "none" : "left"});
1496 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1497 div.css({position: "relative", "z-index": "2", left: "0px"});
1498
1499 var liSize = o.vertical ? height(li) : width(li);
1500 var ulSize = liSize * itemLength;
1501 var divSize = liSize;
1502
1503 li.css({width: li.width(), height: li.height()});
1504 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1505
1506 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001507
Scott Mainf5089842012-08-14 16:31:07 -07001508 //Pagination
1509 if (o.pagination) {
1510 var pagination = $("<div class='pagination'></div>");
1511 var pag_ul = $("<ul></ul>");
1512 if (tl > 1) {
1513 for (var i=0;i<tl;i++) {
1514 var li = $("<li>"+i+"</li>");
1515 pag_ul.append(li);
1516 if (i==o.start) li.addClass('active');
1517 li.click(function() {
1518 go(parseInt($(this).text()));
1519 })
1520 }
1521 pagination.append(pag_ul);
1522 div.append(pagination);
1523 }
1524 }
Scott Main3b90aff2013-08-01 18:09:35 -07001525
Scott Mainf5089842012-08-14 16:31:07 -07001526 //Previous button
1527 if(o.btnPrev)
1528 $(o.btnPrev).click(function(e) {
1529 e.preventDefault();
1530 return go(curr-o.scroll);
1531 });
1532
1533 //Next button
1534 if(o.btnNext)
1535 $(o.btnNext).click(function(e) {
1536 e.preventDefault();
1537 return go(curr+o.scroll);
1538 });
Scott Maineb410352013-01-14 19:03:40 -08001539
1540 //Pause button
1541 if(o.btnPause)
1542 $(o.btnPause).click(function(e) {
1543 e.preventDefault();
1544 if ($(this).hasClass('paused')) {
1545 startRotateTimer();
1546 } else {
1547 pauseRotateTimer();
1548 }
1549 });
Scott Main3b90aff2013-08-01 18:09:35 -07001550
Scott Mainf5089842012-08-14 16:31:07 -07001551 //Auto rotation
1552 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001553
Scott Mainf5089842012-08-14 16:31:07 -07001554 function startRotateTimer() {
1555 clearInterval(timer);
1556 timer = setInterval(function() {
1557 if (curr == tl-1) {
1558 go(0);
1559 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001560 go(curr+o.scroll);
1561 }
Scott Mainf5089842012-08-14 16:31:07 -07001562 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001563 $(o.btnPause).removeClass('paused');
1564 }
1565
1566 function pauseRotateTimer() {
1567 clearInterval(timer);
1568 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001569 }
1570
1571 //Go to an item
1572 function go(to) {
1573 if(!running) {
1574
1575 if(to<0) {
1576 to = itemLength-1;
1577 } else if (to>itemLength-1) {
1578 to = 0;
1579 }
1580 curr = to;
1581
1582 running = true;
1583
1584 ul.animate(
1585 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1586 function() {
1587 running = false;
1588 }
1589 );
1590
1591 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1592 $( (curr-o.scroll<0 && o.btnPrev)
1593 ||
1594 (curr+o.scroll > itemLength && o.btnNext)
1595 ||
1596 []
1597 ).addClass("disabled");
1598
Scott Main3b90aff2013-08-01 18:09:35 -07001599
Scott Mainf5089842012-08-14 16:31:07 -07001600 var nav_items = $('li', pagination);
1601 nav_items.removeClass('active');
1602 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001603
Scott Mainf5089842012-08-14 16:31:07 -07001604
1605 }
1606 if(o.auto) startRotateTimer();
1607 return false;
1608 };
1609 });
1610 };
1611
1612 function css(el, prop) {
1613 return parseInt($.css(el[0], prop)) || 0;
1614 };
1615 function width(el) {
1616 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1617 };
1618 function height(el) {
1619 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1620 };
1621
1622 })(jQuery);
1623
1624
Scott Main3b90aff2013-08-01 18:09:35 -07001625/*
Scott Mainf5089842012-08-14 16:31:07 -07001626 * dacSlideshow 1.0
1627 * Used on develop/index.html for side-sliding tabs
1628 *
1629 * Sample usage:
1630 * HTML -
1631 * <div class="slideshow-container">
1632 * <a href="" class="slideshow-prev">Prev</a>
1633 * <a href="" class="slideshow-next">Next</a>
1634 * <ul>
1635 * <li class="item"><img src="images/marquee1.jpg"></li>
1636 * <li class="item"><img src="images/marquee2.jpg"></li>
1637 * <li class="item"><img src="images/marquee3.jpg"></li>
1638 * <li class="item"><img src="images/marquee4.jpg"></li>
1639 * </ul>
1640 * </div>
1641 *
1642 * <script type="text/javascript">
1643 * $('.slideshow-container').dacSlideshow({
1644 * auto: true,
1645 * btnPrev: '.slideshow-prev',
1646 * btnNext: '.slideshow-next'
1647 * });
1648 * </script>
1649 *
1650 * Options:
1651 * btnPrev: optional identifier for previous button
1652 * btnNext: optional identifier for next button
1653 * auto: whether or not to auto-proceed
1654 * speed: animation speed
1655 * autoTime: time between auto-rotation
1656 * easing: easing function for transition
1657 * start: item to select by default
1658 * scroll: direction to scroll in
1659 * pagination: whether or not to include dotted pagination
1660 *
1661 */
1662 (function($) {
1663 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001664
Scott Mainf5089842012-08-14 16:31:07 -07001665 //Options - see above
1666 o = $.extend({
1667 speed : 250,
1668 easing: null,
1669 nav_id: null,
1670 frame_id: null
1671 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001672
1673 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001674 return this.each(function() {
1675
1676 var curr = 0;
1677 var running = false;
1678 var animCss = "margin-left";
1679 var sizeCss = "width";
1680 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001681
Scott Mainf5089842012-08-14 16:31:07 -07001682 var nav = $(o.nav_id, div);
1683 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001684 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001685 var frame = div.find(o.frame_id);
1686 var content_width = $(frame).find('ul').width();
1687 //Buttons
1688 $(nav_li).click(function(e) {
1689 go($(nav_li).index($(this)));
1690 })
Scott Main3b90aff2013-08-01 18:09:35 -07001691
Scott Mainf5089842012-08-14 16:31:07 -07001692 //Go to an item
1693 function go(to) {
1694 if(!running) {
1695 curr = to;
1696 running = true;
1697
1698 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1699 function() {
1700 running = false;
1701 }
1702 );
1703
Scott Main3b90aff2013-08-01 18:09:35 -07001704
Scott Mainf5089842012-08-14 16:31:07 -07001705 nav_li.removeClass('active');
1706 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001707
Scott Mainf5089842012-08-14 16:31:07 -07001708
1709 }
1710 return false;
1711 };
1712 });
1713 };
1714
1715 function css(el, prop) {
1716 return parseInt($.css(el[0], prop)) || 0;
1717 };
1718 function width(el) {
1719 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1720 };
1721 function height(el) {
1722 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1723 };
1724
1725 })(jQuery);
1726
1727
1728
1729
1730
1731/* ######################################################## */
1732/* ################ SEARCH SUGGESTIONS ################## */
1733/* ######################################################## */
1734
1735
Scott Main7e447ed2013-02-19 17:22:37 -08001736
Scott Main0e76e7e2013-03-12 10:24:07 -07001737var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1738var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1739
Scott Mainf5089842012-08-14 16:31:07 -07001740var gMatches = new Array();
1741var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001742var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001743var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1744var gListLength = 0;
1745
1746
1747var gGoogleMatches = new Array();
1748var ROW_COUNT_GOOGLE = 15; // max number of results in list
1749var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001750
Scott Main0e76e7e2013-03-12 10:24:07 -07001751var gDocsMatches = new Array();
1752var ROW_COUNT_DOCS = 100; // max number of results in list
1753var gDocsListLength = 0;
1754
Scott Mainde295272013-03-25 15:48:35 -07001755function onSuggestionClick(link) {
1756 // When user clicks a suggested document, track it
smain@google.comed677d72014-12-12 10:19:17 -08001757 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
1758 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07001759}
1760
Scott Mainf5089842012-08-14 16:31:07 -07001761function set_item_selected($li, selected)
1762{
1763 if (selected) {
1764 $li.attr('class','jd-autocomplete jd-selected');
1765 } else {
1766 $li.attr('class','jd-autocomplete');
1767 }
1768}
1769
1770function set_item_values(toroot, $li, match)
1771{
1772 var $link = $('a',$li);
1773 $link.html(match.__hilabel || match.label);
1774 $link.attr('href',toroot + match.link);
1775}
1776
Scott Main719acb42013-12-05 16:05:09 -08001777function set_item_values_jd(toroot, $li, match)
1778{
1779 var $link = $('a',$li);
1780 $link.html(match.title);
1781 $link.attr('href',toroot + match.url);
1782}
1783
Scott Main0e76e7e2013-03-12 10:24:07 -07001784function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001785 var $li = $("<li class='jd-autocomplete'></li>");
1786 $list.append($li);
1787
1788 $li.mousedown(function() {
1789 window.location = this.firstChild.getAttribute("href");
1790 });
1791 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001792 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001793 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001794 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1795 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001796 });
Scott Mainde295272013-03-25 15:48:35 -07001797 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001798 $li.attr('class','show-item');
1799 return $li;
1800}
1801
Scott Mainf5089842012-08-14 16:31:07 -07001802function sync_selection_table(toroot)
1803{
Scott Mainf5089842012-08-14 16:31:07 -07001804 var $li; //list item jquery object
1805 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001806
Scott Main0e76e7e2013-03-12 10:24:07 -07001807 // if there are NO results at all, hide all columns
1808 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1809 $('.suggest-card').hide(300);
1810 return;
1811 }
1812
1813 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001814 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001815 // reveal suggestion list
Scott Main0e76e7e2013-03-12 10:24:07 -07001816 $('.suggest-card.reference').show();
1817 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001818
Scott Main0e76e7e2013-03-12 10:24:07 -07001819 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001820 $(".suggest-card.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001821
Scott Main0e76e7e2013-03-12 10:24:07 -07001822 // ########### ANDROID RESULTS #############
1823 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001824
Scott Main0e76e7e2013-03-12 10:24:07 -07001825 // determine android results to show
1826 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1827 gMatches.length : ROW_COUNT_FRAMEWORK;
1828 for (i=0; i<gListLength; i++) {
1829 var $li = new_suggestion($(".suggest-card.reference ul"));
1830 set_item_values(toroot, $li, gMatches[i]);
1831 set_item_selected($li, i == gSelectedIndex);
1832 }
1833 }
Scott Main7e447ed2013-02-19 17:22:37 -08001834
Scott Main0e76e7e2013-03-12 10:24:07 -07001835 // ########### GOOGLE RESULTS #############
1836 if (gGoogleMatches.length > 0) {
1837 // show header for list
1838 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001839
Scott Main0e76e7e2013-03-12 10:24:07 -07001840 // determine google results to show
1841 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1842 for (i=0; i<gGoogleListLength; i++) {
1843 var $li = new_suggestion($(".suggest-card.reference ul"));
1844 set_item_values(toroot, $li, gGoogleMatches[i]);
1845 set_item_selected($li, i == gSelectedIndex);
1846 }
1847 }
Scott Mainf5089842012-08-14 16:31:07 -07001848 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001849 $('.suggest-card.reference').hide();
Scott Main0e76e7e2013-03-12 10:24:07 -07001850 }
1851
1852 // ########### JD DOC RESULTS #############
1853 if (gDocsMatches.length > 0) {
1854 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001855 $(".suggest-card:not(.reference) li").remove();
Scott Main0e76e7e2013-03-12 10:24:07 -07001856
1857 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001858 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1859 // The order must match the reverse order that each section appears as a card in
1860 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001861 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1862 for (i=0; i<gDocsListLength; i++) {
1863 var sugg = gDocsMatches[i];
1864 var $li;
1865 if (sugg.type == "design") {
1866 $li = new_suggestion($(".suggest-card.design ul"));
1867 } else
1868 if (sugg.type == "distribute") {
1869 $li = new_suggestion($(".suggest-card.distribute ul"));
1870 } else
Scott Main719acb42013-12-05 16:05:09 -08001871 if (sugg.type == "samples") {
1872 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1873 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001874 if (sugg.type == "training") {
1875 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1876 } else
Scott Main719acb42013-12-05 16:05:09 -08001877 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001878 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1879 } else {
1880 continue;
1881 }
1882
Scott Main719acb42013-12-05 16:05:09 -08001883 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001884 set_item_selected($li, i == gSelectedIndex);
1885 }
1886
1887 // add heading and show or hide card
1888 if ($(".suggest-card.design li").length > 0) {
1889 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1890 $(".suggest-card.design").show(300);
1891 } else {
1892 $('.suggest-card.design').hide(300);
1893 }
1894 if ($(".suggest-card.distribute li").length > 0) {
1895 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1896 $(".suggest-card.distribute").show(300);
1897 } else {
1898 $('.suggest-card.distribute').hide(300);
1899 }
1900 if ($(".child-card.guides li").length > 0) {
1901 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1902 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1903 }
1904 if ($(".child-card.training li").length > 0) {
1905 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1906 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1907 }
Scott Main719acb42013-12-05 16:05:09 -08001908 if ($(".child-card.samples li").length > 0) {
1909 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1910 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1911 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001912
1913 if ($(".suggest-card.develop li").length > 0) {
1914 $(".suggest-card.develop").show(300);
1915 } else {
1916 $('.suggest-card.develop').hide(300);
1917 }
1918
1919 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001920 $('.suggest-card:not(.reference)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001921 }
1922}
1923
Scott Main0e76e7e2013-03-12 10:24:07 -07001924/** Called by the search input's onkeydown and onkeyup events.
1925 * Handles navigation with keyboard arrows, Enter key to invoke search,
1926 * otherwise invokes search suggestions on key-up event.
1927 * @param e The JS event
1928 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001929 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001930 * @returns True if the event should bubble up
1931 */
Scott Mainf5089842012-08-14 16:31:07 -07001932function search_changed(e, kd, toroot)
1933{
Scott Main719acb42013-12-05 16:05:09 -08001934 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001935 var search = document.getElementById("search_autocomplete");
1936 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001937 // get the ul hosting the currently selected item
1938 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1939 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1940 var $selectedUl = $columns[gSelectedColumn];
1941
Scott Mainf5089842012-08-14 16:31:07 -07001942 // show/hide the close button
1943 if (text != '') {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001944 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07001945 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001946 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07001947 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001948 // 27 = esc
1949 if (e.keyCode == 27) {
1950 // close all search results
Dirk Dougherty29e93432015-05-05 18:17:13 -07001951 if (kd) $('#search-close').trigger('click');
Scott Main0e76e7e2013-03-12 10:24:07 -07001952 return true;
1953 }
Scott Mainf5089842012-08-14 16:31:07 -07001954 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001955 else if (e.keyCode == 13) {
1956 if (gSelectedIndex < 0) {
1957 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001958 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1959 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001960 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001961 return true;
1962 } else {
1963 // otherwise, results are already showing, so allow ajax to auto refresh the results
1964 // and ignore this Enter press to avoid the reload.
1965 return false;
1966 }
1967 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001968 // click the link corresponding to selected item
1969 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001970 return false;
1971 }
1972 }
Scott Mainb16376f2014-05-21 20:35:47 -07001973 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001974 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001975 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001976 if ((sticky ) && (search.value != "")) {
1977 $('body,html').animate({scrollTop:0}, '500', 'swing');
1978 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001979 return true;
1980 }
1981 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001982 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001983 // if the next item is a header, skip it
1984 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001985 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001986 }
1987 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001988 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001989 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001990 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1991 // If user reaches top, reset selected column
1992 if (gSelectedIndex < 0) {
1993 gSelectedColumn = -1;
1994 }
Scott Mainf5089842012-08-14 16:31:07 -07001995 }
1996 return false;
1997 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001998 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001999 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002000 // if the next item is a header, skip it
2001 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07002002 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08002003 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002004 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
2005 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
2006 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08002007 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07002008 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07002009 }
2010 return false;
2011 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002012 // Consider left/right arrow navigation
2013 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
2014 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
2015 // 37 LEFT ARROW
2016 // go left only if current column is not left-most column (last column)
2017 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
2018 $('li', $selectedUl).removeClass('jd-selected');
2019 gSelectedColumn++;
2020 $selectedUl = $columns[gSelectedColumn];
2021 // keep or reset the selected item to last item as appropriate
2022 gSelectedIndex = gSelectedIndex >
2023 $("li", $selectedUl).length-1 ?
2024 $("li", $selectedUl).length-1 : gSelectedIndex;
2025 // if the corresponding item is a header, move down
2026 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2027 gSelectedIndex++;
2028 }
Scott Main3b90aff2013-08-01 18:09:35 -07002029 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002030 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2031 return false;
2032 }
2033 // 39 RIGHT ARROW
2034 // go right only if current column is not the right-most column (first column)
2035 else if (e.keyCode == 39 && gSelectedColumn > 0) {
2036 $('li', $selectedUl).removeClass('jd-selected');
2037 gSelectedColumn--;
2038 $selectedUl = $columns[gSelectedColumn];
2039 // keep or reset the selected item to last item as appropriate
2040 gSelectedIndex = gSelectedIndex >
2041 $("li", $selectedUl).length-1 ?
2042 $("li", $selectedUl).length-1 : gSelectedIndex;
2043 // if the corresponding item is a header, move down
2044 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2045 gSelectedIndex++;
2046 }
Scott Main3b90aff2013-08-01 18:09:35 -07002047 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002048 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2049 return false;
2050 }
2051 }
2052
Scott Main719acb42013-12-05 16:05:09 -08002053 // if key-up event and not arrow down/up/left/right,
2054 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07002055 else if (!kd && (e.keyCode != 40)
2056 && (e.keyCode != 38)
2057 && (e.keyCode != 37)
2058 && (e.keyCode != 39)) {
2059 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07002060 gMatches = new Array();
2061 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002062 gGoogleMatches = new Array();
2063 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07002064 gDocsMatches = new Array();
2065 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002066
2067 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07002068 for (var i=0; i<DATA.length; i++) {
2069 var s = DATA[i];
2070 if (text.length != 0 &&
2071 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2072 gMatches[matchedCount] = s;
2073 matchedCount++;
2074 }
2075 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002076 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07002077 for (var i=0; i<gMatches.length; i++) {
2078 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08002079 }
2080
2081
2082 // Search for Google matches
2083 for (var i=0; i<GOOGLE_DATA.length; i++) {
2084 var s = GOOGLE_DATA[i];
2085 if (text.length != 0 &&
2086 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2087 gGoogleMatches[matchedCountGoogle] = s;
2088 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07002089 }
2090 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002091 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08002092 for (var i=0; i<gGoogleMatches.length; i++) {
2093 var s = gGoogleMatches[i];
2094 }
2095
Scott Mainf5089842012-08-14 16:31:07 -07002096 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07002097
2098
2099
Scott Main719acb42013-12-05 16:05:09 -08002100 // Search for matching JD docs
Dirk Dougherty9b7f8f22014-11-01 17:08:56 -07002101 if (text.length >= 2) {
Scott Main719acb42013-12-05 16:05:09 -08002102 // Regex to match only the beginning of a word
2103 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
2104
2105
2106 // Search for Training classes
2107 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002108 // current search comparison, with counters for tag and title,
2109 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08002110 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07002111 s.matched_tag = 0;
2112 s.matched_title = 0;
2113 var matched = false;
2114
2115 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08002116 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002117 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08002118 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002119 matched = true;
2120 s.matched_tag = j + 1; // add 1 to index position
2121 }
2122 }
Scott Main719acb42013-12-05 16:05:09 -08002123 // Don't consider doc title for lessons (only for class landing pages),
2124 // unless the lesson has a tag that already matches
2125 if ((s.lang == currentLang) &&
2126 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002127 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08002128 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002129 matched = true;
2130 s.matched_title = 1;
2131 }
2132 }
2133 if (matched) {
2134 gDocsMatches[matchedCountDocs] = s;
2135 matchedCountDocs++;
2136 }
2137 }
Scott Main719acb42013-12-05 16:05:09 -08002138
2139
2140 // Search for API Guides
2141 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2142 // current search comparison, with counters for tag and title,
2143 // used later to improve ranking
2144 var s = GUIDE_RESOURCES[i];
2145 s.matched_tag = 0;
2146 s.matched_title = 0;
2147 var matched = false;
2148
2149 // Check if query matches any tags; work backwards toward 1 to assist ranking
2150 for (var j = s.keywords.length - 1; j >= 0; j--) {
2151 // it matches a tag
2152 if (s.keywords[j].toLowerCase().match(textRegex)) {
2153 matched = true;
2154 s.matched_tag = j + 1; // add 1 to index position
2155 }
2156 }
2157 // Check if query matches the doc title, but only for current language
2158 if (s.lang == currentLang) {
2159 // if query matches the doc title
2160 if (s.title.toLowerCase().match(textRegex)) {
2161 matched = true;
2162 s.matched_title = 1;
2163 }
2164 }
2165 if (matched) {
2166 gDocsMatches[matchedCountDocs] = s;
2167 matchedCountDocs++;
2168 }
2169 }
2170
2171
2172 // Search for Tools Guides
2173 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2174 // current search comparison, with counters for tag and title,
2175 // used later to improve ranking
2176 var s = TOOLS_RESOURCES[i];
2177 s.matched_tag = 0;
2178 s.matched_title = 0;
2179 var matched = false;
2180
2181 // Check if query matches any tags; work backwards toward 1 to assist ranking
2182 for (var j = s.keywords.length - 1; j >= 0; j--) {
2183 // it matches a tag
2184 if (s.keywords[j].toLowerCase().match(textRegex)) {
2185 matched = true;
2186 s.matched_tag = j + 1; // add 1 to index position
2187 }
2188 }
2189 // Check if query matches the doc title, but only for current language
2190 if (s.lang == currentLang) {
2191 // if query matches the doc title
2192 if (s.title.toLowerCase().match(textRegex)) {
2193 matched = true;
2194 s.matched_title = 1;
2195 }
2196 }
2197 if (matched) {
2198 gDocsMatches[matchedCountDocs] = s;
2199 matchedCountDocs++;
2200 }
2201 }
2202
2203
2204 // Search for About docs
2205 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2206 // current search comparison, with counters for tag and title,
2207 // used later to improve ranking
2208 var s = ABOUT_RESOURCES[i];
2209 s.matched_tag = 0;
2210 s.matched_title = 0;
2211 var matched = false;
2212
2213 // Check if query matches any tags; work backwards toward 1 to assist ranking
2214 for (var j = s.keywords.length - 1; j >= 0; j--) {
2215 // it matches a tag
2216 if (s.keywords[j].toLowerCase().match(textRegex)) {
2217 matched = true;
2218 s.matched_tag = j + 1; // add 1 to index position
2219 }
2220 }
2221 // Check if query matches the doc title, but only for current language
2222 if (s.lang == currentLang) {
2223 // if query matches the doc title
2224 if (s.title.toLowerCase().match(textRegex)) {
2225 matched = true;
2226 s.matched_title = 1;
2227 }
2228 }
2229 if (matched) {
2230 gDocsMatches[matchedCountDocs] = s;
2231 matchedCountDocs++;
2232 }
2233 }
2234
2235
2236 // Search for Design guides
2237 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2238 // current search comparison, with counters for tag and title,
2239 // used later to improve ranking
2240 var s = DESIGN_RESOURCES[i];
2241 s.matched_tag = 0;
2242 s.matched_title = 0;
2243 var matched = false;
2244
2245 // Check if query matches any tags; work backwards toward 1 to assist ranking
2246 for (var j = s.keywords.length - 1; j >= 0; j--) {
2247 // it matches a tag
2248 if (s.keywords[j].toLowerCase().match(textRegex)) {
2249 matched = true;
2250 s.matched_tag = j + 1; // add 1 to index position
2251 }
2252 }
2253 // Check if query matches the doc title, but only for current language
2254 if (s.lang == currentLang) {
2255 // if query matches the doc title
2256 if (s.title.toLowerCase().match(textRegex)) {
2257 matched = true;
2258 s.matched_title = 1;
2259 }
2260 }
2261 if (matched) {
2262 gDocsMatches[matchedCountDocs] = s;
2263 matchedCountDocs++;
2264 }
2265 }
2266
2267
2268 // Search for Distribute guides
2269 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2270 // current search comparison, with counters for tag and title,
2271 // used later to improve ranking
2272 var s = DISTRIBUTE_RESOURCES[i];
2273 s.matched_tag = 0;
2274 s.matched_title = 0;
2275 var matched = false;
2276
2277 // Check if query matches any tags; work backwards toward 1 to assist ranking
2278 for (var j = s.keywords.length - 1; j >= 0; j--) {
2279 // it matches a tag
2280 if (s.keywords[j].toLowerCase().match(textRegex)) {
2281 matched = true;
2282 s.matched_tag = j + 1; // add 1 to index position
2283 }
2284 }
2285 // Check if query matches the doc title, but only for current language
2286 if (s.lang == currentLang) {
2287 // if query matches the doc title
2288 if (s.title.toLowerCase().match(textRegex)) {
2289 matched = true;
2290 s.matched_title = 1;
2291 }
2292 }
2293 if (matched) {
2294 gDocsMatches[matchedCountDocs] = s;
2295 matchedCountDocs++;
2296 }
2297 }
2298
2299
2300 // Search for Google guides
2301 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2302 // current search comparison, with counters for tag and title,
2303 // used later to improve ranking
2304 var s = GOOGLE_RESOURCES[i];
2305 s.matched_tag = 0;
2306 s.matched_title = 0;
2307 var matched = false;
2308
2309 // Check if query matches any tags; work backwards toward 1 to assist ranking
2310 for (var j = s.keywords.length - 1; j >= 0; j--) {
2311 // it matches a tag
2312 if (s.keywords[j].toLowerCase().match(textRegex)) {
2313 matched = true;
2314 s.matched_tag = j + 1; // add 1 to index position
2315 }
2316 }
2317 // Check if query matches the doc title, but only for current language
2318 if (s.lang == currentLang) {
2319 // if query matches the doc title
2320 if (s.title.toLowerCase().match(textRegex)) {
2321 matched = true;
2322 s.matched_title = 1;
2323 }
2324 }
2325 if (matched) {
2326 gDocsMatches[matchedCountDocs] = s;
2327 matchedCountDocs++;
2328 }
2329 }
2330
2331
2332 // Search for Samples
2333 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2334 // current search comparison, with counters for tag and title,
2335 // used later to improve ranking
2336 var s = SAMPLES_RESOURCES[i];
2337 s.matched_tag = 0;
2338 s.matched_title = 0;
2339 var matched = false;
2340 // Check if query matches any tags; work backwards toward 1 to assist ranking
2341 for (var j = s.keywords.length - 1; j >= 0; j--) {
2342 // it matches a tag
2343 if (s.keywords[j].toLowerCase().match(textRegex)) {
2344 matched = true;
2345 s.matched_tag = j + 1; // add 1 to index position
2346 }
2347 }
2348 // Check if query matches the doc title, but only for current language
2349 if (s.lang == currentLang) {
2350 // if query matches the doc title.t
2351 if (s.title.toLowerCase().match(textRegex)) {
2352 matched = true;
2353 s.matched_title = 1;
2354 }
2355 }
2356 if (matched) {
2357 gDocsMatches[matchedCountDocs] = s;
2358 matchedCountDocs++;
2359 }
2360 }
2361
Joe Fernandeza9d796a2015-05-05 22:07:42 -07002362 // Search for Preview Guides
2363 for (var i=0; i<PREVIEW_RESOURCES.length; i++) {
2364 // current search comparison, with counters for tag and title,
2365 // used later to improve ranking
2366 var s = PREVIEW_RESOURCES[i];
2367 s.matched_tag = 0;
2368 s.matched_title = 0;
2369 var matched = false;
2370
2371 // Check if query matches any tags; work backwards toward 1 to assist ranking
2372 for (var j = s.keywords.length - 1; j >= 0; j--) {
2373 // it matches a tag
2374 if (s.keywords[j].toLowerCase().match(textRegex)) {
2375 matched = true;
2376 s.matched_tag = j + 1; // add 1 to index position
2377 }
2378 }
2379 // Check if query matches the doc title, but only for current language
2380 if (s.lang == currentLang) {
2381 // if query matches the doc title
2382 if (s.title.toLowerCase().match(textRegex)) {
2383 matched = true;
2384 s.matched_title = 1;
2385 }
2386 }
2387 if (matched) {
2388 gDocsMatches[matchedCountDocs] = s;
2389 matchedCountDocs++;
2390 }
2391 }
2392
Scott Main719acb42013-12-05 16:05:09 -08002393 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002394 rank_autocomplete_doc_results(text, gDocsMatches);
2395 }
2396
2397 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002398 sync_selection_table(toroot);
2399 return true; // allow the event to bubble up to the search api
2400 }
2401}
2402
Scott Main0e76e7e2013-03-12 10:24:07 -07002403/* Order the jd doc result list based on match quality */
2404function rank_autocomplete_doc_results(query, matches) {
2405 query = query || '';
2406 if (!matches || !matches.length)
2407 return;
2408
2409 var _resultScoreFn = function(match) {
2410 var score = 1.0;
2411
2412 // if the query matched a tag
2413 if (match.matched_tag > 0) {
2414 // multiply score by factor relative to position in tags list (max of 3)
2415 score *= 3 / match.matched_tag;
2416
2417 // if it also matched the title
2418 if (match.matched_title > 0) {
2419 score *= 2;
2420 }
2421 } else if (match.matched_title > 0) {
2422 score *= 3;
2423 }
2424
2425 return score;
2426 };
2427
2428 for (var i=0; i<matches.length; i++) {
2429 matches[i].__resultScore = _resultScoreFn(matches[i]);
2430 }
2431
2432 matches.sort(function(a,b){
2433 var n = b.__resultScore - a.__resultScore;
2434 if (n == 0) // lexicographical sort if scores are the same
2435 n = (a.label < b.label) ? -1 : 1;
2436 return n;
2437 });
2438}
2439
Scott Main7e447ed2013-02-19 17:22:37 -08002440/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002441function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002442 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002443 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002444 return;
2445
2446 // helper function that gets the last occurence index of the given regex
2447 // in the given string, or -1 if not found
2448 var _lastSearch = function(s, re) {
2449 if (s == '')
2450 return -1;
2451 var l = -1;
2452 var tmp;
2453 while ((tmp = s.search(re)) >= 0) {
2454 if (l < 0) l = 0;
2455 l += tmp;
2456 s = s.substr(tmp + 1);
2457 }
2458 return l;
2459 };
2460
2461 // helper function that counts the occurrences of a given character in
2462 // a given string
2463 var _countChar = function(s, c) {
2464 var n = 0;
2465 for (var i=0; i<s.length; i++)
2466 if (s.charAt(i) == c) ++n;
2467 return n;
2468 };
2469
2470 var queryLower = query.toLowerCase();
2471 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2472 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2473 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2474
2475 var _resultScoreFn = function(result) {
2476 // scores are calculated based on exact and prefix matches,
2477 // and then number of path separators (dots) from the last
2478 // match (i.e. favoring classes and deep package names)
2479 var score = 1.0;
2480 var labelLower = result.label.toLowerCase();
2481 var t;
2482 t = _lastSearch(labelLower, partExactAlnumRE);
2483 if (t >= 0) {
2484 // exact part match
2485 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2486 score *= 200 / (partsAfter + 1);
2487 } else {
2488 t = _lastSearch(labelLower, partPrefixAlnumRE);
2489 if (t >= 0) {
2490 // part prefix match
2491 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2492 score *= 20 / (partsAfter + 1);
2493 }
2494 }
2495
2496 return score;
2497 };
2498
Scott Main7e447ed2013-02-19 17:22:37 -08002499 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002500 // if the API is deprecated, default score is 0; otherwise, perform scoring
2501 if (matches[i].deprecated == "true") {
2502 matches[i].__resultScore = 0;
2503 } else {
2504 matches[i].__resultScore = _resultScoreFn(matches[i]);
2505 }
Scott Mainf5089842012-08-14 16:31:07 -07002506 }
2507
Scott Main7e447ed2013-02-19 17:22:37 -08002508 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002509 var n = b.__resultScore - a.__resultScore;
2510 if (n == 0) // lexicographical sort if scores are the same
2511 n = (a.label < b.label) ? -1 : 1;
2512 return n;
2513 });
2514}
2515
Scott Main7e447ed2013-02-19 17:22:37 -08002516/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002517function highlight_autocomplete_result_labels(query) {
2518 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002519 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002520 return;
2521
2522 var queryLower = query.toLowerCase();
2523 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2524 var queryRE = new RegExp(
2525 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2526 for (var i=0; i<gMatches.length; i++) {
2527 gMatches[i].__hilabel = gMatches[i].label.replace(
2528 queryRE, '<b>$1</b>');
2529 }
Scott Main7e447ed2013-02-19 17:22:37 -08002530 for (var i=0; i<gGoogleMatches.length; i++) {
2531 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2532 queryRE, '<b>$1</b>');
2533 }
Scott Mainf5089842012-08-14 16:31:07 -07002534}
2535
2536function search_focus_changed(obj, focused)
2537{
Scott Main3b90aff2013-08-01 18:09:35 -07002538 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002539 if(obj.value == ""){
Dirk Dougherty29e93432015-05-05 18:17:13 -07002540 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002541 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002542 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002543 }
2544}
2545
2546function submit_search() {
2547 var query = document.getElementById('search_autocomplete').value;
2548 location.hash = 'q=' + query;
2549 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002550 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002551 return false;
2552}
2553
2554
2555function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002556 $("#searchResults").slideUp('fast', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002557 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002558 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002559
Scott Mainf5089842012-08-14 16:31:07 -07002560 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002561
Scott Mainf5089842012-08-14 16:31:07 -07002562 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2563 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002564
2565 // forcefully regain key-up event control (previously jacked by search api)
2566 $("#search_autocomplete").keyup(function(event) {
2567 return search_changed(event, false, toRoot);
2568 });
2569
Scott Mainf5089842012-08-14 16:31:07 -07002570 return false;
2571}
2572
2573
2574
2575/* ########################################################## */
2576/* ################ CUSTOM SEARCH ENGINE ################## */
2577/* ########################################################## */
2578
Scott Mainf5089842012-08-14 16:31:07 -07002579var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002580google.load('search', '1', {"callback" : function() {
2581 searchControl = new google.search.SearchControl();
2582 } });
Scott Mainf5089842012-08-14 16:31:07 -07002583
2584function loadSearchResults() {
2585 document.getElementById("search_autocomplete").style.color = "#000";
2586
Scott Mainf5089842012-08-14 16:31:07 -07002587 searchControl = new google.search.SearchControl();
2588
2589 // use our existing search form and use tabs when multiple searchers are used
2590 drawOptions = new google.search.DrawOptions();
2591 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2592 drawOptions.setInput(document.getElementById("search_autocomplete"));
2593
2594 // configure search result options
2595 searchOptions = new google.search.SearcherOptions();
2596 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2597
2598 // configure each of the searchers, for each tab
2599 devSiteSearcher = new google.search.WebSearch();
2600 devSiteSearcher.setUserDefinedLabel("All");
2601 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2602
2603 designSearcher = new google.search.WebSearch();
2604 designSearcher.setUserDefinedLabel("Design");
2605 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2606
2607 trainingSearcher = new google.search.WebSearch();
2608 trainingSearcher.setUserDefinedLabel("Training");
2609 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2610
2611 guidesSearcher = new google.search.WebSearch();
2612 guidesSearcher.setUserDefinedLabel("Guides");
2613 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2614
2615 referenceSearcher = new google.search.WebSearch();
2616 referenceSearcher.setUserDefinedLabel("Reference");
2617 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2618
Scott Maindf08ada2012-12-03 08:54:37 -08002619 googleSearcher = new google.search.WebSearch();
2620 googleSearcher.setUserDefinedLabel("Google Services");
2621 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2622
Scott Mainf5089842012-08-14 16:31:07 -07002623 blogSearcher = new google.search.WebSearch();
2624 blogSearcher.setUserDefinedLabel("Blog");
2625 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2626
2627 // add each searcher to the search control
2628 searchControl.addSearcher(devSiteSearcher, searchOptions);
2629 searchControl.addSearcher(designSearcher, searchOptions);
2630 searchControl.addSearcher(trainingSearcher, searchOptions);
2631 searchControl.addSearcher(guidesSearcher, searchOptions);
2632 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002633 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002634 searchControl.addSearcher(blogSearcher, searchOptions);
2635
2636 // configure result options
2637 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2638 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2639 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2640 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2641
2642 // upon ajax search, refresh the url and search title
2643 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2644 updateResultTitle(query);
2645 var query = document.getElementById('search_autocomplete').value;
2646 location.hash = 'q=' + query;
2647 });
2648
Scott Mainde295272013-03-25 15:48:35 -07002649 // once search results load, set up click listeners
2650 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2651 addResultClickListeners();
2652 });
2653
Scott Mainf5089842012-08-14 16:31:07 -07002654 // draw the search results box
2655 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2656
2657 // get query and execute the search
2658 searchControl.execute(decodeURI(getQuery(location.hash)));
2659
2660 document.getElementById("search_autocomplete").focus();
2661 addTabListeners();
2662}
2663// End of loadSearchResults
2664
2665
2666google.setOnLoadCallback(function(){
2667 if (location.hash.indexOf("q=") == -1) {
2668 // if there's no query in the url, don't search and make sure results are hidden
2669 $('#searchResults').hide();
2670 return;
2671 } else {
2672 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002673 $('#searchResults').slideDown('slow', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002674 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002675 loadSearchResults();
2676 }
2677}, true);
2678
smain@google.com9a818f52014-10-03 09:25:59 -07002679/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2680 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002681function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002682 // Ignore if there's no search bar (some special pages have no header)
2683 if ($("#search-container").length < 1) return;
2684
smain@google.com3b77ab52014-06-17 11:57:27 -07002685 var hash = escape(location.hash.substr(1));
2686 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002687 // Sanity check that there's an element with that ID on the page
2688 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002689 // If the position of the target element is near the top of the page (<20px, where we expect it
2690 // to be because we need to move it down 60px to become in view), then move it down 60px
2691 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2692 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002693 }
2694 }
2695}
2696
Scott Mainf5089842012-08-14 16:31:07 -07002697// when an event on the browser history occurs (back, forward, load) requery hash and do search
2698$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002699 // Ignore if there's no search bar (some special pages have no header)
2700 if ($("#search-container").length < 1) return;
2701
Dirk Doughertyc3921652014-05-13 16:55:26 -07002702 // If the hash isn't a search query or there's an error in the query,
2703 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002704 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2705 // If the results pane is open, close it.
2706 if (!$("#searchResults").is(":hidden")) {
2707 hideResults();
2708 }
Scott Mainb16376f2014-05-21 20:35:47 -07002709 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002710 return;
2711 }
2712
2713 // Otherwise, we have a search to do
2714 var query = decodeURI(getQuery(location.hash));
2715 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002716 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002717 $("#search_autocomplete").focus();
Dirk Dougherty29e93432015-05-05 18:17:13 -07002718 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002719
2720 updateResultTitle(query);
2721});
2722
2723function updateResultTitle(query) {
2724 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2725}
2726
2727// forcefully regain key-up event control (previously jacked by search api)
2728$("#search_autocomplete").keyup(function(event) {
2729 return search_changed(event, false, toRoot);
2730});
2731
2732// add event listeners to each tab so we can track the browser history
2733function addTabListeners() {
2734 var tabHeaders = $(".gsc-tabHeader");
2735 for (var i = 0; i < tabHeaders.length; i++) {
2736 $(tabHeaders[i]).attr("id",i).click(function() {
2737 /*
2738 // make a copy of the page numbers for the search left pane
2739 setTimeout(function() {
2740 // remove any residual page numbers
2741 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002742 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002743 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002744 // and because we're going to remove it (previous line),
2745 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002746 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2747 .clone().appendTo('#searchResults .gsc-tabsArea');
2748 }, 200);
2749 */
2750 });
2751 }
2752 setTimeout(function(){$(tabHeaders[0]).click()},200);
2753}
2754
Scott Mainde295272013-03-25 15:48:35 -07002755// add analytics tracking events to each result link
2756function addResultClickListeners() {
2757 $("#searchResults a.gs-title").each(function(index, link) {
2758 // When user clicks enter for Google search results, track it
2759 $(link).click(function() {
smain@google.comed677d72014-12-12 10:19:17 -08002760 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'),
2761 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07002762 });
2763 });
2764}
2765
Scott Mainf5089842012-08-14 16:31:07 -07002766
2767function getQuery(hash) {
2768 var queryParts = hash.split('=');
2769 return queryParts[1];
2770}
2771
2772/* returns the given string with all HTML brackets converted to entities
2773 TODO: move this to the site's JS library */
2774function escapeHTML(string) {
2775 return string.replace(/</g,"&lt;")
2776 .replace(/>/g,"&gt;");
2777}
2778
2779
2780
2781
2782
2783
2784
2785/* ######################################################## */
2786/* ################# JAVADOC REFERENCE ################### */
2787/* ######################################################## */
2788
Scott Main65511c02012-09-07 15:51:32 -07002789/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002790if (location.pathname.indexOf("/reference") == 0) {
2791 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2792 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2793 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002794 $(document).ready(function() {
2795 // init available apis based on user pref
2796 changeApiLevel();
2797 initSidenavHeightResize()
2798 });
2799 }
Scott Main65511c02012-09-07 15:51:32 -07002800}
Scott Mainf5089842012-08-14 16:31:07 -07002801
2802var API_LEVEL_COOKIE = "api_level";
2803var minLevel = 1;
2804var maxLevel = 1;
2805
2806/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002807
Scott Mainf5089842012-08-14 16:31:07 -07002808 function initSidenavHeightResize() {
2809 // Change the drag bar size to nicely fit the scrollbar positions
2810 var $dragBar = $(".ui-resizable-s");
2811 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002812
2813 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002814 containment: "#nav-panels",
2815 handles: "s",
2816 alsoResize: "#packages-nav",
2817 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2818 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2819 });
Scott Main3b90aff2013-08-01 18:09:35 -07002820
Scott Mainf5089842012-08-14 16:31:07 -07002821 }
Scott Main3b90aff2013-08-01 18:09:35 -07002822
Scott Mainf5089842012-08-14 16:31:07 -07002823function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002824 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002825 $('#devdoc-nav').css({
2826 'width' : $('#side-nav').css('width'),
2827 'margin' : $('#side-nav').css('margin')
2828 });
2829 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002830
Scott Mainf5089842012-08-14 16:31:07 -07002831 initSidenavHeightResize();
2832}
2833
2834function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002835 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002836 $('#devdoc-nav').css({
2837 'width' : $('#side-nav').css('width'),
2838 'margin' : $('#side-nav').css('margin')
2839 });
2840 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002841
Scott Mainf5089842012-08-14 16:31:07 -07002842 initSidenavHeightResize();
2843}
2844
2845function buildApiLevelSelector() {
2846 maxLevel = SINCE_DATA.length;
2847 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2848 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2849
2850 minLevel = parseInt($("#doc-api-level").attr("class"));
2851 // Handle provisional api levels; the provisional level will always be the highest possible level
2852 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2853 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2854 if (isNaN(minLevel) && minLevel.length) {
2855 minLevel = maxLevel;
2856 }
2857 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2858 for (var i = maxLevel-1; i >= 0; i--) {
2859 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2860 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2861 select.append(option);
2862 }
2863
2864 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2865 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2866 selectedLevelItem.setAttribute('selected',true);
2867}
2868
2869function changeApiLevel() {
2870 maxLevel = SINCE_DATA.length;
2871 var selectedLevel = maxLevel;
2872
2873 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2874 toggleVisisbleApis(selectedLevel, "body");
2875
smain@google.com6bdcb982014-11-14 11:53:07 -08002876 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002877
2878 if (selectedLevel < minLevel) {
2879 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002880 $("#naMessage").show().html("<div><p><strong>This " + thing
2881 + " requires API level " + minLevel + " or higher.</strong></p>"
2882 + "<p>This document is hidden because your selected API level for the documentation is "
2883 + selectedLevel + ". You can change the documentation API level with the selector "
2884 + "above the left navigation.</p>"
2885 + "<p>For more information about specifying the API level your app requires, "
2886 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2887 + ">Supporting Different Platform Versions</a>.</p>"
2888 + "<input type='button' value='OK, make this page visible' "
2889 + "title='Change the API level to " + minLevel + "' "
2890 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2891 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002892 } else {
2893 $("#naMessage").hide();
2894 }
2895}
2896
2897function toggleVisisbleApis(selectedLevel, context) {
2898 var apis = $(".api",context);
2899 apis.each(function(i) {
2900 var obj = $(this);
2901 var className = obj.attr("class");
2902 var apiLevelIndex = className.lastIndexOf("-")+1;
2903 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2904 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2905 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2906 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2907 return;
2908 }
2909 apiLevel = parseInt(apiLevel);
2910
2911 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2912 var selectedLevelNum = parseInt(selectedLevel)
2913 var apiLevelNum = parseInt(apiLevel);
2914 if (isNaN(apiLevelNum)) {
2915 apiLevelNum = maxLevel;
2916 }
2917
2918 // Grey things out that aren't available and give a tooltip title
2919 if (apiLevelNum > selectedLevelNum) {
2920 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002921 + apiLevel + "\" or higher. To reveal, change the target API level "
2922 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002923 }
Scott Mainf5089842012-08-14 16:31:07 -07002924 else obj.removeClass("absent").removeAttr("title");
2925 });
2926}
2927
2928
2929
2930
2931/* ################# SIDENAV TREE VIEW ################### */
2932
2933function new_node(me, mom, text, link, children_data, api_level)
2934{
2935 var node = new Object();
2936 node.children = Array();
2937 node.children_data = children_data;
2938 node.depth = mom.depth + 1;
2939
2940 node.li = document.createElement("li");
2941 mom.get_children_ul().appendChild(node.li);
2942
2943 node.label_div = document.createElement("div");
2944 node.label_div.className = "label";
2945 if (api_level != null) {
2946 $(node.label_div).addClass("api");
2947 $(node.label_div).addClass("api-level-"+api_level);
2948 }
2949 node.li.appendChild(node.label_div);
2950
2951 if (children_data != null) {
2952 node.expand_toggle = document.createElement("a");
2953 node.expand_toggle.href = "javascript:void(0)";
2954 node.expand_toggle.onclick = function() {
2955 if (node.expanded) {
2956 $(node.get_children_ul()).slideUp("fast");
2957 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2958 node.expanded = false;
2959 } else {
2960 expand_node(me, node);
2961 }
2962 };
2963 node.label_div.appendChild(node.expand_toggle);
2964
2965 node.plus_img = document.createElement("img");
2966 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2967 node.plus_img.className = "plus";
2968 node.plus_img.width = "8";
2969 node.plus_img.border = "0";
2970 node.expand_toggle.appendChild(node.plus_img);
2971
2972 node.expanded = false;
2973 }
2974
2975 var a = document.createElement("a");
2976 node.label_div.appendChild(a);
2977 node.label = document.createTextNode(text);
2978 a.appendChild(node.label);
2979 if (link) {
2980 a.href = me.toroot + link;
2981 } else {
2982 if (children_data != null) {
2983 a.className = "nolink";
2984 a.href = "javascript:void(0)";
2985 a.onclick = node.expand_toggle.onclick;
2986 // This next line shouldn't be necessary. I'll buy a beer for the first
2987 // person who figures out how to remove this line and have the link
2988 // toggle shut on the first try. --joeo@android.com
2989 node.expanded = false;
2990 }
2991 }
Scott Main3b90aff2013-08-01 18:09:35 -07002992
Scott Mainf5089842012-08-14 16:31:07 -07002993
2994 node.children_ul = null;
2995 node.get_children_ul = function() {
2996 if (!node.children_ul) {
2997 node.children_ul = document.createElement("ul");
2998 node.children_ul.className = "children_ul";
2999 node.children_ul.style.display = "none";
3000 node.li.appendChild(node.children_ul);
3001 }
3002 return node.children_ul;
3003 };
3004
3005 return node;
3006}
3007
Robert Lyd2dd6e52012-11-29 21:28:48 -08003008
3009
3010
Scott Mainf5089842012-08-14 16:31:07 -07003011function expand_node(me, node)
3012{
3013 if (node.children_data && !node.expanded) {
3014 if (node.children_visited) {
3015 $(node.get_children_ul()).slideDown("fast");
3016 } else {
3017 get_node(me, node);
3018 if ($(node.label_div).hasClass("absent")) {
3019 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07003020 }
Scott Mainf5089842012-08-14 16:31:07 -07003021 $(node.get_children_ul()).slideDown("fast");
3022 }
3023 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
3024 node.expanded = true;
3025
3026 // perform api level toggling because new nodes are new to the DOM
3027 var selectedLevel = $("#apiLevelSelector option:selected").val();
3028 toggleVisisbleApis(selectedLevel, "#side-nav");
3029 }
3030}
3031
3032function get_node(me, mom)
3033{
3034 mom.children_visited = true;
3035 for (var i in mom.children_data) {
3036 var node_data = mom.children_data[i];
3037 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
3038 node_data[2], node_data[3]);
3039 }
3040}
3041
3042function this_page_relative(toroot)
3043{
3044 var full = document.location.pathname;
3045 var file = "";
3046 if (toroot.substr(0, 1) == "/") {
3047 if (full.substr(0, toroot.length) == toroot) {
3048 return full.substr(toroot.length);
3049 } else {
3050 // the file isn't under toroot. Fail.
3051 return null;
3052 }
3053 } else {
3054 if (toroot != "./") {
3055 toroot = "./" + toroot;
3056 }
3057 do {
3058 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3059 var pos = full.lastIndexOf("/");
3060 file = full.substr(pos) + file;
3061 full = full.substr(0, pos);
3062 toroot = toroot.substr(0, toroot.length-3);
3063 }
3064 } while (toroot != "" && toroot != "/");
3065 return file.substr(1);
3066 }
3067}
3068
3069function find_page(url, data)
3070{
3071 var nodes = data;
3072 var result = null;
3073 for (var i in nodes) {
3074 var d = nodes[i];
3075 if (d[1] == url) {
3076 return new Array(i);
3077 }
3078 else if (d[2] != null) {
3079 result = find_page(url, d[2]);
3080 if (result != null) {
3081 return (new Array(i).concat(result));
3082 }
3083 }
3084 }
3085 return null;
3086}
3087
Scott Mainf5089842012-08-14 16:31:07 -07003088function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07003089 // load json file for navtree data
3090 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3091 // when the file is loaded, initialize the tree
3092 if(jqxhr.status === 200) {
3093 init_navtree("tree-list", toroot, NAVTREE_DATA);
3094 }
3095 });
Scott Main3b90aff2013-08-01 18:09:35 -07003096
Scott Mainf5089842012-08-14 16:31:07 -07003097 // perform api level toggling because because the whole tree is new to the DOM
3098 var selectedLevel = $("#apiLevelSelector option:selected").val();
3099 toggleVisisbleApis(selectedLevel, "#side-nav");
3100}
3101
3102function init_navtree(navtree_id, toroot, root_nodes)
3103{
3104 var me = new Object();
3105 me.toroot = toroot;
3106 me.node = new Object();
3107
3108 me.node.li = document.getElementById(navtree_id);
3109 me.node.children_data = root_nodes;
3110 me.node.children = new Array();
3111 me.node.children_ul = document.createElement("ul");
3112 me.node.get_children_ul = function() { return me.node.children_ul; };
3113 //me.node.children_ul.className = "children_ul";
3114 me.node.li.appendChild(me.node.children_ul);
3115 me.node.depth = 0;
3116
3117 get_node(me, me.node);
3118
3119 me.this_page = this_page_relative(toroot);
3120 me.breadcrumbs = find_page(me.this_page, root_nodes);
3121 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3122 var mom = me.node;
3123 for (var i in me.breadcrumbs) {
3124 var j = me.breadcrumbs[i];
3125 mom = mom.children[j];
3126 expand_node(me, mom);
3127 }
3128 mom.label_div.className = mom.label_div.className + " selected";
3129 addLoadEvent(function() {
3130 scrollIntoView("nav-tree");
3131 });
3132 }
3133}
3134
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003135
3136
3137
3138
3139
3140
3141
Robert Lyd2dd6e52012-11-29 21:28:48 -08003142/* TODO: eliminate redundancy with non-google functions */
3143function init_google_navtree(navtree_id, toroot, root_nodes)
3144{
3145 var me = new Object();
3146 me.toroot = toroot;
3147 me.node = new Object();
3148
3149 me.node.li = document.getElementById(navtree_id);
3150 me.node.children_data = root_nodes;
3151 me.node.children = new Array();
3152 me.node.children_ul = document.createElement("ul");
3153 me.node.get_children_ul = function() { return me.node.children_ul; };
3154 //me.node.children_ul.className = "children_ul";
3155 me.node.li.appendChild(me.node.children_ul);
3156 me.node.depth = 0;
3157
3158 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003159}
3160
3161function new_google_node(me, mom, text, link, children_data, api_level)
3162{
3163 var node = new Object();
3164 var child;
3165 node.children = Array();
3166 node.children_data = children_data;
3167 node.depth = mom.depth + 1;
3168 node.get_children_ul = function() {
3169 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003170 node.children_ul = document.createElement("ul");
3171 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003172 node.li.appendChild(node.children_ul);
3173 }
3174 return node.children_ul;
3175 };
3176 node.li = document.createElement("li");
3177
3178 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003179
3180
Robert Lyd2dd6e52012-11-29 21:28:48 -08003181 if(link) {
3182 child = document.createElement("a");
3183
3184 }
3185 else {
3186 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003187 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003188
3189 }
3190 if (children_data != null) {
3191 node.li.className="nav-section";
3192 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003193 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003194 node.li.appendChild(node.label_div);
3195 get_google_node(me, node);
3196 node.label_div.appendChild(child);
3197 }
3198 else {
3199 node.li.appendChild(child);
3200 }
3201 if(link) {
3202 child.href = me.toroot + link;
3203 }
3204 node.label = document.createTextNode(text);
3205 child.appendChild(node.label);
3206
3207 node.children_ul = null;
3208
3209 return node;
3210}
3211
3212function get_google_node(me, mom)
3213{
3214 mom.children_visited = true;
3215 var linkText;
3216 for (var i in mom.children_data) {
3217 var node_data = mom.children_data[i];
3218 linkText = node_data[0];
3219
3220 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3221 linkText = linkText.substr(19, linkText.length);
3222 }
3223 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3224 node_data[2], node_data[3]);
3225 }
3226}
Scott Mainad08f072013-08-20 16:49:57 -07003227
3228
3229
3230
3231
3232
3233/****** NEW version of script to build google and sample navs dynamically ******/
3234// TODO: update Google reference docs to tolerate this new implementation
3235
Scott Maine624b3f2013-09-12 12:56:41 -07003236var NODE_NAME = 0;
3237var NODE_HREF = 1;
3238var NODE_GROUP = 2;
3239var NODE_TAGS = 3;
3240var NODE_CHILDREN = 4;
3241
Scott Mainad08f072013-08-20 16:49:57 -07003242function init_google_navtree2(navtree_id, data)
3243{
3244 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003245 for (var i in data) {
3246 var node_data = data[i];
3247 $containerUl.append(new_google_node2(node_data));
3248 }
3249
Scott Main70557ee2013-10-30 14:47:40 -07003250 // Make all third-generation list items 'sticky' to prevent them from collapsing
3251 $containerUl.find('li li li.nav-section').addClass('sticky');
3252
Scott Mainad08f072013-08-20 16:49:57 -07003253 initExpandableNavItems("#"+navtree_id);
3254}
3255
3256function new_google_node2(node_data)
3257{
Scott Maine624b3f2013-09-12 12:56:41 -07003258 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003259 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3260 linkText = linkText.substr(19, linkText.length);
3261 }
3262 var $li = $('<li>');
3263 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003264 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003265 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3266 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003267 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003268 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3269 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003270 }
3271 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003272 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003273 $li.addClass("nav-section");
3274 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003275 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003276
Scott Maine624b3f2013-09-12 12:56:41 -07003277 for (var i in node_data[NODE_CHILDREN]) {
3278 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003279 $childUl.append(new_google_node2(child_node_data));
3280 }
3281 $li.append($childUl);
3282 }
3283 $li.prepend($a);
3284
3285 return $li;
3286}
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
Robert Lyd2dd6e52012-11-29 21:28:48 -08003298function showGoogleRefTree() {
3299 init_default_google_navtree(toRoot);
3300 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003301}
3302
3303function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003304 // load json file for navtree data
3305 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3306 // when the file is loaded, initialize the tree
3307 if(jqxhr.status === 200) {
3308 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3309 highlightSidenav();
3310 resizeNav();
3311 }
3312 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003313}
3314
3315function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003316 // load json file for navtree data
3317 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3318 // when the file is loaded, initialize the tree
3319 if(jqxhr.status === 200) {
3320 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3321 highlightSidenav();
3322 resizeNav();
3323 }
3324 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003325}
3326
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003327function showSamplesRefTree() {
3328 init_default_samples_navtree(toRoot);
3329}
3330
3331function init_default_samples_navtree(toroot) {
3332 // load json file for navtree data
3333 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3334 // when the file is loaded, initialize the tree
3335 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003336 // hack to remove the "about the samples" link then put it back in
3337 // after we nuke the list to remove the dummy static list of samples
3338 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3339 $("#nav.samples-nav").empty();
3340 $("#nav.samples-nav").append($firstLi);
3341
Scott Mainad08f072013-08-20 16:49:57 -07003342 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003343 highlightSidenav();
3344 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003345 if ($("#jd-content #samples").length) {
3346 showSamples();
3347 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003348 }
3349 });
3350}
3351
Scott Mainf5089842012-08-14 16:31:07 -07003352/* TOGGLE INHERITED MEMBERS */
3353
3354/* Toggle an inherited class (arrow toggle)
3355 * @param linkObj The link that was clicked.
3356 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3357 * 'null' to simply toggle.
3358 */
3359function toggleInherited(linkObj, expand) {
3360 var base = linkObj.getAttribute("id");
3361 var list = document.getElementById(base + "-list");
3362 var summary = document.getElementById(base + "-summary");
3363 var trigger = document.getElementById(base + "-trigger");
3364 var a = $(linkObj);
3365 if ( (expand == null && a.hasClass("closed")) || expand ) {
3366 list.style.display = "none";
3367 summary.style.display = "block";
3368 trigger.src = toRoot + "assets/images/triangle-opened.png";
3369 a.removeClass("closed");
3370 a.addClass("opened");
3371 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3372 list.style.display = "block";
3373 summary.style.display = "none";
3374 trigger.src = toRoot + "assets/images/triangle-closed.png";
3375 a.removeClass("opened");
3376 a.addClass("closed");
3377 }
3378 return false;
3379}
3380
3381/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3382 * @param linkObj The link that was clicked.
3383 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3384 * 'null' to simply toggle.
3385 */
3386function toggleAllInherited(linkObj, expand) {
3387 var a = $(linkObj);
3388 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3389 var expandos = $(".jd-expando-trigger", table);
3390 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3391 expandos.each(function(i) {
3392 toggleInherited(this, true);
3393 });
3394 a.text("[Collapse]");
3395 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3396 expandos.each(function(i) {
3397 toggleInherited(this, false);
3398 });
3399 a.text("[Expand]");
3400 }
3401 return false;
3402}
3403
3404/* Toggle all inherited members in the class (link in the class title)
3405 */
3406function toggleAllClassInherited() {
3407 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3408 var toggles = $(".toggle-all", $("#body-content"));
3409 if (a.text() == "[Expand All]") {
3410 toggles.each(function(i) {
3411 toggleAllInherited(this, true);
3412 });
3413 a.text("[Collapse All]");
3414 } else {
3415 toggles.each(function(i) {
3416 toggleAllInherited(this, false);
3417 });
3418 a.text("[Expand All]");
3419 }
3420 return false;
3421}
3422
3423/* Expand all inherited members in the class. Used when initiating page search */
3424function ensureAllInheritedExpanded() {
3425 var toggles = $(".toggle-all", $("#body-content"));
3426 toggles.each(function(i) {
3427 toggleAllInherited(this, true);
3428 });
3429 $("#toggleAllClassInherited").text("[Collapse All]");
3430}
3431
3432
3433/* HANDLE KEY EVENTS
3434 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3435 */
3436var agent = navigator['userAgent'].toLowerCase();
3437var mac = agent.indexOf("macintosh") != -1;
3438
3439$(document).keydown( function(e) {
3440var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3441 if (control && e.which == 70) { // 70 is "F"
3442 ensureAllInheritedExpanded();
3443 }
3444});
Scott Main498d7102013-08-21 15:47:38 -07003445
3446
3447
3448
3449
3450
3451/* On-demand functions */
3452
3453/** Move sample code line numbers out of PRE block and into non-copyable column */
3454function initCodeLineNumbers() {
3455 var numbers = $("#codesample-block a.number");
3456 if (numbers.length) {
3457 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3458 }
3459
3460 $(document).ready(function() {
3461 // select entire line when clicked
3462 $("span.code-line").click(function() {
3463 if (!shifted) {
3464 selectText(this);
3465 }
3466 });
3467 // invoke line link on double click
3468 $(".code-line").dblclick(function() {
3469 document.location.hash = $(this).attr('id');
3470 });
3471 // highlight the line when hovering on the number
3472 $("#codesample-line-numbers a.number").mouseover(function() {
3473 var id = $(this).attr('href');
3474 $(id).css('background','#e7e7e7');
3475 });
3476 $("#codesample-line-numbers a.number").mouseout(function() {
3477 var id = $(this).attr('href');
3478 $(id).css('background','none');
3479 });
3480 });
3481}
3482
3483// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3484var shifted = false;
3485$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3486
3487// courtesy of jasonedelman.com
3488function selectText(element) {
3489 var doc = document
3490 , range, selection
3491 ;
3492 if (doc.body.createTextRange) { //ms
3493 range = doc.body.createTextRange();
3494 range.moveToElementText(element);
3495 range.select();
3496 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003497 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003498 range = doc.createRange();
3499 range.selectNodeContents(element);
3500 selection.removeAllRanges();
3501 selection.addRange(range);
3502 }
Scott Main285f0772013-08-22 23:22:09 +00003503}
Scott Main03aca9a2013-10-31 07:20:55 -07003504
3505
3506
3507
3508/** Display links and other information about samples that match the
3509 group specified by the URL */
3510function showSamples() {
3511 var group = $("#samples").attr('class');
3512 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3513
3514 var $ul = $("<ul>");
3515 $selectedLi = $("#nav li.selected");
3516
3517 $selectedLi.children("ul").children("li").each(function() {
3518 var $li = $("<li>").append($(this).find("a").first().clone());
3519 $ul.append($li);
3520 });
3521
3522 $("#samples").append($ul);
3523
3524}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003525
3526
3527
3528/* ########################################################## */
3529/* ################### RESOURCE CARDS ##################### */
3530/* ########################################################## */
3531
3532/** Handle resource queries, collections, and grids (sections). Requires
3533 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3534
3535(function() {
3536 // Prevent the same resource from being loaded more than once per page.
3537 var addedPageResources = {};
3538
3539 $(document).ready(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003540 // Need to initialize hero carousel before other sections for dedupe
3541 // to work correctly.
3542 $('[data-carousel-query]').dacCarouselQuery();
3543
Dirk Doughertyc3921652014-05-13 16:55:26 -07003544 $('.resource-widget').each(function() {
3545 initResourceWidget(this);
3546 });
3547
3548 /* Pass the line height to ellipsisfade() to adjust the height of the
3549 text container to show the max number of lines possible, without
3550 showing lines that are cut off. This works with the css ellipsis
3551 classes to fade last text line and apply an ellipsis char. */
3552
Dirk Dougherty29e93432015-05-05 18:17:13 -07003553 //card text currently uses 20px line height.
3554 var lineHeight = 20;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003555 $('.card-info .text').ellipsisfade(lineHeight);
3556 });
3557
3558 /*
3559 Three types of resource layouts:
3560 Flow - Uses a fixed row-height flow using float left style.
3561 Carousel - Single card slideshow all same dimension absolute.
3562 Stack - Uses fixed columns and flexible element height.
3563 */
3564 function initResourceWidget(widget) {
3565 var $widget = $(widget);
3566 var isFlow = $widget.hasClass('resource-flow-layout'),
3567 isCarousel = $widget.hasClass('resource-carousel-layout'),
3568 isStack = $widget.hasClass('resource-stack-layout');
3569
Dirk Dougherty29e93432015-05-05 18:17:13 -07003570 // remove illegal col-x class which is not relevant anymore thanks to responsive styles.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003571 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
Dirk Dougherty29e93432015-05-05 18:17:13 -07003572 if (m && !$widget.is('.cols > *')) {
3573 $widget.removeClass('col-' + m[1]);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003574 }
3575
3576 var opts = {
3577 cardSizes: ($widget.data('cardsizes') || '').split(','),
3578 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3579 itemsPerPage: $widget.data('itemsperpage'),
3580 sortOrder: $widget.data('sortorder'),
3581 query: $widget.data('query'),
3582 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003583 /* Added by LFL 6/6/14 */
3584 resourceStyle: $widget.data('resourcestyle') || 'card',
3585 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003586 };
3587
3588 // run the search for the set of resources to show
3589
3590 var resources = buildResourceList(opts);
3591
3592 if (isFlow) {
3593 drawResourcesFlowWidget($widget, opts, resources);
3594 } else if (isCarousel) {
3595 drawResourcesCarouselWidget($widget, opts, resources);
3596 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003597 /* Looks like this got removed and is not used, so repurposing for the
3598 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003599 Modified by LFL 6/6/14
3600 */
3601 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003602 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003603 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003604 }
3605 }
3606
3607 /* Initializes a Resource Carousel Widget */
3608 function drawResourcesCarouselWidget($widget, opts, resources) {
3609 $widget.empty();
Dirk Dougherty29e93432015-05-05 18:17:13 -07003610 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003611
3612 $widget.addClass('resource-card slideshow-container')
3613 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3614 .append($('<a>').addClass('slideshow-next').text('Next'));
3615
3616 var css = { 'width': $widget.width() + 'px',
3617 'height': $widget.height() + 'px' };
3618
3619 var $ul = $('<ul>');
3620
3621 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003622 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003623 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003624 .decorateResourceCard(resources[i],plusone);
3625
3626 $('<li>').css(css)
3627 .append($card)
3628 .appendTo($ul);
3629 }
3630
3631 $('<div>').addClass('frame')
3632 .append($ul)
3633 .appendTo($widget);
3634
3635 $widget.dacSlideshow({
3636 auto: true,
3637 btnPrev: '.slideshow-prev',
3638 btnNext: '.slideshow-next'
3639 });
3640 };
3641
Robert Lye7eeb402014-06-03 19:35:24 -07003642 /* Initializes a Resource Card Stack Widget (column-based layout)
3643 Modified by LFL 6/6/14
3644 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003645 function drawResourcesStackWidget($widget, opts, resources, sections) {
3646 // Don't empty widget, grab all items inside since they will be the first
3647 // items stacked, followed by the resource query
Dirk Dougherty29e93432015-05-05 18:17:13 -07003648 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003649 var cards = $widget.find('.resource-card').detach().toArray();
3650 var numStacks = opts.numStacks || 1;
3651 var $stacks = [];
3652 var urlString;
3653
3654 for (var i = 0; i < numStacks; ++i) {
3655 $stacks[i] = $('<div>').addClass('resource-card-stack')
3656 .appendTo($widget);
3657 }
3658
3659 var sectionResources = [];
3660
3661 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003662 if (sections) {
3663 for (var i = 0; i < sections.length; ++i) {
3664 if (!sections[i].sections || !sections[i].sections.length) {
3665 // Render it as a resource card
3666 sectionResources.push(
3667 $('<a>')
3668 .addClass('resource-card section-card')
3669 .attr('href', cleanUrl(sections[i].resource.url))
3670 .decorateResourceCard(sections[i].resource,plusone)[0]
3671 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003672
Robert Lye7eeb402014-06-03 19:35:24 -07003673 } else {
3674 cards.push(
3675 $('<div>')
3676 .addClass('resource-card section-card-menu')
3677 .decorateResourceSection(sections[i],plusone)[0]
3678 );
3679 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003680 }
3681 }
3682
3683 cards = cards.concat(sectionResources);
3684
3685 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003686 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003687
Robert Lye7eeb402014-06-03 19:35:24 -07003688 if (opts.resourceStyle.indexOf('related') > -1) {
3689 $card.addClass('related-card');
3690 }
smain@google.com95948b82014-06-16 19:24:25 -07003691
Dirk Doughertyc3921652014-05-13 16:55:26 -07003692 cards.push($card[0]);
3693 }
3694
Robert Lye7eeb402014-06-03 19:35:24 -07003695 if (opts.stackSort != 'false') {
3696 for (var i = 0; i < cards.length; ++i) {
3697 // Find the stack with the shortest height, but give preference to
3698 // left to right order.
3699 var minHeight = $stacks[0].height();
3700 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003701
Robert Lye7eeb402014-06-03 19:35:24 -07003702 for (var j = 1; j < numStacks; ++j) {
3703 var height = $stacks[j].height();
3704 if (height < minHeight - 45) {
3705 minHeight = height;
3706 minIndex = j;
3707 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003708 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003709
Robert Lye7eeb402014-06-03 19:35:24 -07003710 $stacks[minIndex].append($(cards[i]));
3711 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003712 }
3713
3714 };
smain@google.com95948b82014-06-16 19:24:25 -07003715
3716 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003717 Create a resource card using the given resource object and a list of html
3718 configured options. Returns a jquery object containing the element.
3719 */
smain@google.com95948b82014-06-16 19:24:25 -07003720 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003721 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003722
Robert Lye7eeb402014-06-03 19:35:24 -07003723 // The difference here is that generic cards are not entirely clickable
3724 // so its a div instead of an a tag, also the generic one is not given
3725 // the resource-card class so it appears with a transparent background
3726 // and can be styled in whatever way the css setup.
3727 if (opts.resourceStyle == 'generic') {
3728 $el = $('<div>')
3729 .addClass('resource')
3730 .attr('href', cleanUrl(resource.url))
3731 .decorateResource(resource, opts);
3732 } else {
3733 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003734
Robert Lye7eeb402014-06-03 19:35:24 -07003735 $el = $('<a>')
3736 .addClass(cls)
3737 .attr('href', cleanUrl(resource.url))
3738 .decorateResourceCard(resource, plusone);
3739 }
smain@google.com95948b82014-06-16 19:24:25 -07003740
Robert Lye7eeb402014-06-03 19:35:24 -07003741 return $el;
3742 }
Joe Fernandeza9d796a2015-05-05 22:07:42 -07003743
Dirk Dougherty29e93432015-05-05 18:17:13 -07003744 function createResponsiveFlowColumn(cardSize) {
3745 var cardWidth = parseInt(cardSize.match(/(\d+)/)[1], 10);
3746 var column = $('<div>').addClass('col-' + (cardWidth / 3) + 'of6');
3747 if (cardWidth < 9) {
3748 column.addClass('col-tablet-1of2');
3749 } else if (cardWidth > 9 && cardWidth < 18) {
3750 column.addClass('col-tablet-1of1');
3751 }
3752 if (cardWidth < 18) {
3753 column.addClass('col-mobile-1of1')
3754 }
3755 return column;
3756 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003757
3758 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3759 function drawResourcesFlowWidget($widget, opts, resources) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003760 $widget.empty().addClass('cols');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003761 var cardSizes = opts.cardSizes || ['6x6'];
3762 var i = 0, j = 0;
Dirk Dougherty29e93432015-05-05 18:17:13 -07003763 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003764
3765 while (i < resources.length) {
3766 var cardSize = cardSizes[j++ % cardSizes.length];
3767 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Joe Fernandeza9d796a2015-05-05 22:07:42 -07003768
Dirk Dougherty29e93432015-05-05 18:17:13 -07003769 var column = createResponsiveFlowColumn(cardSize).appendTo($widget);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003770
3771 // A stack has a third dimension which is the number of stacked items
3772 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3773 var stackCount = 0;
3774 var $stackDiv = null;
3775
3776 if (isStack) {
3777 // Create a stack container which should have the dimensions defined
3778 // by the product of the items inside.
3779 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
Dirk Dougherty29e93432015-05-05 18:17:13 -07003780 + 'x' + isStack[2] * isStack[3]) .appendTo(column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003781 }
3782
3783 // Build each stack item or just a single item
3784 do {
3785 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003786
Robert Lye7eeb402014-06-03 19:35:24 -07003787 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003788
3789 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003790 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003791
Dirk Doughertyc3921652014-05-13 16:55:26 -07003792 if (isStack) {
3793 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3794 if (++stackCount == parseInt(isStack[3])) {
3795 $card.addClass('resource-card-row-stack-last');
3796 stackCount = 0;
3797 }
3798 } else {
3799 stackCount = 0;
3800 }
3801
Dirk Dougherty29e93432015-05-05 18:17:13 -07003802 $card.appendTo($stackDiv || column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003803
3804 } while (++i < resources.length && stackCount > 0);
3805 }
3806 }
3807
3808 /* Build a site map of resources using a section as a root. */
3809 function buildSectionList(opts) {
3810 if (opts.section && SECTION_BY_ID[opts.section]) {
3811 return SECTION_BY_ID[opts.section].sections || [];
3812 }
3813 return [];
3814 }
3815
3816 function buildResourceList(opts) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003817 return $.queryResources(opts);
3818 }
3819
3820 $.queryResources = function(opts) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003821 var maxResults = opts.maxResults || 100;
3822
3823 var query = opts.query || '';
3824 var expressions = parseResourceQuery(query);
3825 var addedResourceIndices = {};
3826 var results = [];
3827
3828 for (var i = 0; i < expressions.length; i++) {
3829 var clauses = expressions[i];
3830
3831 // build initial set of resources from first clause
3832 var firstClause = clauses[0];
3833 var resources = [];
3834 switch (firstClause.attr) {
3835 case 'type':
3836 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3837 break;
3838 case 'lang':
3839 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3840 break;
3841 case 'tag':
3842 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3843 break;
3844 case 'collection':
3845 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3846 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3847 break;
3848 case 'section':
3849 var urls = SITE_MAP[firstClause.value].sections || [];
3850 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3851 break;
3852 }
3853 // console.log(firstClause.attr + ':' + firstClause.value);
3854 resources = resources || [];
3855
3856 // use additional clauses to filter corpus
3857 if (clauses.length > 1) {
3858 var otherClauses = clauses.slice(1);
3859 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3860 }
3861
3862 // filter out resources already added
3863 if (i > 1) {
3864 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3865 }
3866
3867 // add to list of already added indices
3868 for (var j = 0; j < resources.length; j++) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003869 if (resources[j]) {
3870 addedResourceIndices[resources[j].index] = 1;
3871 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003872 }
3873
3874 // concat to final results list
3875 results = results.concat(resources);
3876 }
3877
3878 if (opts.sortOrder && results.length) {
3879 var attr = opts.sortOrder;
3880
3881 if (opts.sortOrder == 'random') {
3882 var i = results.length, j, temp;
3883 while (--i) {
3884 j = Math.floor(Math.random() * (i + 1));
3885 temp = results[i];
3886 results[i] = results[j];
3887 results[j] = temp;
3888 }
3889 } else {
3890 var desc = attr.charAt(0) == '-';
3891 if (desc) {
3892 attr = attr.substring(1);
3893 }
3894 results = results.sort(function(x,y) {
3895 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3896 });
3897 }
3898 }
3899
3900 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3901 results = results.slice(0, maxResults);
3902
3903 for (var j = 0; j < results.length; ++j) {
3904 addedPageResources[results[j].index] = 1;
3905 }
3906
3907 return results;
3908 }
3909
3910
3911 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3912 return function(resource) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003913 return resource && !addedResourceIndices[resource.index];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003914 };
3915 }
3916
3917
3918 function getResourceMatchesClausesFilter(clauses) {
3919 return function(resource) {
3920 return doesResourceMatchClauses(resource, clauses);
3921 };
3922 }
3923
3924
3925 function doesResourceMatchClauses(resource, clauses) {
3926 for (var i = 0; i < clauses.length; i++) {
3927 var map;
3928 switch (clauses[i].attr) {
3929 case 'type':
3930 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3931 break;
3932 case 'lang':
3933 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3934 break;
3935 case 'tag':
3936 map = IS_RESOURCE_TAGGED[clauses[i].value];
3937 break;
3938 }
3939
3940 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3941 return clauses[i].negative;
3942 }
3943 }
3944 return true;
3945 }
smain@google.com95948b82014-06-16 19:24:25 -07003946
Robert Lye7eeb402014-06-03 19:35:24 -07003947 function cleanUrl(url)
3948 {
3949 if (url && url.indexOf('//') === -1) {
3950 url = toRoot + url;
3951 }
smain@google.com95948b82014-06-16 19:24:25 -07003952
Robert Lye7eeb402014-06-03 19:35:24 -07003953 return url;
3954 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003955
3956
3957 function parseResourceQuery(query) {
3958 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3959 var expressions = [];
3960 var expressionStrs = query.split(',') || [];
3961 for (var i = 0; i < expressionStrs.length; i++) {
3962 var expr = expressionStrs[i] || '';
3963
3964 // Break expression into clauses (clause e.g. 'tag:foo')
3965 var clauses = [];
3966 var clauseStrs = expr.split(/(?=[\+\-])/);
3967 for (var j = 0; j < clauseStrs.length; j++) {
3968 var clauseStr = clauseStrs[j] || '';
3969
3970 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3971 var parts = clauseStr.split(':');
3972 var clause = {};
3973
3974 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3975 if (clause.attr) {
3976 if (clause.attr.charAt(0) == '+') {
3977 clause.attr = clause.attr.substring(1);
3978 } else if (clause.attr.charAt(0) == '-') {
3979 clause.negative = true;
3980 clause.attr = clause.attr.substring(1);
3981 }
3982 }
3983
3984 if (parts.length > 1) {
3985 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3986 }
3987
3988 clauses.push(clause);
3989 }
3990
3991 if (!clauses.length) {
3992 continue;
3993 }
3994
3995 expressions.push(clauses);
3996 }
3997
3998 return expressions;
3999 }
4000})();
4001
4002(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07004003
smain@google.com95948b82014-06-16 19:24:25 -07004004 /*
Robert Lye7eeb402014-06-03 19:35:24 -07004005 Utility method for creating dom for the description area of a card.
4006 Used in decorateResourceCard and decorateResource.
4007 */
4008 function buildResourceCardDescription(resource, plusone) {
4009 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07004010
Robert Lye7eeb402014-06-03 19:35:24 -07004011 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07004012
Robert Lye7eeb402014-06-03 19:35:24 -07004013 if (resource.cta) {
4014 $description.append($('<a>').addClass('cta').html(resource.cta));
4015 }
smain@google.com95948b82014-06-16 19:24:25 -07004016
Robert Lye7eeb402014-06-03 19:35:24 -07004017 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07004018 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07004019 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004020
Robert Lye7eeb402014-06-03 19:35:24 -07004021 $description.append($('<div>').addClass('util')
4022 .append($('<div>').addClass('g-plusone')
4023 .attr('data-size', 'small')
4024 .attr('data-align', 'right')
4025 .attr('data-href', plusurl)));
4026 }
smain@google.com95948b82014-06-16 19:24:25 -07004027
Robert Lye7eeb402014-06-03 19:35:24 -07004028 return $description;
4029 }
smain@google.com95948b82014-06-16 19:24:25 -07004030
4031
Dirk Doughertyc3921652014-05-13 16:55:26 -07004032 /* Simple jquery function to create dom for a standard resource card */
4033 $.fn.decorateResourceCard = function(resource,plusone) {
4034 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07004035 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004036 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07004037
Robert Lye7eeb402014-06-03 19:35:24 -07004038 if (imgUrl.indexOf('//') === -1) {
4039 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07004040 }
Robert Lye7eeb402014-06-03 19:35:24 -07004041
4042 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07004043 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07004044 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07004045 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07004046
Robert Lye7eeb402014-06-03 19:35:24 -07004047 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
4048 .append($('<div>').addClass('section').text(section))
4049 .append($('<div>').addClass('title').html(resource.title))
4050 .append(buildResourceCardDescription(resource, plusone))
4051 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07004052
4053 return this;
4054 };
4055
4056 /* Simple jquery function to create dom for a resource section card (menu) */
4057 $.fn.decorateResourceSection = function(section,plusone) {
4058 var resource = section.resource;
4059 //keep url clean for matching and offline mode handling
4060 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
4061 var $base = $('<a>')
4062 .addClass('card-bg')
4063 .attr('href', resource.url)
4064 .append($('<div>').addClass('card-section-icon')
4065 .append($('<div>').addClass('icon'))
4066 .append($('<div>').addClass('section').html(resource.title)))
4067 .appendTo(this);
4068
4069 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
4070
4071 if (section.sections && section.sections.length) {
4072 // Recurse the section sub-tree to find a resource image.
4073 var stack = [section];
4074
4075 while (stack.length) {
4076 if (stack[0].resource.image) {
4077 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4078 break;
4079 }
4080
4081 if (stack[0].sections) {
4082 stack = stack.concat(stack[0].sections);
4083 }
4084
4085 stack.shift();
4086 }
4087
4088 var $ul = $('<ul>')
4089 .appendTo($cardInfo);
4090
4091 var max = section.sections.length > 3 ? 3 : section.sections.length;
4092
4093 for (var i = 0; i < max; ++i) {
4094
4095 var subResource = section.sections[i];
4096 if (!plusone) {
4097 $('<li>')
4098 .append($('<a>').attr('href', subResource.url)
4099 .append($('<div>').addClass('title').html(subResource.title))
4100 .append($('<div>').addClass('description ellipsis')
4101 .append($('<div>').addClass('text').html(subResource.summary))
4102 .append($('<div>').addClass('util'))))
4103 .appendTo($ul);
4104 } else {
4105 $('<li>')
4106 .append($('<a>').attr('href', subResource.url)
4107 .append($('<div>').addClass('title').html(subResource.title))
4108 .append($('<div>').addClass('description ellipsis')
4109 .append($('<div>').addClass('text').html(subResource.summary))
4110 .append($('<div>').addClass('util')
4111 .append($('<div>').addClass('g-plusone')
4112 .attr('data-size', 'small')
4113 .attr('data-align', 'right')
4114 .attr('data-href', resource.url)))))
4115 .appendTo($ul);
4116 }
4117 }
4118
4119 // Add a more row
4120 if (max < section.sections.length) {
4121 $('<li>')
4122 .append($('<a>').attr('href', resource.url)
4123 .append($('<div>')
4124 .addClass('title')
4125 .text('More')))
4126 .appendTo($ul);
4127 }
4128 } else {
4129 // No sub-resources, just render description?
4130 }
4131
4132 return this;
4133 };
smain@google.com95948b82014-06-16 19:24:25 -07004134
4135
4136
4137
Robert Lye7eeb402014-06-03 19:35:24 -07004138 /* Render other types of resource styles that are not cards. */
4139 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07004140 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004141 'assets/images/resource-card-default-android.jpg';
4142 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004143
Robert Lye7eeb402014-06-03 19:35:24 -07004144 if (imgUrl.indexOf('//') === -1) {
4145 imgUrl = toRoot + imgUrl;
4146 }
smain@google.com95948b82014-06-16 19:24:25 -07004147
Robert Lye7eeb402014-06-03 19:35:24 -07004148 if (linkUrl && linkUrl.indexOf('//') === -1) {
4149 linkUrl = toRoot + linkUrl;
4150 }
4151
4152 $(this).append(
4153 $('<div>').addClass('image')
4154 .css('background-image', 'url(' + imgUrl + ')'),
4155 $('<div>').addClass('info').append(
4156 $('<h4>').addClass('title').html(resource.title),
4157 $('<p>').addClass('summary').html(resource.summary),
4158 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4159 )
4160 );
4161
4162 return this;
4163 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004164})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004165
4166
Dirk Doughertyc3921652014-05-13 16:55:26 -07004167/* Calculate the vertical area remaining */
4168(function($) {
4169 $.fn.ellipsisfade= function(lineHeight) {
4170 this.each(function() {
4171 // get element text
4172 var $this = $(this);
4173 var remainingHeight = $this.parent().parent().height();
4174 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004175 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004176 if ($(this).is(":visible")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004177 var h = $(this).outerHeight(true);
smain@google.comcda1a9a2014-06-19 17:07:46 -07004178 remainingHeight = remainingHeight - h;
4179 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004180 });
4181
4182 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4183 $this.parent().css({'height': adjustedRemainingHeight});
4184 $this.css({'height': "auto"});
4185 });
4186
4187 return this;
4188 };
4189}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004190
4191/*
4192 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004193
Robert Lye7eeb402014-06-03 19:35:24 -07004194 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004195 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004196 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004197
Robert Lye7eeb402014-06-03 19:35:24 -07004198 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004199
Robert Lye7eeb402014-06-03 19:35:24 -07004200 <div class="fullscreen-carousel">
4201 <div class="fullscreen-carousel-content">
4202 <!-- content here -->
4203 </div>
4204 <div class="fullscreen-carousel-content">
4205 <!-- content here -->
4206 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004207
Robert Lye7eeb402014-06-03 19:35:24 -07004208 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004209
Robert Lye7eeb402014-06-03 19:35:24 -07004210 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004211
Robert Lye7eeb402014-06-03 19:35:24 -07004212 Control over how the carousel takes over the screen can mostly be defined in
4213 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004214 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004215 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004216 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004217 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004218
Robert Lye7eeb402014-06-03 19:35:24 -07004219 There is limited functionality for having multiple sections since that request
4220 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4221 scroll between multiple content areas.
4222*/
4223
4224(function() {
4225 $(document).ready(function() {
4226 $('.fullscreen-carousel').each(function() {
4227 initWidget(this);
4228 });
4229 });
4230
4231 function initWidget(widget) {
4232 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004233
Robert Lye7eeb402014-06-03 19:35:24 -07004234 var topOffset = $widget.offset().top;
4235 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4236 var maxHeight = 0;
4237 var minHeight = 0;
4238 var $content = $widget.find('.fullscreen-carousel-content');
4239 var $nextArrow = $widget.find('.next-arrow');
4240 var $prevArrow = $widget.find('.prev-arrow');
4241 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004242
Robert Lye7eeb402014-06-03 19:35:24 -07004243 if ($content.length <= 1) {
4244 $nextArrow.hide();
4245 $prevArrow.hide();
4246 } else {
4247 $nextArrow.click(function() {
4248 var index = ($content.index($curSection) + 1);
4249 $curSection.hide();
4250 $curSection = $($content[index >= $content.length ? 0 : index]);
4251 $curSection.show();
4252 });
smain@google.com95948b82014-06-16 19:24:25 -07004253
Robert Lye7eeb402014-06-03 19:35:24 -07004254 $prevArrow.click(function() {
4255 var index = ($content.index($curSection) - 1);
4256 $curSection.hide();
4257 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4258 $curSection.show();
4259 });
4260 }
4261
4262 // Just hide all content sections except first.
4263 $content.each(function(index) {
4264 if ($(this).height() > minHeight) minHeight = $(this).height();
4265 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4266 });
4267
4268 // Register for changes to window size, and trigger.
4269 $(window).resize(resizeWidget);
4270 resizeWidget();
4271
4272 function resizeWidget() {
4273 var height = $(window).height() - topOffset - padBottom;
4274 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004275 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004276 (maxHeight && height > maxHeight ? maxHeight : height));
4277 }
smain@google.com95948b82014-06-16 19:24:25 -07004278 }
Robert Lye7eeb402014-06-03 19:35:24 -07004279})();
4280
4281
4282
4283
4284
4285/*
4286 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004287
Robert Lye7eeb402014-06-03 19:35:24 -07004288 The following allows tab widgets to be installed via the html below. Each
4289 tab content section should have a data-tab attribute matching one of the
4290 nav items'. Also each tab content section should have a width matching the
4291 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004292
Robert Lye7eeb402014-06-03 19:35:24 -07004293 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004294
Robert Lye7eeb402014-06-03 19:35:24 -07004295 <div class="tab-carousel">
4296 <ul class="tab-nav">
4297 <li><a href="#" data-tab="handsets">Handsets</a>
4298 <li><a href="#" data-tab="wearable">Wearable</a>
4299 <li><a href="#" data-tab="tv">TV</a>
4300 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004301
Robert Lye7eeb402014-06-03 19:35:24 -07004302 <div class="tab-carousel-content">
4303 <div data-tab="handsets">
4304 <!--Full width content here-->
4305 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004306
Robert Lye7eeb402014-06-03 19:35:24 -07004307 <div data-tab="wearable">
4308 <!--Full width content here-->
4309 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004310
Robert Lye7eeb402014-06-03 19:35:24 -07004311 <div data-tab="tv">
4312 <!--Full width content here-->
4313 </div>
4314 </div>
4315 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004316
Robert Lye7eeb402014-06-03 19:35:24 -07004317*/
4318(function() {
4319 $(document).ready(function() {
4320 $('.tab-carousel').each(function() {
4321 initWidget(this);
4322 });
4323 });
4324
4325 function initWidget(widget) {
4326 var $widget = $(widget);
4327 var $nav = $widget.find('.tab-nav');
4328 var $anchors = $nav.find('[data-tab]');
4329 var $li = $nav.find('li');
4330 var $contentContainer = $widget.find('.tab-carousel-content');
4331 var $tabs = $contentContainer.find('[data-tab]');
4332 var $curTab = $($tabs[0]); // Current tab is first tab.
4333 var width = $widget.width();
4334
4335 // Setup nav interactivity.
4336 $anchors.click(function(evt) {
4337 evt.preventDefault();
4338 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004339 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004340 });
smain@google.com95948b82014-06-16 19:24:25 -07004341
Robert Lye7eeb402014-06-03 19:35:24 -07004342 // Add highlight for navigation on first item.
4343 var $highlight = $('<div>').addClass('highlight')
4344 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4345 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004346
Robert Lye7eeb402014-06-03 19:35:24 -07004347 // Store height since we will change contents to absolute.
4348 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004349
Robert Lye7eeb402014-06-03 19:35:24 -07004350 // Absolutely position tabs so they're ready for transition.
4351 $tabs.each(function(index) {
4352 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4353 });
smain@google.com95948b82014-06-16 19:24:25 -07004354
Robert Lye7eeb402014-06-03 19:35:24 -07004355 function transitionWidget($toTab) {
4356 if (!$curTab.is($toTab)) {
4357 var curIndex = $tabs.index($curTab[0]);
4358 var toIndex = $tabs.index($toTab[0]);
4359 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004360
Robert Lye7eeb402014-06-03 19:35:24 -07004361 // Animate content sections.
4362 $toTab.css({left:(width * dir) + 'px'});
4363 $curTab.animate({left:(width * -dir) + 'px'});
4364 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004365
Robert Lye7eeb402014-06-03 19:35:24 -07004366 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004367 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004368 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004369
Robert Lye7eeb402014-06-03 19:35:24 -07004370 // Store new current section.
4371 $curTab = $toTab;
4372 }
4373 }
smain@google.com95948b82014-06-16 19:24:25 -07004374 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08004375})();
Dirk Dougherty29e93432015-05-05 18:17:13 -07004376
4377(function($) {
4378 'use strict';
4379
4380 /**
4381 * Toggle Floating Label state.
4382 * @param {HTMLElement} el - The DOM element.
4383 * @param options
4384 * @constructor
4385 */
4386 function FloatingLabel(el, options) {
4387 this.el = $(el);
4388 this.options = $.extend({}, FloatingLabel.DEFAULTS_, options);
4389 this.group = this.el.closest('.dac-form-input-group');
4390 this.input = this.group.find('.dac-form-input');
4391
4392 this.checkValue_ = this.checkValue_.bind(this);
4393 this.checkValue_();
4394
4395 this.input.on('focus', function() {
4396 this.group.addClass('dac-focused');
4397 }.bind(this));
4398 this.input.on('blur', function() {
4399 this.group.removeClass('dac-focused');
4400 this.checkValue_();
4401 }.bind(this));
4402 this.input.on('keyup', this.checkValue_);
4403 }
4404
4405 /**
4406 * The label is moved out of the textbox when it has a value.
4407 */
4408 FloatingLabel.prototype.checkValue_ = function() {
4409 if (this.input.val().length) {
4410 this.group.addClass('dac-has-value');
4411 } else {
4412 this.group.removeClass('dac-has-value');
4413 }
4414 };
4415
4416 /**
4417 * jQuery plugin
4418 * @param {object} options - Override default options.
4419 */
4420 $.fn.dacFloatingLabel = function(options) {
4421 return this.each(function() {
4422 new FloatingLabel(this, options);
4423 });
4424 };
4425
4426 $(document).on('ready.aranja', function() {
4427 $('.dac-form-floatlabel').each(function() {
4428 $(this).dacFloatingLabel($(this).data());
4429 });
4430 });
4431})(jQuery);
4432
4433/* global toRoot, CAROUSEL_OVERRIDE */
4434(function($) {
4435 // Ordering matters
4436 var TAG_MAP = [
4437 {from: 'developerstory', to: 'Android Developer Story'},
4438 {from: 'googleplay', to: 'Google Play'}
4439 ];
4440
4441 function DacCarouselQuery(el) {
4442 this.el = $(el);
4443
4444 var opts = this.el.data();
4445 opts.maxResults = parseInt(opts.maxResults || '100', 10);
4446 opts.query = opts.carouselQuery;
4447 var resources = $.queryResources(opts);
4448
4449 this.el.empty();
4450 $(resources).map(function() {
4451 var resource = $.extend({}, this, CAROUSEL_OVERRIDE[this.url]);
4452 var slide = $('<article class="dac-expand dac-hero">');
4453 var image = cleanUrl(resource.heroImage || resource.image);
4454 var fullBleed = image && !resource.heroColor;
4455
4456 // Configure background
4457 slide.css({
4458 backgroundImage: fullBleed ? 'url(' + image + ')' : '',
4459 backgroundColor: resource.heroColor || ''
4460 });
4461
4462 // Should copy be inverted
4463 slide.toggleClass('dac-invert', resource.heroInvert || fullBleed);
4464 slide.toggleClass('dac-darken', fullBleed);
4465
4466 var cols = $('<div class="cols dac-hero-content">');
4467
4468 // inline image column
4469 var rightCol = $('<div class="col-1of2 col-push-1of2 dac-hero-figure">')
4470 .appendTo(cols);
4471
4472 if (!fullBleed && image) {
4473 rightCol.append($('<img>').attr('src', image));
4474 }
4475
4476 // info column
4477 $('<div class="col-1of2 col-pull-1of2">')
4478 .append($('<div class="dac-hero-tag">').text(formatTag(resource)))
4479 .append($('<h1 class="dac-hero-title">').text(formatTitle(resource)))
4480 .append($('<p class="dac-hero-description">').text(resource.summary))
4481 .append($('<a class="dac-hero-cta">')
4482 .text(formatCTA(resource))
4483 .attr('href', cleanUrl(resource.url))
4484 .prepend($('<span class="dac-sprite dac-auto-chevron">'))
4485 )
4486 .appendTo(cols);
4487
4488 slide.append(cols.wrap('<div class="wrap">').parent());
4489 return slide[0];
4490 }).prependTo(this.el);
4491
4492 // Pagination element.
4493 this.el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>');
4494
4495 this.el.dacCarousel();
4496 }
4497
4498 function cleanUrl(url) {
4499 if (url && url.indexOf('//') === -1) {
4500 url = toRoot + url;
4501 }
4502 return url;
4503 }
4504
4505 function formatTag(resource) {
4506 // Hmm, need a better more scalable solution for this.
4507 for (var i = 0, mapping; mapping = TAG_MAP[i]; i++) {
4508 if (resource.tags.indexOf(mapping.from) > -1) {
4509 return mapping.to;
4510 }
4511 }
4512 return resource.type;
4513 }
4514
4515 function formatTitle(resource) {
4516 return resource.title.replace(/android developer story: /i, '');
4517 }
4518
4519 function formatCTA(resource) {
4520 return resource.type === 'youtube' ? 'Watch the video' : 'Learn more';
4521 }
4522
4523 // jQuery plugin
4524 $.fn.dacCarouselQuery = function() {
4525 return this.each(function() {
4526 var el = $(this);
4527 var data = el.data('dac.carouselQuery');
4528
4529 if (!data) { el.data('dac.carouselQuery', (data = new DacCarouselQuery(el))); }
4530 });
4531 };
4532
4533 // Data API
4534 $(function() {
4535 $('[data-carousel-query]').dacCarouselQuery();
4536 });
4537})(jQuery);
4538
4539(function($) {
4540 /**
4541 * A CSS based carousel, inspired by SequenceJS.
4542 * @param {jQuery} el
4543 * @param {object} options
4544 * @constructor
4545 */
4546 function DacCarousel(el, options) {
4547 this.el = $(el);
4548 this.options = options = $.extend({}, DacCarousel.OPTIONS, this.el.data(), options || {});
4549 this.frames = this.el.find(options.frameSelector);
4550 this.count = this.frames.size();
4551 this.current = options.start;
4552
4553 this.initPagination();
4554 this.initEvents();
4555 this.initFrame();
4556 }
4557
4558 DacCarousel.OPTIONS = {
4559 auto: true,
4560 autoTime: 10000,
4561 autoMinTime: 5000,
4562 btnPrev: '[data-carousel-prev]',
4563 btnNext: '[data-carousel-next]',
4564 frameSelector: 'article',
4565 loop: true,
4566 start: 0,
4567 pagination: '[data-carousel-pagination]'
4568 };
4569
4570 DacCarousel.prototype.initPagination = function() {
4571 this.pagination = $([]);
4572 if (!this.options.pagination) { return; }
4573
4574 var pagination = $('<ul class="dac-pagination">');
4575 var parent = this.el;
4576 if (typeof this.options.pagination === 'string') { parent = this.el.find(this.options.pagination); }
4577
4578 if (this.count > 1) {
4579 for (var i = 0; i < this.count; i++) {
4580 var li = $('<li class="dac-pagination-item">').text(i);
4581 if (i === this.options.start) { li.addClass('active'); }
4582 li.click(this.go.bind(this, i));
4583
4584 pagination.append(li);
4585 }
4586 this.pagination = pagination.children();
4587 parent.append(pagination);
4588 }
4589 };
4590
4591 DacCarousel.prototype.initEvents = function() {
4592 var that = this;
4593
4594 this.el.hover(function() {
4595 that.pauseRotateTimer();
4596 }, function() {
4597 that.startRotateTimer();
4598 });
4599
4600 $(this.options.btnPrev).click(function(e) {
4601 e.preventDefault();
4602 that.prev();
4603 });
4604
4605 $(this.options.btnNext).click(function(e) {
4606 e.preventDefault();
4607 that.next();
4608 });
4609 };
4610
4611 DacCarousel.prototype.initFrame = function() {
4612 this.frames.removeClass('active').eq(this.options.start).addClass('active');
4613 };
4614
4615 DacCarousel.prototype.startRotateTimer = function() {
4616 if (!this.options.auto || this.rotateTimer) { return; }
4617 this.rotateTimer = setTimeout(this.next.bind(this), this.options.autoTime);
4618 };
4619
4620 DacCarousel.prototype.pauseRotateTimer = function() {
4621 clearTimeout(this.rotateTimer);
4622 this.rotateTimer = null;
4623 };
4624
4625 DacCarousel.prototype.prev = function() {
4626 this.go(this.current - 1);
4627 };
4628
4629 DacCarousel.prototype.next = function() {
4630 this.go(this.current + 1);
4631 };
4632
4633 DacCarousel.prototype.go = function(next) {
4634 // Figure out what the next slide is.
4635 while (this.count > 0 && next >= this.count) { next -= this.count; }
4636 while (next < 0) { next += this.count; }
4637
4638 // Cancel if we're already on that slide.
4639 if (next === this.current) { return; }
4640
4641 // Prepare next slide.
4642 this.frames.eq(next).removeClass('out');
4643
4644 // Recalculate styles before starting slide transition.
4645 var that = this;
4646 resolveStyles(this.el[0], function() {
4647 // Update pagination
4648 that.pagination.removeClass('active').eq(next).addClass('active');
4649
4650 // Transition out current frame
4651 that.frames.eq(that.current).toggleClass('active out');
4652
4653 // Transition in a new frame
4654 that.frames.eq(next).toggleClass('active');
4655
4656 that.current = next;
4657 });
4658 };
4659
4660 // Helper
4661 function resolveStyles(el, callback) {
4662 /*jshint expr:true*/
4663 el.offsetTop;
4664 callback();
4665 }
4666
4667 // jQuery plugin
4668 $.fn.dacCarousel = function() {
4669 this.each(function() {
4670 var $el = $(this);
4671 $el.data('dac-carousel', new DacCarousel(this));
4672 });
4673 return this;
4674 };
4675
4676 // Data API
4677 $(function() {
4678 $('[data-carousel]').dacCarousel();
4679 });
4680})(jQuery);
4681
4682(function($) {
4683 'use strict';
4684
4685 /**
4686 * Toggle the visabilty of the mobile navigation.
4687 * @param {HTMLElement} el - The DOM element.
4688 * @param options
4689 * @constructor
4690 */
4691 function ToggleModal(el, options) {
4692 this.el = $(el);
4693 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
4694 this.el.on('click', this.clickHandler_.bind(this));
4695 }
4696
4697 ToggleModal.DEFAULTS_ = {
4698 toggleClass: 'dac-modal-open'
4699 };
4700
4701 /**
4702 * The actual toggle logic.
4703 * @param event
4704 * @private
4705 */
4706 ToggleModal.prototype.clickHandler_ = function(event) {
4707 event.preventDefault();
4708 //TODO: Toggle a class on the modal itself
4709 $('body').toggleClass(this.options.toggleClass);
4710 $('.dac-modal-dimmer').toggleClass('dac-active');
4711 $('.dac-modal-window').toggleClass('dac-active');
4712 };
4713
4714 /**
4715 * jQuery plugin
4716 * @param {object} options - Override default options.
4717 */
4718 $.fn.dacToggleModal = function(options) {
4719 return this.each(function() {
4720 new ToggleModal(this, options);
4721 });
4722 };
4723
4724 /**
4725 * Data Attribute API
4726 */
4727 $(document).on('ready.aranja', function() {
4728 $('[data-modal-toogle]').each(function() {
4729 $(this).dacToggleModal($(this).data());
4730 });
4731 });
4732})(jQuery);
4733
4734(function($) {
4735 'use strict';
4736
4737 /**
4738 * Toggle the visabilty of the mobile navigation.
4739 * @param {HTMLElement} el - The DOM element.
4740 * @param options
4741 * @constructor
4742 */
4743 function ToggleNav(el, options) {
4744 this.el = $(el);
4745 this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
4746 this.options.target = [this.options.navigation];
4747
4748 if (this.options.body) {this.options.target.push('body')}
4749 if (this.options.dimmer) {this.options.target.push(this.options.dimmer)}
4750
4751 this.el.on('click', this.clickHandler_.bind(this));
4752 }
4753
4754 /**
4755 * ToggleNav Default Settings
4756 * @type {{body: boolean, dimmer: string, navigation: string, toggleClass: string}}
4757 * @private
4758 */
4759 ToggleNav.DEFAULTS_ = {
4760 body: true,
4761 dimmer: '.dac-nav-dimmer',
4762 navigation: '[data-dac-nav]',
4763 toggleClass: 'dac-nav-open'
4764 };
4765
4766 /**
4767 * The actual toggle logic.
4768 * @param event
4769 * @private
4770 */
4771 ToggleNav.prototype.clickHandler_ = function(event) {
4772 event.preventDefault();
4773 $(this.options.target.join(', ')).toggleClass(this.options.toggleClass);
4774 };
4775
4776 /**
4777 * jQuery plugin
4778 * @param {object} options - Override default options.
4779 */
4780 $.fn.dacToggleMobileNav = function(options) {
4781 return this.each(function() {
4782 new ToggleNav(this, options);
4783 });
4784 };
4785
4786 /**
4787 * Data Attribute API
4788 */
4789 $(window).on('load.aranja', function() {
4790 $('[data-dac-toggle-nav]').each(function() {
4791 $(this).dacToggleMobileNav($(this).data());
4792 });
4793 });
4794})(jQuery);
4795
4796(function($) {
4797 'use strict';
4798
4799 /**
4800 * Submit the newsletter form to a Google Form.
4801 * @param {HTMLElement} el - The Form DOM element.
4802 * @constructor
4803 */
4804 function NewsletterForm(el) {
4805 this.el = $(el);
4806 this.url = this.el.attr('action');
4807 this.el.on('submit', this.submitHandler_.bind(this));
4808 }
4809
4810 /**
4811 * Close the modal when the form is sent.
4812 * @private
4813 */
4814 NewsletterForm.prototype.submitHandler_ = function() {
4815 //TODO: Close the modal with an event and let modal.js handle this
4816 $('body').removeClass('dac-modal-open');
4817 $('.dac-modal-dimmer').removeClass('dac-active');
4818 $('.dac-modal-window').removeClass('dac-active');
4819 };
4820
4821 /**
4822 * jQuery plugin
4823 * @param {object} options - Override default options.
4824 */
4825 $.fn.dacNewsletterForm = function(options) {
4826 return this.each(function() {
4827 new NewsletterForm(this, options);
4828 });
4829 };
4830
4831 /**
4832 * Data Attribute API
4833 */
4834 $(document).on('ready.aranja', function() {
4835 $('[data-newsletter-form]').each(function() {
4836 $(this).dacNewsletterForm();
4837 });
4838 });
4839})(jQuery);