blob: 372f25757da3ad00828b293adc5f7aaa3bdc802d [file] [log] [blame]
Scott Maine4d8f1b2012-06-21 18:03:05 -07001var classesNav;
2var devdocNav;
3var sidenav;
4var cookie_namespace = 'android_developer';
5var NAV_PREF_TREE = "tree";
6var NAV_PREF_PANELS = "panels";
7var nav_pref;
Scott Maine4d8f1b2012-06-21 18:03:05 -07008var isMobile = false; // true if mobile, so we can adjust some layout
Scott Mainf6145542013-04-01 16:38:11 -07009var mPagePath; // initialized in ready() function
Scott Maine4d8f1b2012-06-21 18:03:05 -070010
Scott Main1b3db112012-07-03 14:06:22 -070011var basePath = getBaseUri(location.pathname);
12var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
Scott Main7e447ed2013-02-19 17:22:37 -080013var GOOGLE_DATA; // combined data for google service apis, used for search suggest
Scott Main3b90aff2013-08-01 18:09:35 -070014
Scott Main25e73002013-03-27 15:24:06 -070015// Ensure that all ajax getScript() requests allow caching
16$.ajaxSetup({
17 cache: true
18});
Scott Maine4d8f1b2012-06-21 18:03:05 -070019
20/****** ON LOAD SET UP STUFF *********/
21
Scott Maine4d8f1b2012-06-21 18:03:05 -070022$(document).ready(function() {
smain@google.com698fff02014-11-20 20:39:33 -080023
Dirk Doughertyb87e3002014-11-18 19:34:34 -080024 // show lang dialog if the URL includes /intl/
25 //if (location.pathname.substring(0,6) == "/intl/") {
26 // var lang = location.pathname.split('/')[2];
27 // if (lang != getLangPref()) {
28 // $("#langMessage a.yes").attr("onclick","changeLangPref('" + lang
29 // + "', true); $('#langMessage').hide(); return false;");
30 // $("#langMessage .lang." + lang).show();
31 // $("#langMessage").show();
32 // }
33 //}
Scott Main7e447ed2013-02-19 17:22:37 -080034
Scott Main0e76e7e2013-03-12 10:24:07 -070035 // load json file for JD doc search suggestions
Scott Main719acb42013-12-05 16:05:09 -080036 $.getScript(toRoot + 'jd_lists_unified.js');
Scott Main7e447ed2013-02-19 17:22:37 -080037 // load json file for Android API search suggestions
38 $.getScript(toRoot + 'reference/lists.js');
39 // load json files for Google services API suggestions
Scott Main9f2971d2013-02-26 13:07:41 -080040 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080041 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
42 if(jqxhr.status === 200) {
Scott Main9f2971d2013-02-26 13:07:41 -080043 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080044 if(jqxhr.status === 200) {
45 // combine GCM and GMS data
46 GOOGLE_DATA = GMS_DATA;
47 var start = GOOGLE_DATA.length;
48 for (var i=0; i<GCM_DATA.length; i++) {
49 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
50 link:GCM_DATA[i].link, type:GCM_DATA[i].type});
51 }
52 }
53 });
54 }
55 });
56
Scott Main0e76e7e2013-03-12 10:24:07 -070057 // setup keyboard listener for search shortcut
58 $('body').keyup(function(event) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -070059 if (event.which == 191 && $(event.target).is(':not(:input)')) {
Scott Main0e76e7e2013-03-12 10:24:07 -070060 $('#search_autocomplete').focus();
61 }
62 });
Scott Main015d6162013-01-29 09:01:52 -080063
Scott Maine4d8f1b2012-06-21 18:03:05 -070064 // init the fullscreen toggle click event
65 $('#nav-swap .fullscreen').click(function(){
66 if ($(this).hasClass('disabled')) {
67 toggleFullscreen(true);
68 } else {
69 toggleFullscreen(false);
70 }
71 });
Scott Main3b90aff2013-08-01 18:09:35 -070072
Scott Maine4d8f1b2012-06-21 18:03:05 -070073 // initialize the divs with custom scrollbars
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -070074 if (window.innerWidth >= 720) {
75 $('.scroll-pane').jScrollPane({verticalGutter: 0});
76 }
Scott Maine4d8f1b2012-06-21 18:03:05 -070077
78 // set up the search close button
Dirk Doughertycbe032f2015-05-22 11:41:40 -070079 $('#search-close').on('click touchend', function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -070080 $searchInput = $('#search_autocomplete');
81 $searchInput.attr('value', '');
82 $(this).addClass("hide");
83 $("#search-container").removeClass('active');
84 $("#search_autocomplete").blur();
Scott Main0e76e7e2013-03-12 10:24:07 -070085 search_focus_changed($searchInput.get(), false);
86 hideResults();
Scott Maine4d8f1b2012-06-21 18:03:05 -070087 });
88
Scott Main3b90aff2013-08-01 18:09:35 -070089
Scott Maine4d8f1b2012-06-21 18:03:05 -070090 //Set up search
91 $("#search_autocomplete").focus(function() {
92 $("#search-container").addClass('active');
93 })
Dirk Doughertycbe032f2015-05-22 11:41:40 -070094 $("#search-container").on('mouseover touchend', function(e) {
95 if ($(e.target).is('#search-close')) { return; }
Scott Maine4d8f1b2012-06-21 18:03:05 -070096 $("#search-container").addClass('active');
97 $("#search_autocomplete").focus();
98 })
99 $("#search-container").mouseout(function() {
100 if ($("#search_autocomplete").is(":focus")) return;
101 if ($("#search_autocomplete").val() == '') {
102 setTimeout(function(){
103 $("#search-container").removeClass('active');
104 $("#search_autocomplete").blur();
105 },250);
106 }
107 })
108 $("#search_autocomplete").blur(function() {
109 if ($("#search_autocomplete").val() == '') {
110 $("#search-container").removeClass('active');
111 }
112 })
113
Scott Main3b90aff2013-08-01 18:09:35 -0700114
Scott Maine4d8f1b2012-06-21 18:03:05 -0700115 // prep nav expandos
116 var pagePath = document.location.pathname;
117 // account for intl docs by removing the intl/*/ path
118 if (pagePath.indexOf("/intl/") == 0) {
119 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
120 }
Scott Mainac2aef52013-02-12 14:15:23 -0800121
Scott Maine4d8f1b2012-06-21 18:03:05 -0700122 if (pagePath.indexOf(SITE_ROOT) == 0) {
123 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
124 pagePath += 'index.html';
125 }
126 }
127
Scott Main01a25452013-02-12 17:32:27 -0800128 // Need a copy of the pagePath before it gets changed in the next block;
129 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
130 var pagePathOriginal = pagePath;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700131 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
132 // If running locally, SITE_ROOT will be a relative path, so account for that by
133 // finding the relative URL to this page. This will allow us to find links on the page
134 // leading back to this page.
135 var pathParts = pagePath.split('/');
136 var relativePagePathParts = [];
137 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
138 for (var i = 0; i < upDirs; i++) {
139 relativePagePathParts.push('..');
140 }
141 for (var i = 0; i < upDirs; i++) {
142 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
143 }
144 relativePagePathParts.push(pathParts[pathParts.length - 1]);
145 pagePath = relativePagePathParts.join('/');
146 } else {
147 // Otherwise the page path is already an absolute URL
148 }
149
Scott Mainac2aef52013-02-12 14:15:23 -0800150 // Highlight the header tabs...
151 // highlight Design tab
Dirk Dougherty29e93432015-05-05 18:17:13 -0700152 var urlSegments = pagePathOriginal.split('/');
153 var navEl = $(".dac-nav-list");
154 var subNavEl = navEl.find(".dac-nav-secondary");
155 var parentNavEl;
Scott Mainac2aef52013-02-12 14:15:23 -0800156
Dirk Dougherty29e93432015-05-05 18:17:13 -0700157 if ($("body").hasClass("design")) {
158 navEl.find("> li.design > a").addClass("selected");
smain@google.com6040ffa2014-06-13 15:06:23 -0700159 // highlight About tabs
160 } else if ($("body").hasClass("about")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700161 if (urlSegments[1] == "about" || urlSegments[1] == "wear" || urlSegments[1] == "tv" || urlSegments[1] == "auto") {
162 navEl.find("> li.home > a").addClass('has-subnav');
163 subNavEl.find("li." + urlSegments[1] + " > a").addClass("selected");
164 } else {
165 navEl.find("> li.home > a").addClass('selected');
smain@google.com6040ffa2014-06-13 15:06:23 -0700166 }
Dirk Doughertycf7a3b92015-05-21 00:52:33 -0700167
168// highlight NDK tabs
169 } else if ($("body").hasClass("ndk")) {
170 parentNavEl = navEl.find("> li.ndk > a");
171 parentNavEl.addClass('has-subnav');
172 if ($("body").hasClass("guide")) {
173 navEl.find("> li.guides > a").addClass("selected ndk");
174 } else if ($("body").hasClass("reference")) {
175 navEl.find("> li.reference > a").addClass("selected ndk");
176 } else if ($("body").hasClass("samples")) {
177 navEl.find("> li.samples > a").addClass("selected ndk");
178 } else if ($("body").hasClass("downloads")) {
179 navEl.find("> li.downloads > a").addClass("selected ndk");
180 }
181
Scott Mainac2aef52013-02-12 14:15:23 -0800182 // highlight Develop tab
183 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700184 parentNavEl = navEl.find("> li.develop > a");
185 parentNavEl.addClass('has-subnav');
186
Scott Mainac2aef52013-02-12 14:15:23 -0800187 // In Develop docs, also highlight appropriate sub-tab
Dirk Dougherty29e93432015-05-05 18:17:13 -0700188 if (urlSegments[1] == "training") {
189 subNavEl.find("li.training > a").addClass("selected");
190 } else if (urlSegments[1] == "guide") {
191 subNavEl.find("li.guide > a").addClass("selected");
192 } else if (urlSegments[1] == "reference") {
Scott Mainac2aef52013-02-12 14:15:23 -0800193 // If the root is reference, but page is also part of Google Services, select Google
194 if ($("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700195 subNavEl.find("li.google > a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800196 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700197 subNavEl.find("li.reference > a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800198 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700199 } else if ((urlSegments[1] == "tools") || (urlSegments[1] == "sdk")) {
200 subNavEl.find("li.tools > a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800201 } else if ($("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700202 subNavEl.find("li.google > a").addClass("selected");
Dirk Dougherty4f7e5152010-09-16 10:43:40 -0700203 } else if ($("body").hasClass("samples")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700204 subNavEl.find("li.samples > a").addClass("selected");
Dirk Dougherty86120022016-02-09 18:00:05 -0800205 } else if ($("body").hasClass("preview")) {
206 subNavEl.find("li.preview > a").addClass("selected");
Dirk Dougherty29e93432015-05-05 18:17:13 -0700207 } else {
208 parentNavEl.removeClass('has-subnav').addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800209 }
Scott Mainac2aef52013-02-12 14:15:23 -0800210 // highlight Distribute tab
211 } else if ($("body").hasClass("distribute")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700212 parentNavEl = navEl.find("> li.distribute > a");
213 parentNavEl.addClass('has-subnav');
Dirk Doughertyc3921652014-05-13 16:55:26 -0700214
Dirk Dougherty29e93432015-05-05 18:17:13 -0700215 if (urlSegments[2] == "users") {
216 subNavEl.find("li.users > a").addClass("selected");
217 } else if (urlSegments[2] == "engage") {
218 subNavEl.find("li.engage > a").addClass("selected");
219 } else if (urlSegments[2] == "monetize") {
220 subNavEl.find("li.monetize > a").addClass("selected");
221 } else if (urlSegments[2] == "analyze") {
222 subNavEl.find("li.analyze > a").addClass("selected");
223 } else if (urlSegments[2] == "tools") {
Dirk Dougherty825c1aa2015-05-26 14:45:09 -0700224 subNavEl.find("li.essentials > a").addClass("selected");
Dirk Dougherty29e93432015-05-05 18:17:13 -0700225 } else if (urlSegments[2] == "stories") {
226 subNavEl.find("li.stories > a").addClass("selected");
227 } else if (urlSegments[2] == "essentials") {
228 subNavEl.find("li.essentials > a").addClass("selected");
229 } else if (urlSegments[2] == "googleplay") {
230 subNavEl.find("li.googleplay > a").addClass("selected");
231 } else {
232 parentNavEl.removeClass('has-subnav').addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700233 }
Scott Mainb16376f2014-05-21 20:35:47 -0700234 }
Scott Mainac2aef52013-02-12 14:15:23 -0800235
Scott Mainf6145542013-04-01 16:38:11 -0700236 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
237 // and highlight the sidenav
238 mPagePath = pagePath;
239 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700240 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800241
Scott Mainf6145542013-04-01 16:38:11 -0700242 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700243 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700244 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700245 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800246 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700247
248 // set up prev links
249 var $prevLink = [];
250 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700251
Scott Maine4d8f1b2012-06-21 18:03:05 -0700252 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
253false; // navigate across topic boundaries only in design docs
254 if ($prevListItem.length) {
smain@google.comc91ecb72014-06-23 10:22:23 -0700255 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Scott Main5a1123e2012-09-26 12:51:28 -0700256 // jump to last topic of previous section
257 $prevLink = $prevListItem.find('a:last');
258 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700259 // jump to previous topic in this section
260 $prevLink = $prevListItem.find('a:eq(0)');
261 }
262 } else {
263 // jump to this section's index page (if it exists)
264 var $parentListItem = $selListItem.parents('li');
265 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700266
Scott Maine4d8f1b2012-06-21 18:03:05 -0700267 // except if cross boundaries aren't allowed, and we're at the top of a section already
268 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700269 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700270 && $selListItem.hasClass('nav-section')) {
271 $prevLink = [];
272 }
273 }
274
Scott Maine4d8f1b2012-06-21 18:03:05 -0700275 // set up next links
276 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700277 var startClass = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700278 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700279
Scott Main1a00f7f2013-10-29 11:11:19 -0700280 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700281 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700282 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700283
284 // if there aren't any children, go to the next section (required for About pages)
285 if($nextLink.length == 0) {
286 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700287 } else if ($('.topic-start-link').length) {
288 // as long as there's a child link and there is a "topic start link" (we're on a landing)
289 // then set the landing page "start link" text to be the first doc title
290 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700291 }
Scott Main3b90aff2013-08-01 18:09:35 -0700292
Scott Main5a1123e2012-09-26 12:51:28 -0700293 // If the selected page has a description, then it's a class or article homepage
294 if ($selListItem.find('a[description]').length) {
295 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700296 startClass = true;
297 }
298 } else {
299 // jump to the next topic in this section (if it exists)
300 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700301 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700302 isCrossingBoundary = true;
303 // no more topics in this section, jump to the first topic in the next section
smain@google.comabf34112014-06-23 11:39:02 -0700304 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Scott Main5a1123e2012-09-26 12:51:28 -0700305 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
306 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700307 if ($nextLink.length == 0) {
308 // if that doesn't work, we're at the end of the list, so disable NEXT link
309 $('.next-page-link').attr('href','').addClass("disabled")
310 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700311 // and completely hide the one in the footer
312 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700313 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700314 }
315 }
316 }
Scott Main5a1123e2012-09-26 12:51:28 -0700317
318 if (startClass) {
319 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
320
Scott Main3b90aff2013-08-01 18:09:35 -0700321 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700322 // then we need to add a bottom border to button
323 if (!$("#tb").length) {
324 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700325 }
Scott Main5a1123e2012-09-26 12:51:28 -0700326 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
327 $('.content-footer.next-class').show();
328 $('.next-page-link').attr('href','')
329 .removeClass("hide").addClass("disabled")
330 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700331 // and completely hide the one in the footer
332 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700333 if ($nextLink.length) {
334 $('.next-class-link').attr('href',$nextLink.attr('href'))
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700335 .removeClass("hide")
336 .append(": " + $nextLink.html());
Scott Main1a00f7f2013-10-29 11:11:19 -0700337 $('.next-class-link').find('.new').empty();
338 }
Scott Main5a1123e2012-09-26 12:51:28 -0700339 } else {
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700340 $('.next-page-link').attr('href', $nextLink.attr('href'))
341 .removeClass("hide");
342 // for the footer link, also add the next page title
343 $('.content-footer .next-page-link').append(": " + $nextLink.html());
Scott Main5a1123e2012-09-26 12:51:28 -0700344 }
345
346 if (!startClass && $prevLink.length) {
347 var prevHref = $prevLink.attr('href');
348 if (prevHref == SITE_ROOT + 'index.html') {
349 // Don't show Previous when it leads to the homepage
350 } else {
351 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
352 }
Scott Main3b90aff2013-08-01 18:09:35 -0700353 }
Scott Main5a1123e2012-09-26 12:51:28 -0700354
Scott Maine4d8f1b2012-06-21 18:03:05 -0700355 }
Scott Main3b90aff2013-08-01 18:09:35 -0700356
357
358
Scott Main5a1123e2012-09-26 12:51:28 -0700359 // Set up the course landing pages for Training with class names and descriptions
360 if ($('body.trainingcourse').length) {
361 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700362
363 // create an array for all the class descriptions
364 var $classDescriptions = new Array($classLinks.length);
365 var lang = getLangPref();
366 $classLinks.each(function(index) {
367 var langDescr = $(this).attr(lang + "-description");
368 if (typeof langDescr !== 'undefined' && langDescr !== false) {
369 // if there's a class description in the selected language, use that
370 $classDescriptions[index] = langDescr;
371 } else {
372 // otherwise, use the default english description
373 $classDescriptions[index] = $(this).attr("description");
374 }
375 });
Scott Main3b90aff2013-08-01 18:09:35 -0700376
Scott Main5a1123e2012-09-26 12:51:28 -0700377 var $olClasses = $('<ol class="class-list"></ol>');
378 var $liClass;
Scott Main5a1123e2012-09-26 12:51:28 -0700379 var $h2Title;
380 var $pSummary;
381 var $olLessons;
382 var $liLesson;
383 $classLinks.each(function(index) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700384 $liClass = $('<li class="clearfix"></li>');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700385 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2 class="norule">' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700386 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700387
Scott Main5a1123e2012-09-26 12:51:28 -0700388 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700389
Scott Main5a1123e2012-09-26 12:51:28 -0700390 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700391
Scott Main5a1123e2012-09-26 12:51:28 -0700392 if ($lessons.length) {
Scott Main5a1123e2012-09-26 12:51:28 -0700393 $lessons.each(function(index) {
394 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
395 });
396 } else {
Scott Main5a1123e2012-09-26 12:51:28 -0700397 $pSummary.addClass('article');
398 }
399
Dirk Dougherty29e93432015-05-05 18:17:13 -0700400 $liClass.append($h2Title).append($pSummary).append($olLessons);
Scott Main5a1123e2012-09-26 12:51:28 -0700401 $olClasses.append($liClass);
402 });
403 $('.jd-descr').append($olClasses);
404 }
405
Scott Maine4d8f1b2012-06-21 18:03:05 -0700406 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700407 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700408
Scott Main3b90aff2013-08-01 18:09:35 -0700409
Scott Maine4d8f1b2012-06-21 18:03:05 -0700410 $(".scroll-pane").scroll(function(event) {
411 event.preventDefault();
412 return false;
413 });
414
415 /* Resize nav height when window height changes */
416 $(window).resize(function() {
417 if ($('#side-nav').length == 0) return;
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700418 setNavBarDimensions(); // do this even if sidenav isn't fixed because it could become fixed
Scott Maine4d8f1b2012-06-21 18:03:05 -0700419 // make sidenav behave when resizing the window and side-scolling is a concern
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700420 updateSideNavDimensions();
421 checkSticky();
422 resizeNav(250);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700423 });
424
Scott Maine4d8f1b2012-06-21 18:03:05 -0700425 if ($('#devdoc-nav').length) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700426 setNavBarDimensions();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700427 }
428
429
Scott Maine4d8f1b2012-06-21 18:03:05 -0700430 // Set up play-on-hover <video> tags.
431 $('video.play-on-hover').bind('click', function(){
432 $(this).get(0).load(); // in case the video isn't seekable
433 $(this).get(0).play();
434 });
435
436 // Set up tooltips
437 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700438 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700439 var $target = $(this);
440 var $tooltip = $('<div>')
441 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700442 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700443 .hide()
444 .appendTo('body');
445 $target.removeAttr('title');
446
447 $target.hover(function() {
448 // in
449 var targetRect = $target.offset();
450 targetRect.width = $target.width();
451 targetRect.height = $target.height();
452
453 $tooltip.css({
454 left: targetRect.left,
455 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
456 });
457 $tooltip.addClass('below');
458 $tooltip.show();
459 }, function() {
460 // out
461 $tooltip.hide();
462 });
463 });
464
465 // Set up <h2> deeplinks
466 $('h2').click(function() {
467 var id = $(this).attr('id');
468 if (id) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700469 if (history && history.replaceState) {
470 // Change url without scrolling.
471 history.replaceState({}, '', '#' + id);
472 } else {
473 document.location.hash = id;
474 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700475 }
476 });
477
478 //Loads the +1 button
479 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
480 po.src = 'https://apis.google.com/js/plusone.js';
481 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
482
Scott Maine4d8f1b2012-06-21 18:03:05 -0700483 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700484
Scott Maine4d8f1b2012-06-21 18:03:05 -0700485 if ($(".scroll-pane").length > 1) {
486 // Check if there's a user preference for the panel heights
487 var cookieHeight = readCookie("reference_height");
488 if (cookieHeight) {
489 restoreHeight(cookieHeight);
490 }
491 }
Scott Main3b90aff2013-08-01 18:09:35 -0700492
Scott Main06f3f2c2014-05-30 11:23:00 -0700493 // Resize once loading is finished
Scott Maine4d8f1b2012-06-21 18:03:05 -0700494 resizeNav();
Scott Main06f3f2c2014-05-30 11:23:00 -0700495 // Check if there's an anchor that we need to scroll into view.
496 // A delay is needed, because some browsers do not immediately scroll down to the anchor
497 window.setTimeout(offsetScrollForSticky, 100);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700498
Scott Main015d6162013-01-29 09:01:52 -0800499 /* init the language selector based on user cookie for lang */
500 loadLangPref();
501 changeNavLang(getLangPref());
502
503 /* setup event handlers to ensure the overflow menu is visible while picking lang */
504 $("#language select")
505 .mousedown(function() {
506 $("div.morehover").addClass("hover"); })
507 .blur(function() {
508 $("div.morehover").removeClass("hover"); });
509
510 /* some global variable setup */
511 resizePackagesNav = $("#resize-packages-nav");
512 classesNav = $("#classes-nav");
513 devdocNav = $("#devdoc-nav");
514
515 var cookiePath = "";
516 if (location.href.indexOf("/reference/") != -1) {
517 cookiePath = "reference_";
518 } else if (location.href.indexOf("/guide/") != -1) {
519 cookiePath = "guide_";
520 } else if (location.href.indexOf("/tools/") != -1) {
521 cookiePath = "tools_";
522 } else if (location.href.indexOf("/training/") != -1) {
523 cookiePath = "training_";
524 } else if (location.href.indexOf("/design/") != -1) {
525 cookiePath = "design_";
526 } else if (location.href.indexOf("/distribute/") != -1) {
527 cookiePath = "distribute_";
528 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700529
smain@google.com698fff02014-11-20 20:39:33 -0800530
531 /* setup shadowbox for any videos that want it */
smain@google.comf75ee212014-11-24 09:42:59 -0800532 var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
smain@google.com698fff02014-11-20 20:39:33 -0800533 if ($videoLinks.length) {
534 // if there's at least one, add the shadowbox HTML to the body
535 $('body').prepend(
536'<div id="video-container">'+
537 '<div id="video-frame">'+
538 '<div class="video-close">'+
539 '<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
540 '</div>'+
541 '<div id="youTubePlayer"></div>'+
542 '</div>'+
543'</div>');
544
545 // loads the IFrame Player API code asynchronously.
546 $.getScript("https://www.youtube.com/iframe_api");
547
548 $videoLinks.each(function() {
549 var videoId = $(this).attr('href').split('?v=')[1];
550 $(this).click(function(event) {
551 event.preventDefault();
552 startYouTubePlayer(videoId);
553 });
554 });
smain@google.com698fff02014-11-20 20:39:33 -0800555 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700556});
Scott Main7e447ed2013-02-19 17:22:37 -0800557// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700558
559
smain@google.com698fff02014-11-20 20:39:33 -0800560var youTubePlayer;
561function onYouTubeIframeAPIReady() {
562}
563
smain@google.com3de83c12014-12-12 19:06:52 -0800564/* Returns the height the shadowbox video should be. It's based on the current
565 height of the "video-frame" element, which is 100% height for the window.
566 Then minus the margin so the video isn't actually the full window height. */
567function getVideoHeight() {
568 var frameHeight = $("#video-frame").height();
569 var marginTop = $("#video-frame").css('margin-top').split('px')[0];
570 return frameHeight - (marginTop * 2);
571}
572
smain@google.comd162be52015-02-05 13:27:16 -0800573var mPlayerPaused = false;
574
smain@google.com698fff02014-11-20 20:39:33 -0800575function startYouTubePlayer(videoId) {
smain@google.com3de83c12014-12-12 19:06:52 -0800576 $("#video-container").show();
577 $("#video-frame").show();
smain@google.comd162be52015-02-05 13:27:16 -0800578 mPlayerPaused = false;
smain@google.com3de83c12014-12-12 19:06:52 -0800579
580 // compute the size of the player so it's centered in window
581 var maxWidth = 940; // the width of the web site content
582 var videoAspect = .5625; // based on 1280x720 resolution
583 var maxHeight = maxWidth * videoAspect;
584 var videoHeight = getVideoHeight();
585 var videoWidth = videoHeight / videoAspect;
586 if (videoWidth > maxWidth) {
587 videoWidth = maxWidth;
588 videoHeight = maxHeight;
smain@google.com570c2212014-11-25 19:01:40 -0800589 }
smain@google.com3de83c12014-12-12 19:06:52 -0800590 $("#video-frame").css('width', videoWidth);
591
592 // check if we've already created this player
smain@google.com698fff02014-11-20 20:39:33 -0800593 if (youTubePlayer == null) {
smain@google.com3de83c12014-12-12 19:06:52 -0800594 // check if there's a start time specified
595 var idAndHash = videoId.split("#");
596 var startTime = 0;
597 if (idAndHash.length > 1) {
598 startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0;
599 }
600 // enable localized player
601 var lang = getLangPref();
602 var captionsOn = lang == 'en' ? 0 : 1;
603
smain@google.com698fff02014-11-20 20:39:33 -0800604 youTubePlayer = new YT.Player('youTubePlayer', {
smain@google.com3de83c12014-12-12 19:06:52 -0800605 height: videoHeight,
606 width: videoWidth,
smain@google.com570c2212014-11-25 19:01:40 -0800607 videoId: idAndHash[0],
smain@google.com481f15c2014-12-12 11:57:27 -0800608 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
smain@google.com698fff02014-11-20 20:39:33 -0800609 events: {
smain@google.comf75ee212014-11-24 09:42:59 -0800610 'onReady': onPlayerReady,
611 'onStateChange': onPlayerStateChange
smain@google.com698fff02014-11-20 20:39:33 -0800612 }
613 });
614 } else {
smain@google.com3de83c12014-12-12 19:06:52 -0800615 // reset the size in case the user adjusted the window since last play
616 youTubePlayer.setSize(videoWidth, videoHeight);
smain@google.com94e0b532014-12-16 19:07:08 -0800617 // if a video different from the one already playing was requested, cue it up
smain@google.comd162be52015-02-05 13:27:16 -0800618 if (videoId != youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]) {
smain@google.com94e0b532014-12-16 19:07:08 -0800619 youTubePlayer.cueVideoById(videoId);
620 }
smain@google.com698fff02014-11-20 20:39:33 -0800621 youTubePlayer.playVideo();
622 }
smain@google.com698fff02014-11-20 20:39:33 -0800623}
624
625function onPlayerReady(event) {
626 event.target.playVideo();
smain@google.comd162be52015-02-05 13:27:16 -0800627 mPlayerPaused = false;
smain@google.com698fff02014-11-20 20:39:33 -0800628}
629
630function closeVideo() {
631 try {
smain@google.comf75ee212014-11-24 09:42:59 -0800632 youTubePlayer.pauseVideo();
smain@google.com698fff02014-11-20 20:39:33 -0800633 } catch(e) {
smain@google.com698fff02014-11-20 20:39:33 -0800634 }
smain@google.com3de83c12014-12-12 19:06:52 -0800635 $("#video-container").fadeOut(200);
smain@google.com698fff02014-11-20 20:39:33 -0800636}
637
smain@google.comf75ee212014-11-24 09:42:59 -0800638/* Track youtube playback for analytics */
639function onPlayerStateChange(event) {
640 // Video starts, send the video ID
641 if (event.data == YT.PlayerState.PLAYING) {
smain@google.comd162be52015-02-05 13:27:16 -0800642 if (mPlayerPaused) {
643 ga('send', 'event', 'Videos', 'Resume',
644 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]);
645 } else {
646 // track the start playing event so we know from which page the video was selected
647 ga('send', 'event', 'Videos', 'Start: ' +
648 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
649 'on: ' + document.location.href);
650 }
651 mPlayerPaused = false;
smain@google.comf75ee212014-11-24 09:42:59 -0800652 }
653 // Video paused, send video ID and video elapsed time
654 if (event.data == YT.PlayerState.PAUSED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800655 ga('send', 'event', 'Videos', 'Paused',
smain@google.comd162be52015-02-05 13:27:16 -0800656 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
657 youTubePlayer.getCurrentTime());
658 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800659 }
660 // Video finished, send video ID and video elapsed time
661 if (event.data == YT.PlayerState.ENDED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800662 ga('send', 'event', 'Videos', 'Finished',
smain@google.comd162be52015-02-05 13:27:16 -0800663 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
664 youTubePlayer.getCurrentTime());
665 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800666 }
667}
668
smain@google.com698fff02014-11-20 20:39:33 -0800669
670
Scott Mainad08f072013-08-20 16:49:57 -0700671function initExpandableNavItems(rootTag) {
672 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
673 var section = $(this).closest('li.nav-section');
674 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700675 /* hide me and descendants */
676 section.find('ul').slideUp(250, function() {
677 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700678 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700679 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700680 resizeNav();
681 });
682 } else {
683 /* show me */
684 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700685 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700686 $others.removeClass('expanded').children('ul').slideUp(250);
687
688 // now expand me
689 section.closest('li').addClass('expanded');
690 section.children('ul').slideDown(250, function() {
691 resizeNav();
692 });
693 }
694 });
Scott Mainf0093852013-08-22 11:37:11 -0700695
696 // Stop expand/collapse behavior when clicking on nav section links
697 // (since we're navigating away from the page)
698 // This selector captures the first instance of <a>, but not those with "#" as the href.
699 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
700 window.location.href = $(this).attr('href');
701 return false;
702 });
Scott Mainad08f072013-08-20 16:49:57 -0700703}
704
Dirk Doughertyc3921652014-05-13 16:55:26 -0700705
706/** Create the list of breadcrumb links in the sticky header */
707function buildBreadcrumbs() {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700708 var $breadcrumbUl = $(".dac-header-crumbs");
709 var primaryNavLink = ".dac-nav-list > .dac-nav-item > .dac-nav-link";
710
Dirk Doughertyc3921652014-05-13 16:55:26 -0700711 // Add the secondary horizontal nav item, if provided
Dirk Dougherty29e93432015-05-05 18:17:13 -0700712 var $selectedSecondNav = $(".dac-nav-secondary .dac-nav-link.selected").clone()
713 .attr('class', 'dac-header-crumbs-link');
714
Dirk Doughertyc3921652014-05-13 16:55:26 -0700715 if ($selectedSecondNav.length) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700716 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedSecondNav));
Dirk Doughertyc3921652014-05-13 16:55:26 -0700717 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700718
Dirk Doughertyc3921652014-05-13 16:55:26 -0700719 // Add the primary horizontal nav
Dirk Dougherty29e93432015-05-05 18:17:13 -0700720 var $selectedFirstNav = $(primaryNavLink + ".selected, " + primaryNavLink + ".has-subnav").clone()
721 .attr('class', 'dac-header-crumbs-link');
722
Dirk Doughertyc3921652014-05-13 16:55:26 -0700723 // If there's no header nav item, use the logo link and title from alt text
724 if ($selectedFirstNav.length < 1) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700725 $selectedFirstNav = $('<a class="dac-header-crumbs-link">')
Dirk Doughertyc3921652014-05-13 16:55:26 -0700726 .attr('href', $("div#header .logo a").attr('href'))
727 .text($("div#header .logo img").attr('alt'));
728 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700729 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedFirstNav));
Dirk Doughertyc3921652014-05-13 16:55:26 -0700730}
731
732
733
Scott Maine624b3f2013-09-12 12:56:41 -0700734/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700735function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700736 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
737 if ($("ul#nav li.selected").length) {
738 unHighlightSidenav();
739 }
740 // look for URL in sidenav, including the hash
741 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
742
743 // If the selNavLink is still empty, look for it without the hash
744 if ($selNavLink.length == 0) {
745 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
746 }
747
Scott Mainf6145542013-04-01 16:38:11 -0700748 var $selListItem;
749 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700750 // Find this page's <li> in sidenav and set selected
751 $selListItem = $selNavLink.closest('li');
752 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700753
Scott Mainf6145542013-04-01 16:38:11 -0700754 // Traverse up the tree and expand all parent nav-sections
755 $selNavLink.parents('li.nav-section').each(function() {
756 $(this).addClass('expanded');
757 $(this).children('ul').show();
758 });
759 }
760}
761
Scott Maine624b3f2013-09-12 12:56:41 -0700762function unHighlightSidenav() {
763 $("ul#nav li.selected").removeClass("selected");
764 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
765}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700766
767function toggleFullscreen(enable) {
768 var delay = 20;
769 var enabled = true;
770 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
771 if (enable) {
772 // Currently NOT USING fullscreen; enable fullscreen
773 stylesheet.removeAttr('disabled');
774 $('#nav-swap .fullscreen').removeClass('disabled');
775 $('#devdoc-nav').css({left:''});
776 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
777 enabled = true;
778 } else {
779 // Currently USING fullscreen; disable fullscreen
780 stylesheet.attr('disabled', 'disabled');
781 $('#nav-swap .fullscreen').addClass('disabled');
782 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
783 enabled = false;
784 }
smain@google.com6bdcb982014-11-14 11:53:07 -0800785 writeCookie("fullscreen", enabled, null);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700786 setNavBarDimensions();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700787 resizeNav(delay);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700788 updateSideNavDimensions();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700789 setTimeout(initSidenavHeightResize,delay);
790}
791
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700792// TODO: Refactor into a closure.
793var navBarLeftPos;
794var navBarWidth;
795function setNavBarDimensions() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700796 navBarLeftPos = $('#body-content').offset().left;
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700797 navBarWidth = $('#side-nav').width();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700798}
799
800
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700801function updateSideNavDimensions() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700802 var newLeft = $(window).scrollLeft() - navBarLeftPos;
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700803 $('#devdoc-nav').css({left: -newLeft, width: navBarWidth});
Dirk Dougherty29e93432015-05-05 18:17:13 -0700804 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('padding-left')))});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700805}
Scott Main3b90aff2013-08-01 18:09:35 -0700806
Scott Maine4d8f1b2012-06-21 18:03:05 -0700807// TODO: use $(document).ready instead
808function addLoadEvent(newfun) {
809 var current = window.onload;
810 if (typeof window.onload != 'function') {
811 window.onload = newfun;
812 } else {
813 window.onload = function() {
814 current();
815 newfun();
816 }
817 }
818}
819
820var agent = navigator['userAgent'].toLowerCase();
821// If a mobile phone, set flag and do mobile setup
822if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
823 (agent.indexOf("blackberry") != -1) ||
824 (agent.indexOf("webos") != -1) ||
825 (agent.indexOf("mini") != -1)) { // opera mini browsers
826 isMobile = true;
827}
828
829
Scott Main498d7102013-08-21 15:47:38 -0700830$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700831 $("pre:not(.no-pretty-print)").addClass("prettyprint");
832 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700833});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700834
Scott Maine4d8f1b2012-06-21 18:03:05 -0700835
836
837
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700838/* ######### RESIZE THE SIDENAV ########## */
Scott Maine4d8f1b2012-06-21 18:03:05 -0700839
840function resizeNav(delay) {
841 var $nav = $("#devdoc-nav");
842 var $window = $(window);
843 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700844
Scott Maine4d8f1b2012-06-21 18:03:05 -0700845 // Get the height of entire window and the total header height.
846 // Then figure out based on scroll position whether the header is visible
847 var windowHeight = $window.height();
848 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700849 var headerHeight = $('#header-wrapper').outerHeight();
850 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700851
852 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700853 // Could be either margin or top position, depending on whether the nav is fixed.
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700854 var topMargin = (parseInt($nav.css('top')) || 20) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700855 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700856
Scott Maine4d8f1b2012-06-21 18:03:05 -0700857 // Depending on whether the header is visible, set the side nav's height.
858 if (headerVisible) {
859 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700860 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700861 } else {
862 // Once header is off screen, the nav height is almost full window height
863 navHeight = windowHeight - topMargin;
864 }
Scott Main3b90aff2013-08-01 18:09:35 -0700865
866
867
Scott Maine4d8f1b2012-06-21 18:03:05 -0700868 $scrollPanes = $(".scroll-pane");
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700869 if ($window.width() < 720) {
870 $nav.css('height', '');
871 } else if ($scrollPanes.length > 1) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700872 // subtract the height of the api level widget and nav swapper from the available nav height
873 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700874
Scott Maine4d8f1b2012-06-21 18:03:05 -0700875 $("#swapper").css({height:navHeight + "px"});
876 if ($("#nav-tree").is(":visible")) {
877 $("#nav-tree").css({height:navHeight});
878 }
Scott Main3b90aff2013-08-01 18:09:35 -0700879
880 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700881 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700882
883 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700884 // then the package panel should begin to shrink
885 if (parseInt(classesHeight) <= 0) {
886 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
887 $("#packages-nav").css({height:navHeight - 10});
888 }
Scott Main3b90aff2013-08-01 18:09:35 -0700889
Scott Maine4d8f1b2012-06-21 18:03:05 -0700890 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
891 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700892
893
Scott Maine4d8f1b2012-06-21 18:03:05 -0700894 } else {
895 $nav.height(navHeight);
896 }
Scott Main3b90aff2013-08-01 18:09:35 -0700897
Scott Maine4d8f1b2012-06-21 18:03:05 -0700898 if (delay) {
899 updateFromResize = true;
900 delayedReInitScrollbars(delay);
901 } else {
902 reInitScrollbars();
903 }
Scott Main3b90aff2013-08-01 18:09:35 -0700904
Scott Maine4d8f1b2012-06-21 18:03:05 -0700905}
906
907var updateScrollbars = false;
908var updateFromResize = false;
909
910/* Re-initialize the scrollbars to account for changed nav size.
911 * This method postpones the actual update by a 1/4 second in order to optimize the
912 * scroll performance while the header is still visible, because re-initializing the
913 * scroll panes is an intensive process.
914 */
915function delayedReInitScrollbars(delay) {
916 // If we're scheduled for an update, but have received another resize request
917 // before the scheduled resize has occured, just ignore the new request
918 // (and wait for the scheduled one).
919 if (updateScrollbars && updateFromResize) {
920 updateFromResize = false;
921 return;
922 }
Scott Main3b90aff2013-08-01 18:09:35 -0700923
Scott Maine4d8f1b2012-06-21 18:03:05 -0700924 // We're scheduled for an update and the update request came from this method's setTimeout
925 if (updateScrollbars && !updateFromResize) {
926 reInitScrollbars();
927 updateScrollbars = false;
928 } else {
929 updateScrollbars = true;
930 updateFromResize = false;
931 setTimeout('delayedReInitScrollbars()',delay);
932 }
933}
934
935/* Re-initialize the scrollbars to account for changed nav size. */
936function reInitScrollbars() {
937 var pane = $(".scroll-pane").each(function(){
938 var api = $(this).data('jsp');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700939 if (!api) {return;}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700940 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700941 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700942 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
943}
944
945
946/* Resize the height of the nav panels in the reference,
947 * and save the new size to a cookie */
948function saveNavPanels() {
949 var basePath = getBaseUri(location.pathname);
950 var section = basePath.substring(1,basePath.indexOf("/",1));
smain@google.com6bdcb982014-11-14 11:53:07 -0800951 writeCookie("height", resizePackagesNav.css("height"), section);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700952}
953
954
955
956function restoreHeight(packageHeight) {
957 $("#resize-packages-nav").height(packageHeight);
958 $("#packages-nav").height(packageHeight);
959 // var classesHeight = navHeight - packageHeight;
960 // $("#classes-nav").css({height:classesHeight});
961 // $("#classes-nav .jspContainer").css({height:classesHeight});
962}
963
964
965
966/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
967
968
969
970
971
Scott Main3b90aff2013-08-01 18:09:35 -0700972/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700973 This is called when the page finished loading. */
974function scrollIntoView(nav) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700975 return;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700976 var $nav = $("#"+nav);
977 var element = $nav.jScrollPane({/* ...settings... */});
978 var api = element.data('jsp');
979
980 if ($nav.is(':visible')) {
981 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700982 if ($selected.length == 0) {
983 // If no selected item found, exit
984 return;
985 }
Scott Main52dd2062013-08-15 12:22:28 -0700986 // get the selected item's offset from its container nav by measuring the item's offset
987 // relative to the document then subtract the container nav's offset relative to the document
Dirk Dougherty6f10d4d2015-11-07 11:34:59 -0800988 var selectedOffset = $selected.offset().top - $nav.offset().top + 60;
Scott Main52dd2062013-08-15 12:22:28 -0700989 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
990 // if it's more than 80% down the nav
991 // scroll the item up by an amount equal to 80% the container nav's height
992 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700993 }
994 }
995}
996
997
998
999
1000
1001
1002/* Show popup dialogs */
1003function showDialog(id) {
1004 $dialog = $("#"+id);
1005 $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>');
1006 $dialog.wrapInner('<div/>');
1007 $dialog.removeClass("hide");
1008}
1009
1010
1011
1012
1013
1014/* ######### COOKIES! ########## */
1015
1016function readCookie(cookie) {
1017 var myCookie = cookie_namespace+"_"+cookie+"=";
1018 if (document.cookie) {
1019 var index = document.cookie.indexOf(myCookie);
1020 if (index != -1) {
1021 var valStart = index + myCookie.length;
1022 var valEnd = document.cookie.indexOf(";", valStart);
1023 if (valEnd == -1) {
1024 valEnd = document.cookie.length;
1025 }
1026 var val = document.cookie.substring(valStart, valEnd);
1027 return val;
1028 }
1029 }
1030 return 0;
1031}
1032
smain@google.com6bdcb982014-11-14 11:53:07 -08001033function writeCookie(cookie, val, section) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001034 if (val==undefined) return;
1035 section = section == null ? "_" : "_"+section+"_";
smain@google.com6bdcb982014-11-14 11:53:07 -08001036 var age = 2*365*24*60*60; // set max-age to 2 years
Scott Main3b90aff2013-08-01 18:09:35 -07001037 var cookieValue = cookie_namespace + section + cookie + "=" + val
smain@google.com80e38f42014-11-03 10:47:12 -08001038 + "; max-age=" + age +"; path=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -07001039 document.cookie = cookieValue;
1040}
1041
1042/* ######### END COOKIES! ########## */
1043
1044
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001045var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -07001046var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001047var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -07001048/* Sets the vertical scoll position at which the sticky bar should appear.
1049 This method is called to reset the position when search results appear or hide */
1050function setStickyTop() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001051 stickyTop = $('#header-wrapper').outerHeight() - $('#header > .dac-header-inner').outerHeight();
Dirk Doughertyc3921652014-05-13 16:55:26 -07001052}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001053
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001054/*
Scott Mainb16376f2014-05-21 20:35:47 -07001055 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -07001056 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001057$(window).scroll(function(event) {
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001058 // Exit if the mouse target is a DIV, because that means the event is coming
1059 // from a scrollable div and so there's no need to make adjustments to our layout
1060 if ($(event.target).nodeName == "DIV") {
1061 return;
1062 }
1063
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001064 checkSticky();
1065});
1066
1067function checkSticky() {
1068 setStickyTop();
1069 var $headerEl = $('#header');
1070 // Exit if there's no sidenav
1071 if ($('#side-nav').length == 0) return;
1072
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001073 var top = $(window).scrollTop();
1074 // we set the navbar fixed when the scroll position is beyond the height of the site header...
Dirk Doughertycf7a3b92015-05-21 00:52:33 -07001075 var shouldBeSticky = top > stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001076 // ... except if the document content is shorter than the sidenav height.
1077 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1078 if ($("#doc-col").height() < $("#side-nav").height()) {
1079 shouldBeSticky = false;
1080 }
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001081 // Nor on mobile
1082 if (window.innerWidth < 720) {
1083 shouldBeSticky = false;
1084 }
Scott Mainf5257812014-05-22 17:26:38 -07001085 // Account for horizontal scroll
1086 var scrollLeft = $(window).scrollLeft();
1087 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1088 if (sticky && (scrollLeft != prevScrollLeft)) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001089 updateSideNavDimensions();
Scott Mainf5257812014-05-22 17:26:38 -07001090 prevScrollLeft = scrollLeft;
1091 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001092
1093 // Don't continue if the header is sufficently far away
1094 // (to avoid intensive resizing that slows scrolling)
1095 if (sticky == shouldBeSticky) {
1096 return;
1097 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001098
1099 // If sticky header visible and position is now near top, hide sticky
1100 if (sticky && !shouldBeSticky) {
1101 sticky = false;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001102 // make the sidenav static again
1103 $('#devdoc-nav')
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001104 .removeClass('fixed')
1105 .css({'width':'auto','margin':''});
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001106 // delay hide the sticky
Dirk Dougherty29e93432015-05-05 18:17:13 -07001107 $headerEl.removeClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001108
1109 // update the sidenaav position for side scrolling
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001110 updateSideNavDimensions();
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001111 } else if (!sticky && shouldBeSticky) {
1112 sticky = true;
Dirk Dougherty29e93432015-05-05 18:17:13 -07001113 $headerEl.addClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001114
1115 // make the sidenav fixed
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001116 $('#devdoc-nav')
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001117 .addClass('fixed');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001118
1119 // update the sidenaav position for side scrolling
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001120 updateSideNavDimensions();
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001121
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001122 }
1123 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001124}
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001125
1126/*
1127 * Manages secion card states and nav resize to conclude loading
1128 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001129(function() {
1130 $(document).ready(function() {
1131
Dirk Doughertyc3921652014-05-13 16:55:26 -07001132 // Stack hover states
1133 $('.section-card-menu').each(function(index, el) {
1134 var height = $(el).height();
1135 $(el).css({height:height+'px', position:'relative'});
1136 var $cardInfo = $(el).find('.card-info');
1137
1138 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1139 });
1140
Dirk Doughertyc3921652014-05-13 16:55:26 -07001141 });
1142
1143})();
1144
Scott Maine4d8f1b2012-06-21 18:03:05 -07001145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
Scott Maind7026f72013-06-17 15:08:49 -07001158/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001159
1160
1161
1162
1163
1164function toggle(obj, slide) {
1165 var ul = $("ul:first", obj);
1166 var li = ul.parent();
1167 if (li.hasClass("closed")) {
1168 if (slide) {
1169 ul.slideDown("fast");
1170 } else {
1171 ul.show();
1172 }
1173 li.removeClass("closed");
1174 li.addClass("open");
1175 $(".toggle-img", li).attr("title", "hide pages");
1176 } else {
1177 ul.slideUp("fast");
1178 li.removeClass("open");
1179 li.addClass("closed");
1180 $(".toggle-img", li).attr("title", "show pages");
1181 }
1182}
1183
1184
Scott Maine4d8f1b2012-06-21 18:03:05 -07001185function buildToggleLists() {
1186 $(".toggle-list").each(
1187 function(i) {
1188 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1189 $(this).addClass("closed");
1190 });
1191}
1192
1193
1194
Scott Maind7026f72013-06-17 15:08:49 -07001195function hideNestedItems(list, toggle) {
1196 $list = $(list);
1197 // hide nested lists
1198 if($list.hasClass('showing')) {
1199 $("li ol", $list).hide('fast');
1200 $list.removeClass('showing');
1201 // show nested lists
1202 } else {
1203 $("li ol", $list).show('fast');
1204 $list.addClass('showing');
1205 }
1206 $(".more,.less",$(toggle)).toggle();
1207}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001208
1209
smain@google.com95948b82014-06-16 19:24:25 -07001210/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1211function setupIdeDocToggle() {
1212 $( "select.ide" ).change(function() {
1213 var selected = $(this).find("option:selected").attr("value");
1214 $(".select-ide").hide();
1215 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001216
smain@google.com95948b82014-06-16 19:24:25 -07001217 $("select.ide").val(selected);
1218 });
1219}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244/* REFERENCE NAV SWAP */
1245
1246
1247function getNavPref() {
1248 var v = readCookie('reference_nav');
1249 if (v != NAV_PREF_TREE) {
1250 v = NAV_PREF_PANELS;
1251 }
1252 return v;
1253}
1254
1255function chooseDefaultNav() {
1256 nav_pref = getNavPref();
1257 if (nav_pref == NAV_PREF_TREE) {
1258 $("#nav-panels").toggle();
1259 $("#panel-link").toggle();
1260 $("#nav-tree").toggle();
1261 $("#tree-link").toggle();
1262 }
1263}
1264
1265function swapNav() {
1266 if (nav_pref == NAV_PREF_TREE) {
1267 nav_pref = NAV_PREF_PANELS;
1268 } else {
1269 nav_pref = NAV_PREF_TREE;
1270 init_default_navtree(toRoot);
1271 }
smain@google.com6bdcb982014-11-14 11:53:07 -08001272 writeCookie("nav", nav_pref, "reference");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001273
1274 $("#nav-panels").toggle();
1275 $("#panel-link").toggle();
1276 $("#nav-tree").toggle();
1277 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001278
Scott Maine4d8f1b2012-06-21 18:03:05 -07001279 resizeNav();
1280
1281 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1282 $("#nav-tree .jspContainer:visible")
1283 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1284 // Another nasty hack to make the scrollbar appear now that we have height
1285 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001286
Scott Maine4d8f1b2012-06-21 18:03:05 -07001287 if ($("#nav-tree").is(':visible')) {
1288 scrollIntoView("nav-tree");
1289 } else {
1290 scrollIntoView("packages-nav");
1291 scrollIntoView("classes-nav");
1292 }
1293}
1294
1295
1296
Scott Mainf5089842012-08-14 16:31:07 -07001297/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001298/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001299/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001300
1301function getBaseUri(uri) {
1302 var intlUrl = (uri.substring(0,6) == "/intl/");
1303 if (intlUrl) {
1304 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1305 base = base.substring(base.indexOf('/')+1, base.length);
1306 //alert("intl, returning base url: /" + base);
1307 return ("/" + base);
1308 } else {
1309 //alert("not intl, returning uri as found.");
1310 return uri;
1311 }
1312}
1313
1314function requestAppendHL(uri) {
1315//append "?hl=<lang> to an outgoing request (such as to blog)
1316 var lang = getLangPref();
1317 if (lang) {
1318 var q = 'hl=' + lang;
1319 uri += '?' + q;
1320 window.location = uri;
1321 return false;
1322 } else {
1323 return true;
1324 }
1325}
1326
1327
Scott Maine4d8f1b2012-06-21 18:03:05 -07001328function changeNavLang(lang) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001329 if (lang === 'en') { return; }
1330
Dirk Doughertya2c9b912015-10-05 16:45:19 -07001331 var $links = $("a[" + lang + "-lang],p[" + lang + "-lang]");
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001332 $links.each(function(){ // for each link with a translation
Scott Main6eb95f12012-10-02 17:12:23 -07001333 var $link = $(this);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001334 // put the desired language from the attribute as the text
1335 $link.text($link.attr(lang + '-lang'))
Scott Main6eb95f12012-10-02 17:12:23 -07001336 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001337}
1338
Scott Main015d6162013-01-29 09:01:52 -08001339function changeLangPref(lang, submit) {
smain@google.com6bdcb982014-11-14 11:53:07 -08001340 writeCookie("pref_lang", lang, null);
Scott Main015d6162013-01-29 09:01:52 -08001341
1342 // ####### TODO: Remove this condition once we're stable on devsite #######
1343 // This condition is only needed if we still need to support legacy GAE server
1344 if (devsite) {
1345 // Switch language when on Devsite server
1346 if (submit) {
1347 $("#setlang").submit();
1348 }
1349 } else {
1350 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001351 if (submit) {
1352 window.location = getBaseUri(location.pathname);
1353 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001354 }
1355}
1356
1357function loadLangPref() {
1358 var lang = readCookie("pref_lang");
1359 if (lang != 0) {
1360 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1361 }
1362}
1363
1364function getLangPref() {
1365 var lang = $("#language").find(":selected").attr("value");
1366 if (!lang) {
1367 lang = readCookie("pref_lang");
1368 }
1369 return (lang != 0) ? lang : 'en';
1370}
1371
1372/* ########## END LOCALIZATION ############ */
1373
1374
1375
1376
1377
1378
1379/* Used to hide and reveal supplemental content, such as long code samples.
1380 See the companion CSS in android-developer-docs.css */
1381function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001382 var div = $(obj).closest(".toggle-content");
1383 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001384 if (div.hasClass("closed")) { // if it's closed, open it
1385 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001386 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001387 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001388 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001389 + "assets/images/triangle-opened.png");
1390 } else { // if it's open, close it
1391 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001392 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001393 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001394 div.find(".toggle-content").removeClass("open").addClass("closed")
1395 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001396 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001397 + "assets/images/triangle-closed.png");
1398 });
1399 }
1400 return false;
1401}
Scott Mainf5089842012-08-14 16:31:07 -07001402
1403
Scott Maindb3678b2012-10-23 14:13:41 -07001404/* New version of expandable content */
1405function toggleExpandable(link,id) {
1406 if($(id).is(':visible')) {
1407 $(id).slideUp();
1408 $(link).removeClass('expanded');
1409 } else {
1410 $(id).slideDown();
1411 $(link).addClass('expanded');
1412 }
1413}
1414
1415function hideExpandable(ids) {
1416 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001417 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001418}
1419
Scott Mainf5089842012-08-14 16:31:07 -07001420
1421
1422
1423
Scott Main3b90aff2013-08-01 18:09:35 -07001424/*
Scott Mainf5089842012-08-14 16:31:07 -07001425 * Slideshow 1.0
1426 * Used on /index.html and /develop/index.html for carousel
1427 *
1428 * Sample usage:
1429 * HTML -
1430 * <div class="slideshow-container">
1431 * <a href="" class="slideshow-prev">Prev</a>
1432 * <a href="" class="slideshow-next">Next</a>
1433 * <ul>
1434 * <li class="item"><img src="images/marquee1.jpg"></li>
1435 * <li class="item"><img src="images/marquee2.jpg"></li>
1436 * <li class="item"><img src="images/marquee3.jpg"></li>
1437 * <li class="item"><img src="images/marquee4.jpg"></li>
1438 * </ul>
1439 * </div>
1440 *
1441 * <script type="text/javascript">
1442 * $('.slideshow-container').dacSlideshow({
1443 * auto: true,
1444 * btnPrev: '.slideshow-prev',
1445 * btnNext: '.slideshow-next'
1446 * });
1447 * </script>
1448 *
1449 * Options:
1450 * btnPrev: optional identifier for previous button
1451 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001452 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001453 * auto: whether or not to auto-proceed
1454 * speed: animation speed
1455 * autoTime: time between auto-rotation
1456 * easing: easing function for transition
1457 * start: item to select by default
1458 * scroll: direction to scroll in
1459 * pagination: whether or not to include dotted pagination
1460 *
1461 */
1462
1463 (function($) {
1464 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001465
Scott Mainf5089842012-08-14 16:31:07 -07001466 //Options - see above
1467 o = $.extend({
1468 btnPrev: null,
1469 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001470 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001471 auto: true,
1472 speed: 500,
1473 autoTime: 12000,
1474 easing: null,
1475 start: 0,
1476 scroll: 1,
1477 pagination: true
1478
1479 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001480
1481 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001482 return this.each(function() {
1483
1484 var running = false;
1485 var animCss = o.vertical ? "top" : "left";
1486 var sizeCss = o.vertical ? "height" : "width";
1487 var div = $(this);
1488 var ul = $("ul", div);
1489 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001490 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001491 var timer = null;
1492
1493 var li = $("li", ul);
1494 var itemLength = li.size();
1495 var curr = o.start;
1496
1497 li.css({float: o.vertical ? "none" : "left"});
1498 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1499 div.css({position: "relative", "z-index": "2", left: "0px"});
1500
1501 var liSize = o.vertical ? height(li) : width(li);
1502 var ulSize = liSize * itemLength;
1503 var divSize = liSize;
1504
1505 li.css({width: li.width(), height: li.height()});
1506 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1507
1508 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001509
Scott Mainf5089842012-08-14 16:31:07 -07001510 //Pagination
1511 if (o.pagination) {
1512 var pagination = $("<div class='pagination'></div>");
1513 var pag_ul = $("<ul></ul>");
1514 if (tl > 1) {
1515 for (var i=0;i<tl;i++) {
1516 var li = $("<li>"+i+"</li>");
1517 pag_ul.append(li);
1518 if (i==o.start) li.addClass('active');
1519 li.click(function() {
1520 go(parseInt($(this).text()));
1521 })
1522 }
1523 pagination.append(pag_ul);
1524 div.append(pagination);
1525 }
1526 }
Scott Main3b90aff2013-08-01 18:09:35 -07001527
Scott Mainf5089842012-08-14 16:31:07 -07001528 //Previous button
1529 if(o.btnPrev)
1530 $(o.btnPrev).click(function(e) {
1531 e.preventDefault();
1532 return go(curr-o.scroll);
1533 });
1534
1535 //Next button
1536 if(o.btnNext)
1537 $(o.btnNext).click(function(e) {
1538 e.preventDefault();
1539 return go(curr+o.scroll);
1540 });
Scott Maineb410352013-01-14 19:03:40 -08001541
1542 //Pause button
1543 if(o.btnPause)
1544 $(o.btnPause).click(function(e) {
1545 e.preventDefault();
1546 if ($(this).hasClass('paused')) {
1547 startRotateTimer();
1548 } else {
1549 pauseRotateTimer();
1550 }
1551 });
Scott Main3b90aff2013-08-01 18:09:35 -07001552
Scott Mainf5089842012-08-14 16:31:07 -07001553 //Auto rotation
1554 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001555
Scott Mainf5089842012-08-14 16:31:07 -07001556 function startRotateTimer() {
1557 clearInterval(timer);
1558 timer = setInterval(function() {
1559 if (curr == tl-1) {
1560 go(0);
1561 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001562 go(curr+o.scroll);
1563 }
Scott Mainf5089842012-08-14 16:31:07 -07001564 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001565 $(o.btnPause).removeClass('paused');
1566 }
1567
1568 function pauseRotateTimer() {
1569 clearInterval(timer);
1570 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001571 }
1572
1573 //Go to an item
1574 function go(to) {
1575 if(!running) {
1576
1577 if(to<0) {
1578 to = itemLength-1;
1579 } else if (to>itemLength-1) {
1580 to = 0;
1581 }
1582 curr = to;
1583
1584 running = true;
1585
1586 ul.animate(
1587 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1588 function() {
1589 running = false;
1590 }
1591 );
1592
1593 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1594 $( (curr-o.scroll<0 && o.btnPrev)
1595 ||
1596 (curr+o.scroll > itemLength && o.btnNext)
1597 ||
1598 []
1599 ).addClass("disabled");
1600
Scott Main3b90aff2013-08-01 18:09:35 -07001601
Scott Mainf5089842012-08-14 16:31:07 -07001602 var nav_items = $('li', pagination);
1603 nav_items.removeClass('active');
1604 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001605
Scott Mainf5089842012-08-14 16:31:07 -07001606
1607 }
1608 if(o.auto) startRotateTimer();
1609 return false;
1610 };
1611 });
1612 };
1613
1614 function css(el, prop) {
1615 return parseInt($.css(el[0], prop)) || 0;
1616 };
1617 function width(el) {
1618 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1619 };
1620 function height(el) {
1621 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1622 };
1623
1624 })(jQuery);
1625
1626
Scott Main3b90aff2013-08-01 18:09:35 -07001627/*
Scott Mainf5089842012-08-14 16:31:07 -07001628 * dacSlideshow 1.0
1629 * Used on develop/index.html for side-sliding tabs
1630 *
1631 * Sample usage:
1632 * HTML -
1633 * <div class="slideshow-container">
1634 * <a href="" class="slideshow-prev">Prev</a>
1635 * <a href="" class="slideshow-next">Next</a>
1636 * <ul>
1637 * <li class="item"><img src="images/marquee1.jpg"></li>
1638 * <li class="item"><img src="images/marquee2.jpg"></li>
1639 * <li class="item"><img src="images/marquee3.jpg"></li>
1640 * <li class="item"><img src="images/marquee4.jpg"></li>
1641 * </ul>
1642 * </div>
1643 *
1644 * <script type="text/javascript">
1645 * $('.slideshow-container').dacSlideshow({
1646 * auto: true,
1647 * btnPrev: '.slideshow-prev',
1648 * btnNext: '.slideshow-next'
1649 * });
1650 * </script>
1651 *
1652 * Options:
1653 * btnPrev: optional identifier for previous button
1654 * btnNext: optional identifier for next button
1655 * auto: whether or not to auto-proceed
1656 * speed: animation speed
1657 * autoTime: time between auto-rotation
1658 * easing: easing function for transition
1659 * start: item to select by default
1660 * scroll: direction to scroll in
1661 * pagination: whether or not to include dotted pagination
1662 *
1663 */
1664 (function($) {
1665 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001666
Scott Mainf5089842012-08-14 16:31:07 -07001667 //Options - see above
1668 o = $.extend({
1669 speed : 250,
1670 easing: null,
1671 nav_id: null,
1672 frame_id: null
1673 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001674
1675 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001676 return this.each(function() {
1677
1678 var curr = 0;
1679 var running = false;
1680 var animCss = "margin-left";
1681 var sizeCss = "width";
1682 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001683
Scott Mainf5089842012-08-14 16:31:07 -07001684 var nav = $(o.nav_id, div);
1685 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001686 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001687 var frame = div.find(o.frame_id);
1688 var content_width = $(frame).find('ul').width();
1689 //Buttons
1690 $(nav_li).click(function(e) {
1691 go($(nav_li).index($(this)));
1692 })
Scott Main3b90aff2013-08-01 18:09:35 -07001693
Scott Mainf5089842012-08-14 16:31:07 -07001694 //Go to an item
1695 function go(to) {
1696 if(!running) {
1697 curr = to;
1698 running = true;
1699
1700 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1701 function() {
1702 running = false;
1703 }
1704 );
1705
Scott Main3b90aff2013-08-01 18:09:35 -07001706
Scott Mainf5089842012-08-14 16:31:07 -07001707 nav_li.removeClass('active');
1708 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001709
Scott Mainf5089842012-08-14 16:31:07 -07001710
1711 }
1712 return false;
1713 };
1714 });
1715 };
1716
1717 function css(el, prop) {
1718 return parseInt($.css(el[0], prop)) || 0;
1719 };
1720 function width(el) {
1721 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1722 };
1723 function height(el) {
1724 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1725 };
1726
1727 })(jQuery);
1728
1729
1730
1731
1732
1733/* ######################################################## */
1734/* ################ SEARCH SUGGESTIONS ################## */
1735/* ######################################################## */
1736
1737
Scott Main7e447ed2013-02-19 17:22:37 -08001738
Scott Main0e76e7e2013-03-12 10:24:07 -07001739var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1740var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1741
Scott Mainf5089842012-08-14 16:31:07 -07001742var gMatches = new Array();
1743var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001744var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001745var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1746var gListLength = 0;
1747
1748
1749var gGoogleMatches = new Array();
1750var ROW_COUNT_GOOGLE = 15; // max number of results in list
1751var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001752
Scott Main0e76e7e2013-03-12 10:24:07 -07001753var gDocsMatches = new Array();
1754var ROW_COUNT_DOCS = 100; // max number of results in list
1755var gDocsListLength = 0;
1756
Scott Mainde295272013-03-25 15:48:35 -07001757function onSuggestionClick(link) {
1758 // When user clicks a suggested document, track it
smain@google.comed677d72014-12-12 10:19:17 -08001759 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
1760 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07001761}
1762
Scott Mainf5089842012-08-14 16:31:07 -07001763function set_item_selected($li, selected)
1764{
1765 if (selected) {
1766 $li.attr('class','jd-autocomplete jd-selected');
1767 } else {
1768 $li.attr('class','jd-autocomplete');
1769 }
1770}
1771
1772function set_item_values(toroot, $li, match)
1773{
1774 var $link = $('a',$li);
1775 $link.html(match.__hilabel || match.label);
1776 $link.attr('href',toroot + match.link);
1777}
1778
Scott Main719acb42013-12-05 16:05:09 -08001779function set_item_values_jd(toroot, $li, match)
1780{
1781 var $link = $('a',$li);
1782 $link.html(match.title);
1783 $link.attr('href',toroot + match.url);
1784}
1785
Scott Main0e76e7e2013-03-12 10:24:07 -07001786function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001787 var $li = $("<li class='jd-autocomplete'></li>");
1788 $list.append($li);
1789
1790 $li.mousedown(function() {
1791 window.location = this.firstChild.getAttribute("href");
1792 });
1793 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001794 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001795 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001796 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1797 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001798 });
Scott Mainde295272013-03-25 15:48:35 -07001799 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001800 $li.attr('class','show-item');
1801 return $li;
1802}
1803
Scott Mainf5089842012-08-14 16:31:07 -07001804function sync_selection_table(toroot)
1805{
Scott Mainf5089842012-08-14 16:31:07 -07001806 var $li; //list item jquery object
1807 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001808
Scott Main0e76e7e2013-03-12 10:24:07 -07001809 // if there are NO results at all, hide all columns
1810 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1811 $('.suggest-card').hide(300);
1812 return;
1813 }
1814
1815 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001816 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001817 // reveal suggestion list
Scott Main0e76e7e2013-03-12 10:24:07 -07001818 $('.suggest-card.reference').show();
1819 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001820
Scott Main0e76e7e2013-03-12 10:24:07 -07001821 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001822 $(".suggest-card.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001823
Scott Main0e76e7e2013-03-12 10:24:07 -07001824 // ########### ANDROID RESULTS #############
1825 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001826
Scott Main0e76e7e2013-03-12 10:24:07 -07001827 // determine android results to show
1828 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1829 gMatches.length : ROW_COUNT_FRAMEWORK;
1830 for (i=0; i<gListLength; i++) {
1831 var $li = new_suggestion($(".suggest-card.reference ul"));
1832 set_item_values(toroot, $li, gMatches[i]);
1833 set_item_selected($li, i == gSelectedIndex);
1834 }
1835 }
Scott Main7e447ed2013-02-19 17:22:37 -08001836
Scott Main0e76e7e2013-03-12 10:24:07 -07001837 // ########### GOOGLE RESULTS #############
1838 if (gGoogleMatches.length > 0) {
1839 // show header for list
1840 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001841
Scott Main0e76e7e2013-03-12 10:24:07 -07001842 // determine google results to show
1843 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1844 for (i=0; i<gGoogleListLength; i++) {
1845 var $li = new_suggestion($(".suggest-card.reference ul"));
1846 set_item_values(toroot, $li, gGoogleMatches[i]);
1847 set_item_selected($li, i == gSelectedIndex);
1848 }
1849 }
Scott Mainf5089842012-08-14 16:31:07 -07001850 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001851 $('.suggest-card.reference').hide();
Scott Main0e76e7e2013-03-12 10:24:07 -07001852 }
1853
1854 // ########### JD DOC RESULTS #############
1855 if (gDocsMatches.length > 0) {
1856 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001857 $(".suggest-card:not(.reference) li").remove();
Scott Main0e76e7e2013-03-12 10:24:07 -07001858
1859 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001860 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1861 // The order must match the reverse order that each section appears as a card in
1862 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001863 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1864 for (i=0; i<gDocsListLength; i++) {
1865 var sugg = gDocsMatches[i];
1866 var $li;
1867 if (sugg.type == "design") {
1868 $li = new_suggestion($(".suggest-card.design ul"));
1869 } else
1870 if (sugg.type == "distribute") {
1871 $li = new_suggestion($(".suggest-card.distribute ul"));
1872 } else
Scott Main719acb42013-12-05 16:05:09 -08001873 if (sugg.type == "samples") {
1874 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1875 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001876 if (sugg.type == "training") {
1877 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1878 } else
Scott Main719acb42013-12-05 16:05:09 -08001879 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001880 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1881 } else {
1882 continue;
1883 }
1884
Scott Main719acb42013-12-05 16:05:09 -08001885 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001886 set_item_selected($li, i == gSelectedIndex);
1887 }
1888
1889 // add heading and show or hide card
1890 if ($(".suggest-card.design li").length > 0) {
1891 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1892 $(".suggest-card.design").show(300);
1893 } else {
1894 $('.suggest-card.design').hide(300);
1895 }
1896 if ($(".suggest-card.distribute li").length > 0) {
1897 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1898 $(".suggest-card.distribute").show(300);
1899 } else {
1900 $('.suggest-card.distribute').hide(300);
1901 }
1902 if ($(".child-card.guides li").length > 0) {
1903 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1904 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1905 }
1906 if ($(".child-card.training li").length > 0) {
1907 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1908 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1909 }
Scott Main719acb42013-12-05 16:05:09 -08001910 if ($(".child-card.samples li").length > 0) {
1911 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1912 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1913 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001914
1915 if ($(".suggest-card.develop li").length > 0) {
1916 $(".suggest-card.develop").show(300);
1917 } else {
1918 $('.suggest-card.develop').hide(300);
1919 }
1920
1921 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001922 $('.suggest-card:not(.reference)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001923 }
1924}
1925
Scott Main0e76e7e2013-03-12 10:24:07 -07001926/** Called by the search input's onkeydown and onkeyup events.
1927 * Handles navigation with keyboard arrows, Enter key to invoke search,
1928 * otherwise invokes search suggestions on key-up event.
1929 * @param e The JS event
1930 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001931 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001932 * @returns True if the event should bubble up
1933 */
Scott Mainf5089842012-08-14 16:31:07 -07001934function search_changed(e, kd, toroot)
1935{
Scott Main719acb42013-12-05 16:05:09 -08001936 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001937 var search = document.getElementById("search_autocomplete");
1938 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001939 // get the ul hosting the currently selected item
1940 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1941 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1942 var $selectedUl = $columns[gSelectedColumn];
1943
Scott Mainf5089842012-08-14 16:31:07 -07001944 // show/hide the close button
1945 if (text != '') {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001946 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07001947 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001948 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07001949 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001950 // 27 = esc
1951 if (e.keyCode == 27) {
1952 // close all search results
Dirk Dougherty29e93432015-05-05 18:17:13 -07001953 if (kd) $('#search-close').trigger('click');
Scott Main0e76e7e2013-03-12 10:24:07 -07001954 return true;
1955 }
Scott Mainf5089842012-08-14 16:31:07 -07001956 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001957 else if (e.keyCode == 13) {
1958 if (gSelectedIndex < 0) {
1959 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001960 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1961 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001962 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001963 return true;
1964 } else {
1965 // otherwise, results are already showing, so allow ajax to auto refresh the results
1966 // and ignore this Enter press to avoid the reload.
1967 return false;
1968 }
1969 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001970 // click the link corresponding to selected item
1971 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001972 return false;
1973 }
1974 }
Scott Mainb16376f2014-05-21 20:35:47 -07001975 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001976 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001977 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001978 if ((sticky ) && (search.value != "")) {
1979 $('body,html').animate({scrollTop:0}, '500', 'swing');
1980 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001981 return true;
1982 }
1983 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001984 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001985 // if the next item is a header, skip it
1986 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001987 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001988 }
1989 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001990 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001991 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001992 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1993 // If user reaches top, reset selected column
1994 if (gSelectedIndex < 0) {
1995 gSelectedColumn = -1;
1996 }
Scott Mainf5089842012-08-14 16:31:07 -07001997 }
1998 return false;
1999 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002000 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07002001 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002002 // if the next item is a header, skip it
2003 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07002004 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08002005 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002006 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
2007 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
2008 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08002009 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07002010 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07002011 }
2012 return false;
2013 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002014 // Consider left/right arrow navigation
2015 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
2016 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
2017 // 37 LEFT ARROW
2018 // go left only if current column is not left-most column (last column)
2019 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
2020 $('li', $selectedUl).removeClass('jd-selected');
2021 gSelectedColumn++;
2022 $selectedUl = $columns[gSelectedColumn];
2023 // keep or reset the selected item to last item as appropriate
2024 gSelectedIndex = gSelectedIndex >
2025 $("li", $selectedUl).length-1 ?
2026 $("li", $selectedUl).length-1 : gSelectedIndex;
2027 // if the corresponding item is a header, move down
2028 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2029 gSelectedIndex++;
2030 }
Scott Main3b90aff2013-08-01 18:09:35 -07002031 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002032 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2033 return false;
2034 }
2035 // 39 RIGHT ARROW
2036 // go right only if current column is not the right-most column (first column)
2037 else if (e.keyCode == 39 && gSelectedColumn > 0) {
2038 $('li', $selectedUl).removeClass('jd-selected');
2039 gSelectedColumn--;
2040 $selectedUl = $columns[gSelectedColumn];
2041 // keep or reset the selected item to last item as appropriate
2042 gSelectedIndex = gSelectedIndex >
2043 $("li", $selectedUl).length-1 ?
2044 $("li", $selectedUl).length-1 : gSelectedIndex;
2045 // if the corresponding item is a header, move down
2046 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2047 gSelectedIndex++;
2048 }
Scott Main3b90aff2013-08-01 18:09:35 -07002049 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002050 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2051 return false;
2052 }
2053 }
2054
Scott Main719acb42013-12-05 16:05:09 -08002055 // if key-up event and not arrow down/up/left/right,
2056 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07002057 else if (!kd && (e.keyCode != 40)
2058 && (e.keyCode != 38)
2059 && (e.keyCode != 37)
2060 && (e.keyCode != 39)) {
2061 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07002062 gMatches = new Array();
2063 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002064 gGoogleMatches = new Array();
2065 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07002066 gDocsMatches = new Array();
2067 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002068
2069 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07002070 for (var i=0; i<DATA.length; i++) {
2071 var s = DATA[i];
2072 if (text.length != 0 &&
2073 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2074 gMatches[matchedCount] = s;
2075 matchedCount++;
2076 }
2077 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002078 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07002079 for (var i=0; i<gMatches.length; i++) {
2080 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08002081 }
2082
2083
2084 // Search for Google matches
2085 for (var i=0; i<GOOGLE_DATA.length; i++) {
2086 var s = GOOGLE_DATA[i];
2087 if (text.length != 0 &&
2088 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2089 gGoogleMatches[matchedCountGoogle] = s;
2090 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07002091 }
2092 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002093 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08002094 for (var i=0; i<gGoogleMatches.length; i++) {
2095 var s = gGoogleMatches[i];
2096 }
2097
Scott Mainf5089842012-08-14 16:31:07 -07002098 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07002099
2100
2101
Scott Main719acb42013-12-05 16:05:09 -08002102 // Search for matching JD docs
Dirk Dougherty9b7f8f22014-11-01 17:08:56 -07002103 if (text.length >= 2) {
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002104 // match only the beginning of a word
2105 var queryStr = text.toLowerCase();
Scott Main719acb42013-12-05 16:05:09 -08002106
2107 // Search for Training classes
2108 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002109 // current search comparison, with counters for tag and title,
2110 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08002111 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07002112 s.matched_tag = 0;
2113 s.matched_title = 0;
2114 var matched = false;
2115
2116 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08002117 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002118 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002119 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002120 matched = true;
2121 s.matched_tag = j + 1; // add 1 to index position
2122 }
2123 }
Scott Main719acb42013-12-05 16:05:09 -08002124 // Don't consider doc title for lessons (only for class landing pages),
2125 // unless the lesson has a tag that already matches
2126 if ((s.lang == currentLang) &&
2127 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002128 // it matches the doc title
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002129 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002130 matched = true;
2131 s.matched_title = 1;
2132 }
2133 }
2134 if (matched) {
2135 gDocsMatches[matchedCountDocs] = s;
2136 matchedCountDocs++;
2137 }
2138 }
Scott Main719acb42013-12-05 16:05:09 -08002139
2140
2141 // Search for API Guides
2142 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2143 // current search comparison, with counters for tag and title,
2144 // used later to improve ranking
2145 var s = GUIDE_RESOURCES[i];
2146 s.matched_tag = 0;
2147 s.matched_title = 0;
2148 var matched = false;
2149
2150 // Check if query matches any tags; work backwards toward 1 to assist ranking
2151 for (var j = s.keywords.length - 1; j >= 0; j--) {
2152 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002153 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
2154
Scott Main719acb42013-12-05 16:05:09 -08002155 matched = true;
2156 s.matched_tag = j + 1; // add 1 to index position
2157 }
2158 }
2159 // Check if query matches the doc title, but only for current language
2160 if (s.lang == currentLang) {
2161 // if query matches the doc title
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002162 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002163 matched = true;
2164 s.matched_title = 1;
2165 }
2166 }
2167 if (matched) {
2168 gDocsMatches[matchedCountDocs] = s;
2169 matchedCountDocs++;
2170 }
2171 }
2172
2173
2174 // Search for Tools Guides
2175 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2176 // current search comparison, with counters for tag and title,
2177 // used later to improve ranking
2178 var s = TOOLS_RESOURCES[i];
2179 s.matched_tag = 0;
2180 s.matched_title = 0;
2181 var matched = false;
2182
2183 // Check if query matches any tags; work backwards toward 1 to assist ranking
2184 for (var j = s.keywords.length - 1; j >= 0; j--) {
2185 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002186 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002187 matched = true;
2188 s.matched_tag = j + 1; // add 1 to index position
2189 }
2190 }
2191 // Check if query matches the doc title, but only for current language
2192 if (s.lang == currentLang) {
2193 // if query matches the doc title
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002194 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002195 matched = true;
2196 s.matched_title = 1;
2197 }
2198 }
2199 if (matched) {
2200 gDocsMatches[matchedCountDocs] = s;
2201 matchedCountDocs++;
2202 }
2203 }
2204
2205
2206 // Search for About docs
2207 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2208 // current search comparison, with counters for tag and title,
2209 // used later to improve ranking
2210 var s = ABOUT_RESOURCES[i];
2211 s.matched_tag = 0;
2212 s.matched_title = 0;
2213 var matched = false;
2214
2215 // Check if query matches any tags; work backwards toward 1 to assist ranking
2216 for (var j = s.keywords.length - 1; j >= 0; j--) {
2217 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002218 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002219 matched = true;
2220 s.matched_tag = j + 1; // add 1 to index position
2221 }
2222 }
2223 // Check if query matches the doc title, but only for current language
2224 if (s.lang == currentLang) {
2225 // if query matches the doc title
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002226 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002227 matched = true;
2228 s.matched_title = 1;
2229 }
2230 }
2231 if (matched) {
2232 gDocsMatches[matchedCountDocs] = s;
2233 matchedCountDocs++;
2234 }
2235 }
2236
2237
2238 // Search for Design guides
2239 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2240 // current search comparison, with counters for tag and title,
2241 // used later to improve ranking
2242 var s = DESIGN_RESOURCES[i];
2243 s.matched_tag = 0;
2244 s.matched_title = 0;
2245 var matched = false;
2246
2247 // Check if query matches any tags; work backwards toward 1 to assist ranking
2248 for (var j = s.keywords.length - 1; j >= 0; j--) {
2249 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002250 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002251 matched = true;
2252 s.matched_tag = j + 1; // add 1 to index position
2253 }
2254 }
2255 // Check if query matches the doc title, but only for current language
2256 if (s.lang == currentLang) {
2257 // if query matches the doc title
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002258 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002259 matched = true;
2260 s.matched_title = 1;
2261 }
2262 }
2263 if (matched) {
2264 gDocsMatches[matchedCountDocs] = s;
2265 matchedCountDocs++;
2266 }
2267 }
2268
2269
2270 // Search for Distribute guides
2271 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2272 // current search comparison, with counters for tag and title,
2273 // used later to improve ranking
2274 var s = DISTRIBUTE_RESOURCES[i];
2275 s.matched_tag = 0;
2276 s.matched_title = 0;
2277 var matched = false;
2278
2279 // Check if query matches any tags; work backwards toward 1 to assist ranking
2280 for (var j = s.keywords.length - 1; j >= 0; j--) {
2281 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002282 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002283 matched = true;
2284 s.matched_tag = j + 1; // add 1 to index position
2285 }
2286 }
2287 // Check if query matches the doc title, but only for current language
2288 if (s.lang == currentLang) {
2289 // if query matches the doc title
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002290 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002291 matched = true;
2292 s.matched_title = 1;
2293 }
2294 }
2295 if (matched) {
2296 gDocsMatches[matchedCountDocs] = s;
2297 matchedCountDocs++;
2298 }
2299 }
2300
2301
2302 // Search for Google guides
2303 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2304 // current search comparison, with counters for tag and title,
2305 // used later to improve ranking
2306 var s = GOOGLE_RESOURCES[i];
2307 s.matched_tag = 0;
2308 s.matched_title = 0;
2309 var matched = false;
2310
2311 // Check if query matches any tags; work backwards toward 1 to assist ranking
2312 for (var j = s.keywords.length - 1; j >= 0; j--) {
2313 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002314 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002315 matched = true;
2316 s.matched_tag = j + 1; // add 1 to index position
2317 }
2318 }
2319 // Check if query matches the doc title, but only for current language
2320 if (s.lang == currentLang) {
2321 // if query matches the doc title
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002322 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002323 matched = true;
2324 s.matched_title = 1;
2325 }
2326 }
2327 if (matched) {
2328 gDocsMatches[matchedCountDocs] = s;
2329 matchedCountDocs++;
2330 }
2331 }
2332
2333
2334 // Search for Samples
2335 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2336 // current search comparison, with counters for tag and title,
2337 // used later to improve ranking
2338 var s = SAMPLES_RESOURCES[i];
2339 s.matched_tag = 0;
2340 s.matched_title = 0;
2341 var matched = false;
2342 // Check if query matches any tags; work backwards toward 1 to assist ranking
2343 for (var j = s.keywords.length - 1; j >= 0; j--) {
2344 // it matches a tag
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002345 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002346 matched = true;
2347 s.matched_tag = j + 1; // add 1 to index position
2348 }
2349 }
2350 // Check if query matches the doc title, but only for current language
2351 if (s.lang == currentLang) {
2352 // if query matches the doc title.t
Dirk Dougherty0b9e2042015-07-02 14:21:55 -07002353 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
Scott Main719acb42013-12-05 16:05:09 -08002354 matched = true;
2355 s.matched_title = 1;
2356 }
2357 }
2358 if (matched) {
2359 gDocsMatches[matchedCountDocs] = s;
2360 matchedCountDocs++;
2361 }
2362 }
2363
Dirk Dougherty86120022016-02-09 18:00:05 -08002364 // Search for Preview Guides
2365 for (var i=0; i<_RESOURCES.length; i++) {
2366 // current search comparison, with counters for tag and title,
2367 // used later to improve ranking
2368 var s = _RESOURCES[i];
2369 s.matched_tag = 0;
2370 s.matched_title = 0;
2371 var matched = false;
2372
2373 // Check if query matches any tags; work backwards toward 1 to assist ranking
2374 for (var j = s.keywords.length - 1; j >= 0; j--) {
2375 // it matches a tag
2376 if (s.keywords[j].toLowerCase().indexOf(queryStr) == 0) {
2377 matched = true;
2378 s.matched_tag = j + 1; // add 1 to index position
2379 }
2380 }
2381 // Check if query matches the doc title, but only for current language
2382 if (s.lang == currentLang) {
2383 // if query matches the doc title
2384 if (s.title.toLowerCase().indexOf(queryStr) == 0) {
2385 matched = true;
2386 s.matched_title = 1;
2387 }
2388 }
2389 if (matched) {
2390 gDocsMatches[matchedCountDocs] = s;
2391 matchedCountDocs++;
2392 }
2393 }
2394
Scott Main719acb42013-12-05 16:05:09 -08002395 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002396 rank_autocomplete_doc_results(text, gDocsMatches);
2397 }
2398
2399 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002400 sync_selection_table(toroot);
2401 return true; // allow the event to bubble up to the search api
2402 }
2403}
2404
Scott Main0e76e7e2013-03-12 10:24:07 -07002405/* Order the jd doc result list based on match quality */
2406function rank_autocomplete_doc_results(query, matches) {
2407 query = query || '';
2408 if (!matches || !matches.length)
2409 return;
2410
2411 var _resultScoreFn = function(match) {
2412 var score = 1.0;
2413
2414 // if the query matched a tag
2415 if (match.matched_tag > 0) {
2416 // multiply score by factor relative to position in tags list (max of 3)
2417 score *= 3 / match.matched_tag;
2418
2419 // if it also matched the title
2420 if (match.matched_title > 0) {
2421 score *= 2;
2422 }
2423 } else if (match.matched_title > 0) {
2424 score *= 3;
2425 }
2426
2427 return score;
2428 };
2429
2430 for (var i=0; i<matches.length; i++) {
2431 matches[i].__resultScore = _resultScoreFn(matches[i]);
2432 }
2433
2434 matches.sort(function(a,b){
2435 var n = b.__resultScore - a.__resultScore;
2436 if (n == 0) // lexicographical sort if scores are the same
2437 n = (a.label < b.label) ? -1 : 1;
2438 return n;
2439 });
2440}
2441
Scott Main7e447ed2013-02-19 17:22:37 -08002442/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002443function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002444 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002445 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002446 return;
2447
2448 // helper function that gets the last occurence index of the given regex
2449 // in the given string, or -1 if not found
2450 var _lastSearch = function(s, re) {
2451 if (s == '')
2452 return -1;
2453 var l = -1;
2454 var tmp;
2455 while ((tmp = s.search(re)) >= 0) {
2456 if (l < 0) l = 0;
2457 l += tmp;
2458 s = s.substr(tmp + 1);
2459 }
2460 return l;
2461 };
2462
2463 // helper function that counts the occurrences of a given character in
2464 // a given string
2465 var _countChar = function(s, c) {
2466 var n = 0;
2467 for (var i=0; i<s.length; i++)
2468 if (s.charAt(i) == c) ++n;
2469 return n;
2470 };
2471
2472 var queryLower = query.toLowerCase();
2473 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2474 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2475 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2476
2477 var _resultScoreFn = function(result) {
2478 // scores are calculated based on exact and prefix matches,
2479 // and then number of path separators (dots) from the last
2480 // match (i.e. favoring classes and deep package names)
2481 var score = 1.0;
2482 var labelLower = result.label.toLowerCase();
2483 var t;
2484 t = _lastSearch(labelLower, partExactAlnumRE);
2485 if (t >= 0) {
2486 // exact part match
2487 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2488 score *= 200 / (partsAfter + 1);
2489 } else {
2490 t = _lastSearch(labelLower, partPrefixAlnumRE);
2491 if (t >= 0) {
2492 // part prefix match
2493 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2494 score *= 20 / (partsAfter + 1);
2495 }
2496 }
2497
2498 return score;
2499 };
2500
Scott Main7e447ed2013-02-19 17:22:37 -08002501 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002502 // if the API is deprecated, default score is 0; otherwise, perform scoring
2503 if (matches[i].deprecated == "true") {
2504 matches[i].__resultScore = 0;
2505 } else {
2506 matches[i].__resultScore = _resultScoreFn(matches[i]);
2507 }
Scott Mainf5089842012-08-14 16:31:07 -07002508 }
2509
Scott Main7e447ed2013-02-19 17:22:37 -08002510 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002511 var n = b.__resultScore - a.__resultScore;
2512 if (n == 0) // lexicographical sort if scores are the same
2513 n = (a.label < b.label) ? -1 : 1;
2514 return n;
2515 });
2516}
2517
Scott Main7e447ed2013-02-19 17:22:37 -08002518/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002519function highlight_autocomplete_result_labels(query) {
2520 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002521 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002522 return;
2523
2524 var queryLower = query.toLowerCase();
2525 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2526 var queryRE = new RegExp(
2527 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2528 for (var i=0; i<gMatches.length; i++) {
2529 gMatches[i].__hilabel = gMatches[i].label.replace(
2530 queryRE, '<b>$1</b>');
2531 }
Scott Main7e447ed2013-02-19 17:22:37 -08002532 for (var i=0; i<gGoogleMatches.length; i++) {
2533 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2534 queryRE, '<b>$1</b>');
2535 }
Scott Mainf5089842012-08-14 16:31:07 -07002536}
2537
2538function search_focus_changed(obj, focused)
2539{
Scott Main3b90aff2013-08-01 18:09:35 -07002540 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002541 if(obj.value == ""){
Dirk Dougherty29e93432015-05-05 18:17:13 -07002542 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002543 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002544 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002545 }
2546}
2547
2548function submit_search() {
2549 var query = document.getElementById('search_autocomplete').value;
2550 location.hash = 'q=' + query;
2551 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002552 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002553 return false;
2554}
2555
2556
2557function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002558 $("#searchResults").slideUp('fast', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002559 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002560 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002561
Scott Mainf5089842012-08-14 16:31:07 -07002562 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002563
Scott Mainf5089842012-08-14 16:31:07 -07002564 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2565 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002566
2567 // forcefully regain key-up event control (previously jacked by search api)
2568 $("#search_autocomplete").keyup(function(event) {
2569 return search_changed(event, false, toRoot);
2570 });
2571
Scott Mainf5089842012-08-14 16:31:07 -07002572 return false;
2573}
2574
2575
2576
2577/* ########################################################## */
2578/* ################ CUSTOM SEARCH ENGINE ################## */
2579/* ########################################################## */
2580
Scott Mainf5089842012-08-14 16:31:07 -07002581var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002582google.load('search', '1', {"callback" : function() {
2583 searchControl = new google.search.SearchControl();
2584 } });
Scott Mainf5089842012-08-14 16:31:07 -07002585
2586function loadSearchResults() {
2587 document.getElementById("search_autocomplete").style.color = "#000";
2588
Scott Mainf5089842012-08-14 16:31:07 -07002589 searchControl = new google.search.SearchControl();
2590
2591 // use our existing search form and use tabs when multiple searchers are used
2592 drawOptions = new google.search.DrawOptions();
2593 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2594 drawOptions.setInput(document.getElementById("search_autocomplete"));
2595
2596 // configure search result options
2597 searchOptions = new google.search.SearcherOptions();
2598 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2599
2600 // configure each of the searchers, for each tab
2601 devSiteSearcher = new google.search.WebSearch();
2602 devSiteSearcher.setUserDefinedLabel("All");
2603 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2604
2605 designSearcher = new google.search.WebSearch();
2606 designSearcher.setUserDefinedLabel("Design");
2607 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2608
2609 trainingSearcher = new google.search.WebSearch();
2610 trainingSearcher.setUserDefinedLabel("Training");
2611 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2612
2613 guidesSearcher = new google.search.WebSearch();
2614 guidesSearcher.setUserDefinedLabel("Guides");
2615 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2616
2617 referenceSearcher = new google.search.WebSearch();
2618 referenceSearcher.setUserDefinedLabel("Reference");
2619 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2620
Scott Maindf08ada2012-12-03 08:54:37 -08002621 googleSearcher = new google.search.WebSearch();
2622 googleSearcher.setUserDefinedLabel("Google Services");
2623 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2624
Scott Mainf5089842012-08-14 16:31:07 -07002625 blogSearcher = new google.search.WebSearch();
2626 blogSearcher.setUserDefinedLabel("Blog");
2627 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2628
2629 // add each searcher to the search control
2630 searchControl.addSearcher(devSiteSearcher, searchOptions);
2631 searchControl.addSearcher(designSearcher, searchOptions);
2632 searchControl.addSearcher(trainingSearcher, searchOptions);
2633 searchControl.addSearcher(guidesSearcher, searchOptions);
2634 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002635 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002636 searchControl.addSearcher(blogSearcher, searchOptions);
2637
2638 // configure result options
2639 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2640 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2641 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2642 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2643
2644 // upon ajax search, refresh the url and search title
2645 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2646 updateResultTitle(query);
2647 var query = document.getElementById('search_autocomplete').value;
2648 location.hash = 'q=' + query;
2649 });
2650
Scott Mainde295272013-03-25 15:48:35 -07002651 // once search results load, set up click listeners
2652 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2653 addResultClickListeners();
2654 });
2655
Scott Mainf5089842012-08-14 16:31:07 -07002656 // draw the search results box
2657 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2658
2659 // get query and execute the search
2660 searchControl.execute(decodeURI(getQuery(location.hash)));
2661
2662 document.getElementById("search_autocomplete").focus();
2663 addTabListeners();
2664}
2665// End of loadSearchResults
2666
2667
2668google.setOnLoadCallback(function(){
2669 if (location.hash.indexOf("q=") == -1) {
2670 // if there's no query in the url, don't search and make sure results are hidden
2671 $('#searchResults').hide();
2672 return;
2673 } else {
2674 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002675 $('#searchResults').slideDown('slow', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002676 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002677 loadSearchResults();
2678 }
2679}, true);
2680
smain@google.com9a818f52014-10-03 09:25:59 -07002681/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2682 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002683function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002684 // Ignore if there's no search bar (some special pages have no header)
2685 if ($("#search-container").length < 1) return;
2686
smain@google.com3b77ab52014-06-17 11:57:27 -07002687 var hash = escape(location.hash.substr(1));
2688 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002689 // Sanity check that there's an element with that ID on the page
2690 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002691 // If the position of the target element is near the top of the page (<20px, where we expect it
2692 // to be because we need to move it down 60px to become in view), then move it down 60px
2693 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2694 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002695 }
2696 }
2697}
2698
Scott Mainf5089842012-08-14 16:31:07 -07002699// when an event on the browser history occurs (back, forward, load) requery hash and do search
2700$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002701 // Ignore if there's no search bar (some special pages have no header)
2702 if ($("#search-container").length < 1) return;
2703
Dirk Doughertyc3921652014-05-13 16:55:26 -07002704 // If the hash isn't a search query or there's an error in the query,
2705 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002706 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2707 // If the results pane is open, close it.
2708 if (!$("#searchResults").is(":hidden")) {
2709 hideResults();
2710 }
Scott Mainb16376f2014-05-21 20:35:47 -07002711 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002712 return;
2713 }
2714
2715 // Otherwise, we have a search to do
2716 var query = decodeURI(getQuery(location.hash));
2717 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002718 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002719 $("#search_autocomplete").focus();
Dirk Dougherty29e93432015-05-05 18:17:13 -07002720 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002721
2722 updateResultTitle(query);
2723});
2724
2725function updateResultTitle(query) {
2726 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2727}
2728
2729// forcefully regain key-up event control (previously jacked by search api)
2730$("#search_autocomplete").keyup(function(event) {
2731 return search_changed(event, false, toRoot);
2732});
2733
2734// add event listeners to each tab so we can track the browser history
2735function addTabListeners() {
2736 var tabHeaders = $(".gsc-tabHeader");
2737 for (var i = 0; i < tabHeaders.length; i++) {
2738 $(tabHeaders[i]).attr("id",i).click(function() {
2739 /*
2740 // make a copy of the page numbers for the search left pane
2741 setTimeout(function() {
2742 // remove any residual page numbers
2743 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002744 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002745 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002746 // and because we're going to remove it (previous line),
2747 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002748 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2749 .clone().appendTo('#searchResults .gsc-tabsArea');
2750 }, 200);
2751 */
2752 });
2753 }
2754 setTimeout(function(){$(tabHeaders[0]).click()},200);
2755}
2756
Scott Mainde295272013-03-25 15:48:35 -07002757// add analytics tracking events to each result link
2758function addResultClickListeners() {
2759 $("#searchResults a.gs-title").each(function(index, link) {
2760 // When user clicks enter for Google search results, track it
2761 $(link).click(function() {
smain@google.comed677d72014-12-12 10:19:17 -08002762 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'),
2763 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07002764 });
2765 });
2766}
2767
Scott Mainf5089842012-08-14 16:31:07 -07002768
2769function getQuery(hash) {
2770 var queryParts = hash.split('=');
2771 return queryParts[1];
2772}
2773
2774/* returns the given string with all HTML brackets converted to entities
2775 TODO: move this to the site's JS library */
2776function escapeHTML(string) {
2777 return string.replace(/</g,"&lt;")
2778 .replace(/>/g,"&gt;");
2779}
2780
2781
2782
2783
2784
2785
2786
2787/* ######################################################## */
2788/* ################# JAVADOC REFERENCE ################### */
2789/* ######################################################## */
2790
Scott Main65511c02012-09-07 15:51:32 -07002791/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002792if (location.pathname.indexOf("/reference") == 0) {
2793 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2794 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2795 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002796 $(document).ready(function() {
2797 // init available apis based on user pref
2798 changeApiLevel();
2799 initSidenavHeightResize()
2800 });
2801 }
Scott Main65511c02012-09-07 15:51:32 -07002802}
Scott Mainf5089842012-08-14 16:31:07 -07002803
2804var API_LEVEL_COOKIE = "api_level";
2805var minLevel = 1;
2806var maxLevel = 1;
2807
2808/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002809
Scott Mainf5089842012-08-14 16:31:07 -07002810 function initSidenavHeightResize() {
2811 // Change the drag bar size to nicely fit the scrollbar positions
2812 var $dragBar = $(".ui-resizable-s");
2813 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002814
2815 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002816 containment: "#nav-panels",
2817 handles: "s",
2818 alsoResize: "#packages-nav",
2819 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2820 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2821 });
Scott Main3b90aff2013-08-01 18:09:35 -07002822
Scott Mainf5089842012-08-14 16:31:07 -07002823 }
Scott Main3b90aff2013-08-01 18:09:35 -07002824
Scott Mainf5089842012-08-14 16:31:07 -07002825function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002826 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002827 $('#devdoc-nav').css({
2828 'width' : $('#side-nav').css('width'),
2829 'margin' : $('#side-nav').css('margin')
2830 });
2831 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002832
Scott Mainf5089842012-08-14 16:31:07 -07002833 initSidenavHeightResize();
2834}
2835
2836function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002837 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002838 $('#devdoc-nav').css({
2839 'width' : $('#side-nav').css('width'),
2840 'margin' : $('#side-nav').css('margin')
2841 });
2842 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002843
Scott Mainf5089842012-08-14 16:31:07 -07002844 initSidenavHeightResize();
2845}
2846
2847function buildApiLevelSelector() {
2848 maxLevel = SINCE_DATA.length;
2849 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2850 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2851
2852 minLevel = parseInt($("#doc-api-level").attr("class"));
2853 // Handle provisional api levels; the provisional level will always be the highest possible level
2854 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2855 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2856 if (isNaN(minLevel) && minLevel.length) {
2857 minLevel = maxLevel;
2858 }
2859 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2860 for (var i = maxLevel-1; i >= 0; i--) {
2861 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2862 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2863 select.append(option);
2864 }
2865
2866 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2867 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2868 selectedLevelItem.setAttribute('selected',true);
2869}
2870
2871function changeApiLevel() {
2872 maxLevel = SINCE_DATA.length;
2873 var selectedLevel = maxLevel;
2874
2875 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2876 toggleVisisbleApis(selectedLevel, "body");
2877
smain@google.com6bdcb982014-11-14 11:53:07 -08002878 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002879
2880 if (selectedLevel < minLevel) {
2881 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002882 $("#naMessage").show().html("<div><p><strong>This " + thing
2883 + " requires API level " + minLevel + " or higher.</strong></p>"
2884 + "<p>This document is hidden because your selected API level for the documentation is "
2885 + selectedLevel + ". You can change the documentation API level with the selector "
2886 + "above the left navigation.</p>"
2887 + "<p>For more information about specifying the API level your app requires, "
2888 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2889 + ">Supporting Different Platform Versions</a>.</p>"
2890 + "<input type='button' value='OK, make this page visible' "
2891 + "title='Change the API level to " + minLevel + "' "
2892 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2893 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002894 } else {
2895 $("#naMessage").hide();
2896 }
2897}
2898
2899function toggleVisisbleApis(selectedLevel, context) {
2900 var apis = $(".api",context);
2901 apis.each(function(i) {
2902 var obj = $(this);
2903 var className = obj.attr("class");
2904 var apiLevelIndex = className.lastIndexOf("-")+1;
2905 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2906 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2907 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2908 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2909 return;
2910 }
2911 apiLevel = parseInt(apiLevel);
2912
2913 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2914 var selectedLevelNum = parseInt(selectedLevel)
2915 var apiLevelNum = parseInt(apiLevel);
2916 if (isNaN(apiLevelNum)) {
2917 apiLevelNum = maxLevel;
2918 }
2919
2920 // Grey things out that aren't available and give a tooltip title
2921 if (apiLevelNum > selectedLevelNum) {
2922 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002923 + apiLevel + "\" or higher. To reveal, change the target API level "
2924 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002925 }
Scott Mainf5089842012-08-14 16:31:07 -07002926 else obj.removeClass("absent").removeAttr("title");
2927 });
2928}
2929
2930
2931
2932
2933/* ################# SIDENAV TREE VIEW ################### */
2934
2935function new_node(me, mom, text, link, children_data, api_level)
2936{
2937 var node = new Object();
2938 node.children = Array();
2939 node.children_data = children_data;
2940 node.depth = mom.depth + 1;
2941
2942 node.li = document.createElement("li");
2943 mom.get_children_ul().appendChild(node.li);
2944
2945 node.label_div = document.createElement("div");
2946 node.label_div.className = "label";
2947 if (api_level != null) {
2948 $(node.label_div).addClass("api");
2949 $(node.label_div).addClass("api-level-"+api_level);
2950 }
2951 node.li.appendChild(node.label_div);
2952
2953 if (children_data != null) {
2954 node.expand_toggle = document.createElement("a");
2955 node.expand_toggle.href = "javascript:void(0)";
2956 node.expand_toggle.onclick = function() {
2957 if (node.expanded) {
2958 $(node.get_children_ul()).slideUp("fast");
2959 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2960 node.expanded = false;
2961 } else {
2962 expand_node(me, node);
2963 }
2964 };
2965 node.label_div.appendChild(node.expand_toggle);
2966
2967 node.plus_img = document.createElement("img");
2968 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2969 node.plus_img.className = "plus";
2970 node.plus_img.width = "8";
2971 node.plus_img.border = "0";
2972 node.expand_toggle.appendChild(node.plus_img);
2973
2974 node.expanded = false;
2975 }
2976
2977 var a = document.createElement("a");
2978 node.label_div.appendChild(a);
2979 node.label = document.createTextNode(text);
2980 a.appendChild(node.label);
2981 if (link) {
2982 a.href = me.toroot + link;
2983 } else {
2984 if (children_data != null) {
2985 a.className = "nolink";
2986 a.href = "javascript:void(0)";
2987 a.onclick = node.expand_toggle.onclick;
2988 // This next line shouldn't be necessary. I'll buy a beer for the first
2989 // person who figures out how to remove this line and have the link
2990 // toggle shut on the first try. --joeo@android.com
2991 node.expanded = false;
2992 }
2993 }
Scott Main3b90aff2013-08-01 18:09:35 -07002994
Scott Mainf5089842012-08-14 16:31:07 -07002995
2996 node.children_ul = null;
2997 node.get_children_ul = function() {
2998 if (!node.children_ul) {
2999 node.children_ul = document.createElement("ul");
3000 node.children_ul.className = "children_ul";
3001 node.children_ul.style.display = "none";
3002 node.li.appendChild(node.children_ul);
3003 }
3004 return node.children_ul;
3005 };
3006
3007 return node;
3008}
3009
Robert Lyd2dd6e52012-11-29 21:28:48 -08003010
3011
3012
Scott Mainf5089842012-08-14 16:31:07 -07003013function expand_node(me, node)
3014{
3015 if (node.children_data && !node.expanded) {
3016 if (node.children_visited) {
3017 $(node.get_children_ul()).slideDown("fast");
3018 } else {
3019 get_node(me, node);
3020 if ($(node.label_div).hasClass("absent")) {
3021 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07003022 }
Scott Mainf5089842012-08-14 16:31:07 -07003023 $(node.get_children_ul()).slideDown("fast");
3024 }
3025 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
3026 node.expanded = true;
3027
3028 // perform api level toggling because new nodes are new to the DOM
3029 var selectedLevel = $("#apiLevelSelector option:selected").val();
3030 toggleVisisbleApis(selectedLevel, "#side-nav");
3031 }
3032}
3033
3034function get_node(me, mom)
3035{
3036 mom.children_visited = true;
3037 for (var i in mom.children_data) {
3038 var node_data = mom.children_data[i];
3039 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
3040 node_data[2], node_data[3]);
3041 }
3042}
3043
3044function this_page_relative(toroot)
3045{
3046 var full = document.location.pathname;
3047 var file = "";
3048 if (toroot.substr(0, 1) == "/") {
3049 if (full.substr(0, toroot.length) == toroot) {
3050 return full.substr(toroot.length);
3051 } else {
3052 // the file isn't under toroot. Fail.
3053 return null;
3054 }
3055 } else {
3056 if (toroot != "./") {
3057 toroot = "./" + toroot;
3058 }
3059 do {
3060 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3061 var pos = full.lastIndexOf("/");
3062 file = full.substr(pos) + file;
3063 full = full.substr(0, pos);
3064 toroot = toroot.substr(0, toroot.length-3);
3065 }
3066 } while (toroot != "" && toroot != "/");
3067 return file.substr(1);
3068 }
3069}
3070
3071function find_page(url, data)
3072{
3073 var nodes = data;
3074 var result = null;
3075 for (var i in nodes) {
3076 var d = nodes[i];
3077 if (d[1] == url) {
3078 return new Array(i);
3079 }
3080 else if (d[2] != null) {
3081 result = find_page(url, d[2]);
3082 if (result != null) {
3083 return (new Array(i).concat(result));
3084 }
3085 }
3086 }
3087 return null;
3088}
3089
Scott Mainf5089842012-08-14 16:31:07 -07003090function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07003091 // load json file for navtree data
3092 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3093 // when the file is loaded, initialize the tree
3094 if(jqxhr.status === 200) {
3095 init_navtree("tree-list", toroot, NAVTREE_DATA);
3096 }
3097 });
Scott Main3b90aff2013-08-01 18:09:35 -07003098
Scott Mainf5089842012-08-14 16:31:07 -07003099 // perform api level toggling because because the whole tree is new to the DOM
3100 var selectedLevel = $("#apiLevelSelector option:selected").val();
3101 toggleVisisbleApis(selectedLevel, "#side-nav");
3102}
3103
3104function init_navtree(navtree_id, toroot, root_nodes)
3105{
3106 var me = new Object();
3107 me.toroot = toroot;
3108 me.node = new Object();
3109
3110 me.node.li = document.getElementById(navtree_id);
3111 me.node.children_data = root_nodes;
3112 me.node.children = new Array();
3113 me.node.children_ul = document.createElement("ul");
3114 me.node.get_children_ul = function() { return me.node.children_ul; };
3115 //me.node.children_ul.className = "children_ul";
3116 me.node.li.appendChild(me.node.children_ul);
3117 me.node.depth = 0;
3118
3119 get_node(me, me.node);
3120
3121 me.this_page = this_page_relative(toroot);
3122 me.breadcrumbs = find_page(me.this_page, root_nodes);
3123 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3124 var mom = me.node;
3125 for (var i in me.breadcrumbs) {
3126 var j = me.breadcrumbs[i];
3127 mom = mom.children[j];
3128 expand_node(me, mom);
3129 }
3130 mom.label_div.className = mom.label_div.className + " selected";
3131 addLoadEvent(function() {
3132 scrollIntoView("nav-tree");
3133 });
3134 }
3135}
3136
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003137
3138
3139
3140
3141
3142
3143
Robert Lyd2dd6e52012-11-29 21:28:48 -08003144/* TODO: eliminate redundancy with non-google functions */
3145function init_google_navtree(navtree_id, toroot, root_nodes)
3146{
3147 var me = new Object();
3148 me.toroot = toroot;
3149 me.node = new Object();
3150
3151 me.node.li = document.getElementById(navtree_id);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003152 if (!me.node.li) {
3153 return;
3154 }
3155
Robert Lyd2dd6e52012-11-29 21:28:48 -08003156 me.node.children_data = root_nodes;
3157 me.node.children = new Array();
3158 me.node.children_ul = document.createElement("ul");
3159 me.node.get_children_ul = function() { return me.node.children_ul; };
3160 //me.node.children_ul.className = "children_ul";
3161 me.node.li.appendChild(me.node.children_ul);
3162 me.node.depth = 0;
3163
3164 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003165}
3166
3167function new_google_node(me, mom, text, link, children_data, api_level)
3168{
3169 var node = new Object();
3170 var child;
3171 node.children = Array();
3172 node.children_data = children_data;
3173 node.depth = mom.depth + 1;
3174 node.get_children_ul = function() {
3175 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003176 node.children_ul = document.createElement("ul");
3177 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003178 node.li.appendChild(node.children_ul);
3179 }
3180 return node.children_ul;
3181 };
3182 node.li = document.createElement("li");
3183
3184 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003185
3186
Robert Lyd2dd6e52012-11-29 21:28:48 -08003187 if(link) {
3188 child = document.createElement("a");
3189
3190 }
3191 else {
3192 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003193 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003194
3195 }
3196 if (children_data != null) {
3197 node.li.className="nav-section";
3198 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003199 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003200 node.li.appendChild(node.label_div);
3201 get_google_node(me, node);
3202 node.label_div.appendChild(child);
3203 }
3204 else {
3205 node.li.appendChild(child);
3206 }
3207 if(link) {
3208 child.href = me.toroot + link;
3209 }
3210 node.label = document.createTextNode(text);
3211 child.appendChild(node.label);
3212
3213 node.children_ul = null;
3214
3215 return node;
3216}
3217
3218function get_google_node(me, mom)
3219{
3220 mom.children_visited = true;
3221 var linkText;
3222 for (var i in mom.children_data) {
3223 var node_data = mom.children_data[i];
3224 linkText = node_data[0];
3225
3226 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3227 linkText = linkText.substr(19, linkText.length);
3228 }
3229 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3230 node_data[2], node_data[3]);
3231 }
3232}
Scott Mainad08f072013-08-20 16:49:57 -07003233
3234
3235
3236
3237
3238
3239/****** NEW version of script to build google and sample navs dynamically ******/
3240// TODO: update Google reference docs to tolerate this new implementation
3241
Scott Maine624b3f2013-09-12 12:56:41 -07003242var NODE_NAME = 0;
3243var NODE_HREF = 1;
3244var NODE_GROUP = 2;
3245var NODE_TAGS = 3;
3246var NODE_CHILDREN = 4;
3247
Scott Mainad08f072013-08-20 16:49:57 -07003248function init_google_navtree2(navtree_id, data)
3249{
3250 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003251 for (var i in data) {
3252 var node_data = data[i];
3253 $containerUl.append(new_google_node2(node_data));
3254 }
3255
Scott Main70557ee2013-10-30 14:47:40 -07003256 // Make all third-generation list items 'sticky' to prevent them from collapsing
3257 $containerUl.find('li li li.nav-section').addClass('sticky');
3258
Scott Mainad08f072013-08-20 16:49:57 -07003259 initExpandableNavItems("#"+navtree_id);
3260}
3261
3262function new_google_node2(node_data)
3263{
Scott Maine624b3f2013-09-12 12:56:41 -07003264 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003265 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3266 linkText = linkText.substr(19, linkText.length);
3267 }
3268 var $li = $('<li>');
3269 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003270 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003271 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3272 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003273 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003274 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3275 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003276 }
3277 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003278 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003279 $li.addClass("nav-section");
3280 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003281 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003282
Scott Maine624b3f2013-09-12 12:56:41 -07003283 for (var i in node_data[NODE_CHILDREN]) {
3284 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003285 $childUl.append(new_google_node2(child_node_data));
3286 }
3287 $li.append($childUl);
3288 }
3289 $li.prepend($a);
3290
3291 return $li;
3292}
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
Robert Lyd2dd6e52012-11-29 21:28:48 -08003304function showGoogleRefTree() {
3305 init_default_google_navtree(toRoot);
3306 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003307}
3308
3309function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003310 // load json file for navtree data
3311 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3312 // when the file is loaded, initialize the tree
3313 if(jqxhr.status === 200) {
3314 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3315 highlightSidenav();
3316 resizeNav();
3317 }
3318 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003319}
3320
3321function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003322 // load json file for navtree data
3323 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3324 // when the file is loaded, initialize the tree
3325 if(jqxhr.status === 200) {
3326 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3327 highlightSidenav();
3328 resizeNav();
3329 }
3330 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003331}
3332
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003333function showSamplesRefTree() {
3334 init_default_samples_navtree(toRoot);
3335}
3336
3337function init_default_samples_navtree(toroot) {
3338 // load json file for navtree data
3339 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3340 // when the file is loaded, initialize the tree
3341 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003342 // hack to remove the "about the samples" link then put it back in
3343 // after we nuke the list to remove the dummy static list of samples
3344 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3345 $("#nav.samples-nav").empty();
3346 $("#nav.samples-nav").append($firstLi);
3347
Scott Mainad08f072013-08-20 16:49:57 -07003348 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003349 highlightSidenav();
3350 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003351 if ($("#jd-content #samples").length) {
3352 showSamples();
3353 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003354 }
3355 });
3356}
3357
Scott Mainf5089842012-08-14 16:31:07 -07003358/* TOGGLE INHERITED MEMBERS */
3359
3360/* Toggle an inherited class (arrow toggle)
3361 * @param linkObj The link that was clicked.
3362 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3363 * 'null' to simply toggle.
3364 */
3365function toggleInherited(linkObj, expand) {
3366 var base = linkObj.getAttribute("id");
3367 var list = document.getElementById(base + "-list");
3368 var summary = document.getElementById(base + "-summary");
3369 var trigger = document.getElementById(base + "-trigger");
3370 var a = $(linkObj);
3371 if ( (expand == null && a.hasClass("closed")) || expand ) {
3372 list.style.display = "none";
3373 summary.style.display = "block";
3374 trigger.src = toRoot + "assets/images/triangle-opened.png";
3375 a.removeClass("closed");
3376 a.addClass("opened");
3377 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3378 list.style.display = "block";
3379 summary.style.display = "none";
3380 trigger.src = toRoot + "assets/images/triangle-closed.png";
3381 a.removeClass("opened");
3382 a.addClass("closed");
3383 }
3384 return false;
3385}
3386
3387/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3388 * @param linkObj The link that was clicked.
3389 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3390 * 'null' to simply toggle.
3391 */
3392function toggleAllInherited(linkObj, expand) {
3393 var a = $(linkObj);
3394 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3395 var expandos = $(".jd-expando-trigger", table);
3396 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3397 expandos.each(function(i) {
3398 toggleInherited(this, true);
3399 });
3400 a.text("[Collapse]");
3401 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3402 expandos.each(function(i) {
3403 toggleInherited(this, false);
3404 });
3405 a.text("[Expand]");
3406 }
3407 return false;
3408}
3409
3410/* Toggle all inherited members in the class (link in the class title)
3411 */
3412function toggleAllClassInherited() {
3413 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3414 var toggles = $(".toggle-all", $("#body-content"));
3415 if (a.text() == "[Expand All]") {
3416 toggles.each(function(i) {
3417 toggleAllInherited(this, true);
3418 });
3419 a.text("[Collapse All]");
3420 } else {
3421 toggles.each(function(i) {
3422 toggleAllInherited(this, false);
3423 });
3424 a.text("[Expand All]");
3425 }
3426 return false;
3427}
3428
3429/* Expand all inherited members in the class. Used when initiating page search */
3430function ensureAllInheritedExpanded() {
3431 var toggles = $(".toggle-all", $("#body-content"));
3432 toggles.each(function(i) {
3433 toggleAllInherited(this, true);
3434 });
3435 $("#toggleAllClassInherited").text("[Collapse All]");
3436}
3437
3438
3439/* HANDLE KEY EVENTS
3440 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3441 */
3442var agent = navigator['userAgent'].toLowerCase();
3443var mac = agent.indexOf("macintosh") != -1;
3444
3445$(document).keydown( function(e) {
3446var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3447 if (control && e.which == 70) { // 70 is "F"
3448 ensureAllInheritedExpanded();
3449 }
3450});
Scott Main498d7102013-08-21 15:47:38 -07003451
3452
3453
3454
3455
3456
3457/* On-demand functions */
3458
3459/** Move sample code line numbers out of PRE block and into non-copyable column */
3460function initCodeLineNumbers() {
3461 var numbers = $("#codesample-block a.number");
3462 if (numbers.length) {
3463 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3464 }
3465
3466 $(document).ready(function() {
3467 // select entire line when clicked
3468 $("span.code-line").click(function() {
3469 if (!shifted) {
3470 selectText(this);
3471 }
3472 });
3473 // invoke line link on double click
3474 $(".code-line").dblclick(function() {
3475 document.location.hash = $(this).attr('id');
3476 });
3477 // highlight the line when hovering on the number
3478 $("#codesample-line-numbers a.number").mouseover(function() {
3479 var id = $(this).attr('href');
3480 $(id).css('background','#e7e7e7');
3481 });
3482 $("#codesample-line-numbers a.number").mouseout(function() {
3483 var id = $(this).attr('href');
3484 $(id).css('background','none');
3485 });
3486 });
3487}
3488
3489// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3490var shifted = false;
3491$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3492
3493// courtesy of jasonedelman.com
3494function selectText(element) {
3495 var doc = document
3496 , range, selection
3497 ;
3498 if (doc.body.createTextRange) { //ms
3499 range = doc.body.createTextRange();
3500 range.moveToElementText(element);
3501 range.select();
3502 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003503 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003504 range = doc.createRange();
3505 range.selectNodeContents(element);
3506 selection.removeAllRanges();
3507 selection.addRange(range);
3508 }
Scott Main285f0772013-08-22 23:22:09 +00003509}
Scott Main03aca9a2013-10-31 07:20:55 -07003510
3511
3512
3513
3514/** Display links and other information about samples that match the
3515 group specified by the URL */
3516function showSamples() {
3517 var group = $("#samples").attr('class');
3518 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3519
3520 var $ul = $("<ul>");
3521 $selectedLi = $("#nav li.selected");
3522
3523 $selectedLi.children("ul").children("li").each(function() {
3524 var $li = $("<li>").append($(this).find("a").first().clone());
3525 $ul.append($li);
3526 });
3527
3528 $("#samples").append($ul);
3529
3530}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003531
3532
3533
3534/* ########################################################## */
3535/* ################### RESOURCE CARDS ##################### */
3536/* ########################################################## */
3537
3538/** Handle resource queries, collections, and grids (sections). Requires
3539 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3540
3541(function() {
3542 // Prevent the same resource from being loaded more than once per page.
3543 var addedPageResources = {};
3544
3545 $(document).ready(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003546 // Need to initialize hero carousel before other sections for dedupe
3547 // to work correctly.
3548 $('[data-carousel-query]').dacCarouselQuery();
3549
Dirk Doughertyc3921652014-05-13 16:55:26 -07003550 $('.resource-widget').each(function() {
3551 initResourceWidget(this);
3552 });
3553
3554 /* Pass the line height to ellipsisfade() to adjust the height of the
3555 text container to show the max number of lines possible, without
3556 showing lines that are cut off. This works with the css ellipsis
3557 classes to fade last text line and apply an ellipsis char. */
3558
Dirk Dougherty29e93432015-05-05 18:17:13 -07003559 //card text currently uses 20px line height.
3560 var lineHeight = 20;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003561 $('.card-info .text').ellipsisfade(lineHeight);
3562 });
3563
3564 /*
3565 Three types of resource layouts:
3566 Flow - Uses a fixed row-height flow using float left style.
3567 Carousel - Single card slideshow all same dimension absolute.
3568 Stack - Uses fixed columns and flexible element height.
3569 */
3570 function initResourceWidget(widget) {
3571 var $widget = $(widget);
3572 var isFlow = $widget.hasClass('resource-flow-layout'),
3573 isCarousel = $widget.hasClass('resource-carousel-layout'),
3574 isStack = $widget.hasClass('resource-stack-layout');
3575
Dirk Dougherty29e93432015-05-05 18:17:13 -07003576 // remove illegal col-x class which is not relevant anymore thanks to responsive styles.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003577 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
Dirk Dougherty29e93432015-05-05 18:17:13 -07003578 if (m && !$widget.is('.cols > *')) {
3579 $widget.removeClass('col-' + m[1]);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003580 }
3581
3582 var opts = {
3583 cardSizes: ($widget.data('cardsizes') || '').split(','),
3584 maxResults: parseInt($widget.data('maxresults') || '100', 10),
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003585 initialResults: $widget.data('initialResults'),
Dirk Doughertyc3921652014-05-13 16:55:26 -07003586 itemsPerPage: $widget.data('itemsperpage'),
3587 sortOrder: $widget.data('sortorder'),
3588 query: $widget.data('query'),
3589 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003590 /* Added by LFL 6/6/14 */
3591 resourceStyle: $widget.data('resourcestyle') || 'card',
3592 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003593 };
3594
3595 // run the search for the set of resources to show
3596
3597 var resources = buildResourceList(opts);
3598
3599 if (isFlow) {
3600 drawResourcesFlowWidget($widget, opts, resources);
3601 } else if (isCarousel) {
3602 drawResourcesCarouselWidget($widget, opts, resources);
3603 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003604 /* Looks like this got removed and is not used, so repurposing for the
3605 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003606 Modified by LFL 6/6/14
3607 */
3608 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003609 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003610 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003611 }
3612 }
3613
3614 /* Initializes a Resource Carousel Widget */
3615 function drawResourcesCarouselWidget($widget, opts, resources) {
3616 $widget.empty();
Dirk Dougherty29e93432015-05-05 18:17:13 -07003617 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003618
3619 $widget.addClass('resource-card slideshow-container')
3620 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3621 .append($('<a>').addClass('slideshow-next').text('Next'));
3622
3623 var css = { 'width': $widget.width() + 'px',
3624 'height': $widget.height() + 'px' };
3625
3626 var $ul = $('<ul>');
3627
3628 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003629 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003630 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003631 .decorateResourceCard(resources[i],plusone);
3632
3633 $('<li>').css(css)
3634 .append($card)
3635 .appendTo($ul);
3636 }
3637
3638 $('<div>').addClass('frame')
3639 .append($ul)
3640 .appendTo($widget);
3641
3642 $widget.dacSlideshow({
3643 auto: true,
3644 btnPrev: '.slideshow-prev',
3645 btnNext: '.slideshow-next'
3646 });
3647 };
3648
Robert Lye7eeb402014-06-03 19:35:24 -07003649 /* Initializes a Resource Card Stack Widget (column-based layout)
3650 Modified by LFL 6/6/14
3651 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003652 function drawResourcesStackWidget($widget, opts, resources, sections) {
3653 // Don't empty widget, grab all items inside since they will be the first
3654 // items stacked, followed by the resource query
Dirk Dougherty29e93432015-05-05 18:17:13 -07003655 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003656 var cards = $widget.find('.resource-card').detach().toArray();
3657 var numStacks = opts.numStacks || 1;
3658 var $stacks = [];
3659 var urlString;
3660
3661 for (var i = 0; i < numStacks; ++i) {
3662 $stacks[i] = $('<div>').addClass('resource-card-stack')
3663 .appendTo($widget);
3664 }
3665
3666 var sectionResources = [];
3667
3668 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003669 if (sections) {
3670 for (var i = 0; i < sections.length; ++i) {
3671 if (!sections[i].sections || !sections[i].sections.length) {
3672 // Render it as a resource card
3673 sectionResources.push(
3674 $('<a>')
3675 .addClass('resource-card section-card')
3676 .attr('href', cleanUrl(sections[i].resource.url))
3677 .decorateResourceCard(sections[i].resource,plusone)[0]
3678 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003679
Robert Lye7eeb402014-06-03 19:35:24 -07003680 } else {
3681 cards.push(
3682 $('<div>')
3683 .addClass('resource-card section-card-menu')
3684 .decorateResourceSection(sections[i],plusone)[0]
3685 );
3686 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003687 }
3688 }
3689
3690 cards = cards.concat(sectionResources);
3691
3692 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003693 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003694
Robert Lye7eeb402014-06-03 19:35:24 -07003695 if (opts.resourceStyle.indexOf('related') > -1) {
3696 $card.addClass('related-card');
3697 }
smain@google.com95948b82014-06-16 19:24:25 -07003698
Dirk Doughertyc3921652014-05-13 16:55:26 -07003699 cards.push($card[0]);
3700 }
3701
Robert Lye7eeb402014-06-03 19:35:24 -07003702 if (opts.stackSort != 'false') {
3703 for (var i = 0; i < cards.length; ++i) {
3704 // Find the stack with the shortest height, but give preference to
3705 // left to right order.
3706 var minHeight = $stacks[0].height();
3707 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003708
Robert Lye7eeb402014-06-03 19:35:24 -07003709 for (var j = 1; j < numStacks; ++j) {
3710 var height = $stacks[j].height();
3711 if (height < minHeight - 45) {
3712 minHeight = height;
3713 minIndex = j;
3714 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003715 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003716
Robert Lye7eeb402014-06-03 19:35:24 -07003717 $stacks[minIndex].append($(cards[i]));
3718 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003719 }
3720
3721 };
smain@google.com95948b82014-06-16 19:24:25 -07003722
3723 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003724 Create a resource card using the given resource object and a list of html
3725 configured options. Returns a jquery object containing the element.
3726 */
smain@google.com95948b82014-06-16 19:24:25 -07003727 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003728 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003729
Robert Lye7eeb402014-06-03 19:35:24 -07003730 // The difference here is that generic cards are not entirely clickable
3731 // so its a div instead of an a tag, also the generic one is not given
3732 // the resource-card class so it appears with a transparent background
3733 // and can be styled in whatever way the css setup.
3734 if (opts.resourceStyle == 'generic') {
3735 $el = $('<div>')
3736 .addClass('resource')
3737 .attr('href', cleanUrl(resource.url))
3738 .decorateResource(resource, opts);
3739 } else {
3740 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003741
Robert Lye7eeb402014-06-03 19:35:24 -07003742 $el = $('<a>')
3743 .addClass(cls)
3744 .attr('href', cleanUrl(resource.url))
3745 .decorateResourceCard(resource, plusone);
3746 }
smain@google.com95948b82014-06-16 19:24:25 -07003747
Robert Lye7eeb402014-06-03 19:35:24 -07003748 return $el;
3749 }
Quddus Chong2cb2f682015-09-04 14:45:46 -07003750
Dirk Dougherty29e93432015-05-05 18:17:13 -07003751 function createResponsiveFlowColumn(cardSize) {
3752 var cardWidth = parseInt(cardSize.match(/(\d+)/)[1], 10);
3753 var column = $('<div>').addClass('col-' + (cardWidth / 3) + 'of6');
3754 if (cardWidth < 9) {
3755 column.addClass('col-tablet-1of2');
3756 } else if (cardWidth > 9 && cardWidth < 18) {
3757 column.addClass('col-tablet-1of1');
3758 }
3759 if (cardWidth < 18) {
3760 column.addClass('col-mobile-1of1')
3761 }
3762 return column;
3763 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003764
3765 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3766 function drawResourcesFlowWidget($widget, opts, resources) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003767 $widget.empty().addClass('cols');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003768 var cardSizes = opts.cardSizes || ['6x6'];
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003769 var initialResults = opts.initialResults || resources.length;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003770 var i = 0, j = 0;
Dirk Dougherty29e93432015-05-05 18:17:13 -07003771 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003772 var cardParent = $widget;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003773
3774 while (i < resources.length) {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003775
3776 if (i === initialResults && initialResults < resources.length) {
3777 // Toggle remaining cards
3778 cardParent = $('<div class="dac-toggle-content clearfix">').appendTo($widget);
3779 $widget.addClass('dac-toggle');
3780 $('<div class="col-1of1 dac-section-links dac-text-center">')
3781 .append(
3782 $('<div class="dac-section-link" data-toggle="section">')
3783 .append('<span class="dac-toggle-expand">More<i class="dac-sprite dac-auto-unfold-more"></i></span>')
3784 .append('<span class="dac-toggle-collapse">Less<i class="dac-sprite dac-auto-unfold-less"></i></span>')
3785 )
3786 .appendTo($widget)
3787 }
3788
Dirk Doughertyc3921652014-05-13 16:55:26 -07003789 var cardSize = cardSizes[j++ % cardSizes.length];
3790 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Quddus Chong2cb2f682015-09-04 14:45:46 -07003791
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003792 var column = createResponsiveFlowColumn(cardSize).appendTo(cardParent);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003793
3794 // A stack has a third dimension which is the number of stacked items
3795 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3796 var stackCount = 0;
3797 var $stackDiv = null;
3798
3799 if (isStack) {
3800 // Create a stack container which should have the dimensions defined
3801 // by the product of the items inside.
3802 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
Dirk Dougherty29e93432015-05-05 18:17:13 -07003803 + 'x' + isStack[2] * isStack[3]) .appendTo(column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003804 }
3805
3806 // Build each stack item or just a single item
3807 do {
3808 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003809
Robert Lye7eeb402014-06-03 19:35:24 -07003810 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003811
3812 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003813 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003814
Dirk Doughertyc3921652014-05-13 16:55:26 -07003815 if (isStack) {
3816 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3817 if (++stackCount == parseInt(isStack[3])) {
3818 $card.addClass('resource-card-row-stack-last');
3819 stackCount = 0;
3820 }
3821 } else {
3822 stackCount = 0;
3823 }
3824
Dirk Dougherty29e93432015-05-05 18:17:13 -07003825 $card.appendTo($stackDiv || column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003826
3827 } while (++i < resources.length && stackCount > 0);
3828 }
3829 }
3830
3831 /* Build a site map of resources using a section as a root. */
3832 function buildSectionList(opts) {
3833 if (opts.section && SECTION_BY_ID[opts.section]) {
3834 return SECTION_BY_ID[opts.section].sections || [];
3835 }
3836 return [];
3837 }
3838
3839 function buildResourceList(opts) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003840 return $.queryResources(opts);
3841 }
3842
3843 $.queryResources = function(opts) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003844 var maxResults = opts.maxResults || 100;
3845
3846 var query = opts.query || '';
3847 var expressions = parseResourceQuery(query);
3848 var addedResourceIndices = {};
3849 var results = [];
3850
3851 for (var i = 0; i < expressions.length; i++) {
3852 var clauses = expressions[i];
3853
3854 // build initial set of resources from first clause
3855 var firstClause = clauses[0];
3856 var resources = [];
3857 switch (firstClause.attr) {
3858 case 'type':
3859 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3860 break;
3861 case 'lang':
3862 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3863 break;
3864 case 'tag':
3865 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3866 break;
3867 case 'collection':
3868 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3869 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3870 break;
3871 case 'section':
3872 var urls = SITE_MAP[firstClause.value].sections || [];
3873 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3874 break;
3875 }
3876 // console.log(firstClause.attr + ':' + firstClause.value);
3877 resources = resources || [];
3878
3879 // use additional clauses to filter corpus
3880 if (clauses.length > 1) {
3881 var otherClauses = clauses.slice(1);
3882 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3883 }
3884
3885 // filter out resources already added
3886 if (i > 1) {
3887 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3888 }
3889
3890 // add to list of already added indices
3891 for (var j = 0; j < resources.length; j++) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003892 if (resources[j]) {
3893 addedResourceIndices[resources[j].index] = 1;
3894 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003895 }
3896
3897 // concat to final results list
3898 results = results.concat(resources);
3899 }
3900
3901 if (opts.sortOrder && results.length) {
3902 var attr = opts.sortOrder;
3903
3904 if (opts.sortOrder == 'random') {
3905 var i = results.length, j, temp;
3906 while (--i) {
3907 j = Math.floor(Math.random() * (i + 1));
3908 temp = results[i];
3909 results[i] = results[j];
3910 results[j] = temp;
3911 }
3912 } else {
3913 var desc = attr.charAt(0) == '-';
3914 if (desc) {
3915 attr = attr.substring(1);
3916 }
3917 results = results.sort(function(x,y) {
3918 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3919 });
3920 }
3921 }
3922
3923 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3924 results = results.slice(0, maxResults);
3925
3926 for (var j = 0; j < results.length; ++j) {
3927 addedPageResources[results[j].index] = 1;
3928 }
3929
3930 return results;
3931 }
3932
3933
3934 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3935 return function(resource) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003936 return resource && !addedResourceIndices[resource.index];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003937 };
3938 }
3939
3940
3941 function getResourceMatchesClausesFilter(clauses) {
3942 return function(resource) {
3943 return doesResourceMatchClauses(resource, clauses);
3944 };
3945 }
3946
3947
3948 function doesResourceMatchClauses(resource, clauses) {
3949 for (var i = 0; i < clauses.length; i++) {
3950 var map;
3951 switch (clauses[i].attr) {
3952 case 'type':
3953 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3954 break;
3955 case 'lang':
3956 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3957 break;
3958 case 'tag':
3959 map = IS_RESOURCE_TAGGED[clauses[i].value];
3960 break;
3961 }
3962
3963 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3964 return clauses[i].negative;
3965 }
3966 }
3967 return true;
3968 }
smain@google.com95948b82014-06-16 19:24:25 -07003969
Robert Lye7eeb402014-06-03 19:35:24 -07003970 function cleanUrl(url)
3971 {
3972 if (url && url.indexOf('//') === -1) {
3973 url = toRoot + url;
3974 }
smain@google.com95948b82014-06-16 19:24:25 -07003975
Robert Lye7eeb402014-06-03 19:35:24 -07003976 return url;
3977 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003978
3979
3980 function parseResourceQuery(query) {
3981 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3982 var expressions = [];
3983 var expressionStrs = query.split(',') || [];
3984 for (var i = 0; i < expressionStrs.length; i++) {
3985 var expr = expressionStrs[i] || '';
3986
3987 // Break expression into clauses (clause e.g. 'tag:foo')
3988 var clauses = [];
3989 var clauseStrs = expr.split(/(?=[\+\-])/);
3990 for (var j = 0; j < clauseStrs.length; j++) {
3991 var clauseStr = clauseStrs[j] || '';
3992
3993 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3994 var parts = clauseStr.split(':');
3995 var clause = {};
3996
3997 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3998 if (clause.attr) {
3999 if (clause.attr.charAt(0) == '+') {
4000 clause.attr = clause.attr.substring(1);
4001 } else if (clause.attr.charAt(0) == '-') {
4002 clause.negative = true;
4003 clause.attr = clause.attr.substring(1);
4004 }
4005 }
4006
4007 if (parts.length > 1) {
4008 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
4009 }
4010
4011 clauses.push(clause);
4012 }
4013
4014 if (!clauses.length) {
4015 continue;
4016 }
4017
4018 expressions.push(clauses);
4019 }
4020
4021 return expressions;
4022 }
4023})();
4024
4025(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07004026
smain@google.com95948b82014-06-16 19:24:25 -07004027 /*
Robert Lye7eeb402014-06-03 19:35:24 -07004028 Utility method for creating dom for the description area of a card.
4029 Used in decorateResourceCard and decorateResource.
4030 */
4031 function buildResourceCardDescription(resource, plusone) {
4032 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07004033
Robert Lye7eeb402014-06-03 19:35:24 -07004034 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07004035
Robert Lye7eeb402014-06-03 19:35:24 -07004036 if (resource.cta) {
4037 $description.append($('<a>').addClass('cta').html(resource.cta));
4038 }
smain@google.com95948b82014-06-16 19:24:25 -07004039
Robert Lye7eeb402014-06-03 19:35:24 -07004040 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07004041 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07004042 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004043
Robert Lye7eeb402014-06-03 19:35:24 -07004044 $description.append($('<div>').addClass('util')
4045 .append($('<div>').addClass('g-plusone')
4046 .attr('data-size', 'small')
4047 .attr('data-align', 'right')
4048 .attr('data-href', plusurl)));
4049 }
smain@google.com95948b82014-06-16 19:24:25 -07004050
Robert Lye7eeb402014-06-03 19:35:24 -07004051 return $description;
4052 }
smain@google.com95948b82014-06-16 19:24:25 -07004053
4054
Dirk Doughertyc3921652014-05-13 16:55:26 -07004055 /* Simple jquery function to create dom for a standard resource card */
4056 $.fn.decorateResourceCard = function(resource,plusone) {
4057 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07004058 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004059 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07004060
Robert Lye7eeb402014-06-03 19:35:24 -07004061 if (imgUrl.indexOf('//') === -1) {
4062 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07004063 }
Robert Lye7eeb402014-06-03 19:35:24 -07004064
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004065 if (resource.type === 'youtube') {
4066 $('<div>').addClass('play-button')
4067 .append($('<i class="dac-sprite dac-play-white">'))
4068 .appendTo(this);
4069 }
4070
Robert Lye7eeb402014-06-03 19:35:24 -07004071 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07004072 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07004073 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07004074 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07004075
Robert Lye7eeb402014-06-03 19:35:24 -07004076 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
4077 .append($('<div>').addClass('section').text(section))
4078 .append($('<div>').addClass('title').html(resource.title))
4079 .append(buildResourceCardDescription(resource, plusone))
4080 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07004081
4082 return this;
4083 };
4084
4085 /* Simple jquery function to create dom for a resource section card (menu) */
4086 $.fn.decorateResourceSection = function(section,plusone) {
4087 var resource = section.resource;
4088 //keep url clean for matching and offline mode handling
4089 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
4090 var $base = $('<a>')
4091 .addClass('card-bg')
4092 .attr('href', resource.url)
4093 .append($('<div>').addClass('card-section-icon')
4094 .append($('<div>').addClass('icon'))
4095 .append($('<div>').addClass('section').html(resource.title)))
4096 .appendTo(this);
4097
4098 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
4099
4100 if (section.sections && section.sections.length) {
4101 // Recurse the section sub-tree to find a resource image.
4102 var stack = [section];
4103
4104 while (stack.length) {
4105 if (stack[0].resource.image) {
4106 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4107 break;
4108 }
4109
4110 if (stack[0].sections) {
4111 stack = stack.concat(stack[0].sections);
4112 }
4113
4114 stack.shift();
4115 }
4116
4117 var $ul = $('<ul>')
4118 .appendTo($cardInfo);
4119
4120 var max = section.sections.length > 3 ? 3 : section.sections.length;
4121
4122 for (var i = 0; i < max; ++i) {
4123
4124 var subResource = section.sections[i];
4125 if (!plusone) {
4126 $('<li>')
4127 .append($('<a>').attr('href', subResource.url)
4128 .append($('<div>').addClass('title').html(subResource.title))
4129 .append($('<div>').addClass('description ellipsis')
4130 .append($('<div>').addClass('text').html(subResource.summary))
4131 .append($('<div>').addClass('util'))))
4132 .appendTo($ul);
4133 } else {
4134 $('<li>')
4135 .append($('<a>').attr('href', subResource.url)
4136 .append($('<div>').addClass('title').html(subResource.title))
4137 .append($('<div>').addClass('description ellipsis')
4138 .append($('<div>').addClass('text').html(subResource.summary))
4139 .append($('<div>').addClass('util')
4140 .append($('<div>').addClass('g-plusone')
4141 .attr('data-size', 'small')
4142 .attr('data-align', 'right')
4143 .attr('data-href', resource.url)))))
4144 .appendTo($ul);
4145 }
4146 }
4147
4148 // Add a more row
4149 if (max < section.sections.length) {
4150 $('<li>')
4151 .append($('<a>').attr('href', resource.url)
4152 .append($('<div>')
4153 .addClass('title')
4154 .text('More')))
4155 .appendTo($ul);
4156 }
4157 } else {
4158 // No sub-resources, just render description?
4159 }
4160
4161 return this;
4162 };
smain@google.com95948b82014-06-16 19:24:25 -07004163
4164
4165
4166
Robert Lye7eeb402014-06-03 19:35:24 -07004167 /* Render other types of resource styles that are not cards. */
4168 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07004169 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004170 'assets/images/resource-card-default-android.jpg';
4171 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004172
Robert Lye7eeb402014-06-03 19:35:24 -07004173 if (imgUrl.indexOf('//') === -1) {
4174 imgUrl = toRoot + imgUrl;
4175 }
smain@google.com95948b82014-06-16 19:24:25 -07004176
Robert Lye7eeb402014-06-03 19:35:24 -07004177 if (linkUrl && linkUrl.indexOf('//') === -1) {
4178 linkUrl = toRoot + linkUrl;
4179 }
4180
4181 $(this).append(
4182 $('<div>').addClass('image')
4183 .css('background-image', 'url(' + imgUrl + ')'),
4184 $('<div>').addClass('info').append(
4185 $('<h4>').addClass('title').html(resource.title),
4186 $('<p>').addClass('summary').html(resource.summary),
4187 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4188 )
4189 );
4190
4191 return this;
4192 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004193})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004194
4195
Dirk Doughertyc3921652014-05-13 16:55:26 -07004196/* Calculate the vertical area remaining */
4197(function($) {
4198 $.fn.ellipsisfade= function(lineHeight) {
4199 this.each(function() {
4200 // get element text
4201 var $this = $(this);
4202 var remainingHeight = $this.parent().parent().height();
4203 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004204 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004205 if ($(this).is(":visible")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004206 var h = $(this).outerHeight(true);
smain@google.comcda1a9a2014-06-19 17:07:46 -07004207 remainingHeight = remainingHeight - h;
4208 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004209 });
4210
4211 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4212 $this.parent().css({'height': adjustedRemainingHeight});
4213 $this.css({'height': "auto"});
4214 });
4215
4216 return this;
4217 };
4218}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004219
4220/*
4221 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004222
Robert Lye7eeb402014-06-03 19:35:24 -07004223 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004224 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004225 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004226
Robert Lye7eeb402014-06-03 19:35:24 -07004227 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004228
Robert Lye7eeb402014-06-03 19:35:24 -07004229 <div class="fullscreen-carousel">
4230 <div class="fullscreen-carousel-content">
4231 <!-- content here -->
4232 </div>
4233 <div class="fullscreen-carousel-content">
4234 <!-- content here -->
4235 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004236
Robert Lye7eeb402014-06-03 19:35:24 -07004237 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004238
Robert Lye7eeb402014-06-03 19:35:24 -07004239 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004240
Robert Lye7eeb402014-06-03 19:35:24 -07004241 Control over how the carousel takes over the screen can mostly be defined in
4242 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004243 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004244 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004245 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004246 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004247
Robert Lye7eeb402014-06-03 19:35:24 -07004248 There is limited functionality for having multiple sections since that request
4249 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4250 scroll between multiple content areas.
4251*/
4252
4253(function() {
4254 $(document).ready(function() {
4255 $('.fullscreen-carousel').each(function() {
4256 initWidget(this);
4257 });
4258 });
4259
4260 function initWidget(widget) {
4261 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004262
Robert Lye7eeb402014-06-03 19:35:24 -07004263 var topOffset = $widget.offset().top;
4264 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4265 var maxHeight = 0;
4266 var minHeight = 0;
4267 var $content = $widget.find('.fullscreen-carousel-content');
4268 var $nextArrow = $widget.find('.next-arrow');
4269 var $prevArrow = $widget.find('.prev-arrow');
4270 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004271
Robert Lye7eeb402014-06-03 19:35:24 -07004272 if ($content.length <= 1) {
4273 $nextArrow.hide();
4274 $prevArrow.hide();
4275 } else {
4276 $nextArrow.click(function() {
4277 var index = ($content.index($curSection) + 1);
4278 $curSection.hide();
4279 $curSection = $($content[index >= $content.length ? 0 : index]);
4280 $curSection.show();
4281 });
smain@google.com95948b82014-06-16 19:24:25 -07004282
Robert Lye7eeb402014-06-03 19:35:24 -07004283 $prevArrow.click(function() {
4284 var index = ($content.index($curSection) - 1);
4285 $curSection.hide();
4286 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4287 $curSection.show();
4288 });
4289 }
4290
4291 // Just hide all content sections except first.
4292 $content.each(function(index) {
4293 if ($(this).height() > minHeight) minHeight = $(this).height();
4294 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4295 });
4296
4297 // Register for changes to window size, and trigger.
4298 $(window).resize(resizeWidget);
4299 resizeWidget();
4300
4301 function resizeWidget() {
4302 var height = $(window).height() - topOffset - padBottom;
4303 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004304 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004305 (maxHeight && height > maxHeight ? maxHeight : height));
4306 }
smain@google.com95948b82014-06-16 19:24:25 -07004307 }
Robert Lye7eeb402014-06-03 19:35:24 -07004308})();
4309
4310
4311
4312
4313
4314/*
4315 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004316
Robert Lye7eeb402014-06-03 19:35:24 -07004317 The following allows tab widgets to be installed via the html below. Each
4318 tab content section should have a data-tab attribute matching one of the
4319 nav items'. Also each tab content section should have a width matching the
4320 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004321
Robert Lye7eeb402014-06-03 19:35:24 -07004322 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004323
Robert Lye7eeb402014-06-03 19:35:24 -07004324 <div class="tab-carousel">
4325 <ul class="tab-nav">
4326 <li><a href="#" data-tab="handsets">Handsets</a>
4327 <li><a href="#" data-tab="wearable">Wearable</a>
4328 <li><a href="#" data-tab="tv">TV</a>
4329 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004330
Robert Lye7eeb402014-06-03 19:35:24 -07004331 <div class="tab-carousel-content">
4332 <div data-tab="handsets">
4333 <!--Full width content here-->
4334 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004335
Robert Lye7eeb402014-06-03 19:35:24 -07004336 <div data-tab="wearable">
4337 <!--Full width content here-->
4338 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004339
Robert Lye7eeb402014-06-03 19:35:24 -07004340 <div data-tab="tv">
4341 <!--Full width content here-->
4342 </div>
4343 </div>
4344 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004345
Robert Lye7eeb402014-06-03 19:35:24 -07004346*/
4347(function() {
4348 $(document).ready(function() {
4349 $('.tab-carousel').each(function() {
4350 initWidget(this);
4351 });
4352 });
4353
4354 function initWidget(widget) {
4355 var $widget = $(widget);
4356 var $nav = $widget.find('.tab-nav');
4357 var $anchors = $nav.find('[data-tab]');
4358 var $li = $nav.find('li');
4359 var $contentContainer = $widget.find('.tab-carousel-content');
4360 var $tabs = $contentContainer.find('[data-tab]');
4361 var $curTab = $($tabs[0]); // Current tab is first tab.
4362 var width = $widget.width();
4363
4364 // Setup nav interactivity.
4365 $anchors.click(function(evt) {
4366 evt.preventDefault();
4367 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004368 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004369 });
smain@google.com95948b82014-06-16 19:24:25 -07004370
Robert Lye7eeb402014-06-03 19:35:24 -07004371 // Add highlight for navigation on first item.
4372 var $highlight = $('<div>').addClass('highlight')
4373 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4374 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004375
Robert Lye7eeb402014-06-03 19:35:24 -07004376 // Store height since we will change contents to absolute.
4377 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004378
Robert Lye7eeb402014-06-03 19:35:24 -07004379 // Absolutely position tabs so they're ready for transition.
4380 $tabs.each(function(index) {
4381 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4382 });
smain@google.com95948b82014-06-16 19:24:25 -07004383
Robert Lye7eeb402014-06-03 19:35:24 -07004384 function transitionWidget($toTab) {
4385 if (!$curTab.is($toTab)) {
4386 var curIndex = $tabs.index($curTab[0]);
4387 var toIndex = $tabs.index($toTab[0]);
4388 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004389
Robert Lye7eeb402014-06-03 19:35:24 -07004390 // Animate content sections.
4391 $toTab.css({left:(width * dir) + 'px'});
4392 $curTab.animate({left:(width * -dir) + 'px'});
4393 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004394
Robert Lye7eeb402014-06-03 19:35:24 -07004395 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004396 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004397 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004398
Robert Lye7eeb402014-06-03 19:35:24 -07004399 // Store new current section.
4400 $curTab = $toTab;
4401 }
4402 }
smain@google.com95948b82014-06-16 19:24:25 -07004403 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08004404})();
Dirk Dougherty29e93432015-05-05 18:17:13 -07004405
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004406/**
4407 * Auto TOC
4408 *
4409 * Upgrades h2s on the page to have a rule and be toggle-able on mobile.
4410 */
4411(function($) {
4412 var upgraded = false;
4413 var h2Titles;
4414
4415 function initWidget() {
4416 // add HRs below all H2s (except for a few other h2 variants)
4417 // Consider doing this with css instead.
4418 h2Titles = $('h2').not('#qv h2, #tb h2, .sidebox h2, #devdoc-nav h2, h2.norule');
4419 h2Titles.css({marginBottom:0}).after('<hr/>');
4420
4421 // Exit early if on older browser.
4422 if (!window.matchMedia) {
4423 return;
4424 }
4425
4426 // Only run logic in mobile layout.
4427 var query = window.matchMedia('(max-width: 719px)');
4428 if (query.matches) {
4429 makeTogglable();
4430 } else {
4431 query.addListener(makeTogglable);
4432 }
4433 }
4434
4435 function makeTogglable() {
4436 // Only run this logic once.
4437 if (upgraded) { return; }
4438 upgraded = true;
4439
4440 // Only make content h2s togglable.
4441 var contentTitles = h2Titles.filter('#jd-content *');
4442
4443 // If there are more than 1
4444 if (contentTitles.size() < 2) {
4445 return;
4446 }
4447
4448 contentTitles.each(function() {
4449 // Find all the relevant nodes.
4450 var $title = $(this);
4451 var $hr = $title.next();
4452 var $contents = $hr.nextUntil('h2, .next-docs');
4453 var $section = $($title)
4454 .add($hr)
4455 .add($title.prev('a[name]'))
4456 .add($contents);
4457 var $anchor = $section.first().prev();
4458 var anchorMethod = 'after';
4459 if ($anchor.length === 0) {
4460 $anchor = $title.parent();
4461 anchorMethod = 'prepend';
4462 }
4463
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004464 // Some h2s are in their own container making it pretty hard to find the end, so skip.
4465 if ($contents.length === 0) {
4466 return;
4467 }
4468
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004469 // Remove from DOM before messing with it. DOM is slow!
4470 $section.detach();
4471
4472 // Add mobile-only expand arrows.
4473 $title.prepend('<span class="dac-visible-mobile-inline-block">' +
4474 '<i class="dac-toggle-expand dac-sprite dac-expand-more-black"></i>' +
4475 '<i class="dac-toggle-collapse dac-sprite dac-expand-less-black"></i>' +
4476 '</span>')
4477 .attr('data-toggle', 'section');
4478
4479 // Wrap in magic markup.
4480 $section = $section.wrapAll('<div class="dac-toggle dac-mobile">').parent();
4481 $contents.wrapAll('<div class="dac-toggle-content"><div>'); // extra div used for max-height calculation.
4482
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004483 // Pre-expand section if requested.
4484 if ($title.hasClass('is-expanded')) {
4485 $section.addClass('is-expanded');
4486 }
4487
4488 // Pre-expand section if targetted by hash.
4489 if (location.hash && $section.find(location.hash).length) {
4490 $section.addClass('is-expanded');
4491 }
4492
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004493 // Add it back to the dom.
4494 $anchor[anchorMethod].call($anchor, $section);
4495 });
4496 }
4497
4498 $(function() {
4499 initWidget();
4500 });
4501})(jQuery);
4502
Dirk Dougherty29e93432015-05-05 18:17:13 -07004503(function($) {
4504 'use strict';
4505
4506 /**
4507 * Toggle Floating Label state.
4508 * @param {HTMLElement} el - The DOM element.
4509 * @param options
4510 * @constructor
4511 */
4512 function FloatingLabel(el, options) {
4513 this.el = $(el);
4514 this.options = $.extend({}, FloatingLabel.DEFAULTS_, options);
4515 this.group = this.el.closest('.dac-form-input-group');
4516 this.input = this.group.find('.dac-form-input');
4517
4518 this.checkValue_ = this.checkValue_.bind(this);
4519 this.checkValue_();
4520
4521 this.input.on('focus', function() {
4522 this.group.addClass('dac-focused');
4523 }.bind(this));
4524 this.input.on('blur', function() {
4525 this.group.removeClass('dac-focused');
4526 this.checkValue_();
4527 }.bind(this));
4528 this.input.on('keyup', this.checkValue_);
4529 }
4530
4531 /**
4532 * The label is moved out of the textbox when it has a value.
4533 */
4534 FloatingLabel.prototype.checkValue_ = function() {
4535 if (this.input.val().length) {
4536 this.group.addClass('dac-has-value');
4537 } else {
4538 this.group.removeClass('dac-has-value');
4539 }
4540 };
4541
4542 /**
4543 * jQuery plugin
4544 * @param {object} options - Override default options.
4545 */
4546 $.fn.dacFloatingLabel = function(options) {
4547 return this.each(function() {
4548 new FloatingLabel(this, options);
4549 });
4550 };
4551
4552 $(document).on('ready.aranja', function() {
4553 $('.dac-form-floatlabel').each(function() {
4554 $(this).dacFloatingLabel($(this).data());
4555 });
4556 });
4557})(jQuery);
4558
4559/* global toRoot, CAROUSEL_OVERRIDE */
4560(function($) {
4561 // Ordering matters
4562 var TAG_MAP = [
4563 {from: 'developerstory', to: 'Android Developer Story'},
4564 {from: 'googleplay', to: 'Google Play'}
4565 ];
4566
4567 function DacCarouselQuery(el) {
4568 this.el = $(el);
4569
4570 var opts = this.el.data();
4571 opts.maxResults = parseInt(opts.maxResults || '100', 10);
4572 opts.query = opts.carouselQuery;
4573 var resources = $.queryResources(opts);
4574
4575 this.el.empty();
4576 $(resources).map(function() {
4577 var resource = $.extend({}, this, CAROUSEL_OVERRIDE[this.url]);
4578 var slide = $('<article class="dac-expand dac-hero">');
4579 var image = cleanUrl(resource.heroImage || resource.image);
4580 var fullBleed = image && !resource.heroColor;
4581
4582 // Configure background
4583 slide.css({
4584 backgroundImage: fullBleed ? 'url(' + image + ')' : '',
4585 backgroundColor: resource.heroColor || ''
4586 });
4587
4588 // Should copy be inverted
4589 slide.toggleClass('dac-invert', resource.heroInvert || fullBleed);
4590 slide.toggleClass('dac-darken', fullBleed);
4591
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004592 // Should be clickable
4593 slide.append($('<a class="dac-hero-carousel-action">').attr('href', cleanUrl(resource.url)));
4594
Dirk Dougherty29e93432015-05-05 18:17:13 -07004595 var cols = $('<div class="cols dac-hero-content">');
4596
4597 // inline image column
4598 var rightCol = $('<div class="col-1of2 col-push-1of2 dac-hero-figure">')
4599 .appendTo(cols);
4600
4601 if (!fullBleed && image) {
4602 rightCol.append($('<img>').attr('src', image));
4603 }
4604
4605 // info column
4606 $('<div class="col-1of2 col-pull-1of2">')
4607 .append($('<div class="dac-hero-tag">').text(formatTag(resource)))
4608 .append($('<h1 class="dac-hero-title">').text(formatTitle(resource)))
4609 .append($('<p class="dac-hero-description">').text(resource.summary))
4610 .append($('<a class="dac-hero-cta">')
4611 .text(formatCTA(resource))
4612 .attr('href', cleanUrl(resource.url))
4613 .prepend($('<span class="dac-sprite dac-auto-chevron">'))
4614 )
4615 .appendTo(cols);
4616
4617 slide.append(cols.wrap('<div class="wrap">').parent());
4618 return slide[0];
4619 }).prependTo(this.el);
4620
4621 // Pagination element.
4622 this.el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>');
4623
4624 this.el.dacCarousel();
4625 }
4626
4627 function cleanUrl(url) {
4628 if (url && url.indexOf('//') === -1) {
4629 url = toRoot + url;
4630 }
4631 return url;
4632 }
4633
4634 function formatTag(resource) {
4635 // Hmm, need a better more scalable solution for this.
4636 for (var i = 0, mapping; mapping = TAG_MAP[i]; i++) {
4637 if (resource.tags.indexOf(mapping.from) > -1) {
4638 return mapping.to;
4639 }
4640 }
4641 return resource.type;
4642 }
4643
4644 function formatTitle(resource) {
4645 return resource.title.replace(/android developer story: /i, '');
4646 }
4647
4648 function formatCTA(resource) {
4649 return resource.type === 'youtube' ? 'Watch the video' : 'Learn more';
4650 }
4651
4652 // jQuery plugin
4653 $.fn.dacCarouselQuery = function() {
4654 return this.each(function() {
4655 var el = $(this);
4656 var data = el.data('dac.carouselQuery');
4657
4658 if (!data) { el.data('dac.carouselQuery', (data = new DacCarouselQuery(el))); }
4659 });
4660 };
4661
4662 // Data API
4663 $(function() {
4664 $('[data-carousel-query]').dacCarouselQuery();
4665 });
4666})(jQuery);
4667
4668(function($) {
4669 /**
4670 * A CSS based carousel, inspired by SequenceJS.
4671 * @param {jQuery} el
4672 * @param {object} options
4673 * @constructor
4674 */
4675 function DacCarousel(el, options) {
4676 this.el = $(el);
4677 this.options = options = $.extend({}, DacCarousel.OPTIONS, this.el.data(), options || {});
4678 this.frames = this.el.find(options.frameSelector);
4679 this.count = this.frames.size();
4680 this.current = options.start;
4681
4682 this.initPagination();
4683 this.initEvents();
4684 this.initFrame();
4685 }
4686
4687 DacCarousel.OPTIONS = {
4688 auto: true,
4689 autoTime: 10000,
4690 autoMinTime: 5000,
4691 btnPrev: '[data-carousel-prev]',
4692 btnNext: '[data-carousel-next]',
4693 frameSelector: 'article',
4694 loop: true,
4695 start: 0,
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004696 swipeThreshold: 160,
Dirk Dougherty29e93432015-05-05 18:17:13 -07004697 pagination: '[data-carousel-pagination]'
4698 };
4699
4700 DacCarousel.prototype.initPagination = function() {
4701 this.pagination = $([]);
4702 if (!this.options.pagination) { return; }
4703
4704 var pagination = $('<ul class="dac-pagination">');
4705 var parent = this.el;
4706 if (typeof this.options.pagination === 'string') { parent = this.el.find(this.options.pagination); }
4707
4708 if (this.count > 1) {
4709 for (var i = 0; i < this.count; i++) {
4710 var li = $('<li class="dac-pagination-item">').text(i);
4711 if (i === this.options.start) { li.addClass('active'); }
4712 li.click(this.go.bind(this, i));
4713
4714 pagination.append(li);
4715 }
4716 this.pagination = pagination.children();
4717 parent.append(pagination);
4718 }
4719 };
4720
4721 DacCarousel.prototype.initEvents = function() {
4722 var that = this;
4723
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004724 this.touch = {
4725 start: {x: 0, y: 0},
4726 end: {x: 0, y: 0}
4727 };
4728
4729 this.el.on('touchstart', this.touchstart_.bind(this));
4730 this.el.on('touchend', this.touchend_.bind(this));
4731 this.el.on('touchmove', this.touchmove_.bind(this));
4732
Dirk Dougherty29e93432015-05-05 18:17:13 -07004733 this.el.hover(function() {
4734 that.pauseRotateTimer();
4735 }, function() {
4736 that.startRotateTimer();
4737 });
4738
4739 $(this.options.btnPrev).click(function(e) {
4740 e.preventDefault();
4741 that.prev();
4742 });
4743
4744 $(this.options.btnNext).click(function(e) {
4745 e.preventDefault();
4746 that.next();
4747 });
4748 };
4749
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004750 DacCarousel.prototype.touchstart_ = function(event) {
4751 var t = event.originalEvent.touches[0];
4752 this.touch.start = {x: t.screenX, y: t.screenY};
4753 };
4754
4755 DacCarousel.prototype.touchend_ = function() {
4756 var deltaX = this.touch.end.x - this.touch.start.x;
4757 var deltaY = Math.abs(this.touch.end.y - this.touch.start.y);
4758 var shouldSwipe = (deltaY < Math.abs(deltaX)) && (Math.abs(deltaX) >= this.options.swipeThreshold);
4759
4760 if (shouldSwipe) {
4761 if (deltaX > 0) {
4762 this.prev();
4763 } else {
4764 this.next();
4765 }
4766 }
4767 };
4768
4769 DacCarousel.prototype.touchmove_ = function(event) {
4770 var t = event.originalEvent.touches[0];
4771 this.touch.end = {x: t.screenX, y: t.screenY};
4772 };
4773
Dirk Dougherty29e93432015-05-05 18:17:13 -07004774 DacCarousel.prototype.initFrame = function() {
4775 this.frames.removeClass('active').eq(this.options.start).addClass('active');
4776 };
4777
4778 DacCarousel.prototype.startRotateTimer = function() {
4779 if (!this.options.auto || this.rotateTimer) { return; }
4780 this.rotateTimer = setTimeout(this.next.bind(this), this.options.autoTime);
4781 };
4782
4783 DacCarousel.prototype.pauseRotateTimer = function() {
4784 clearTimeout(this.rotateTimer);
4785 this.rotateTimer = null;
4786 };
4787
4788 DacCarousel.prototype.prev = function() {
4789 this.go(this.current - 1);
4790 };
4791
4792 DacCarousel.prototype.next = function() {
4793 this.go(this.current + 1);
4794 };
4795
4796 DacCarousel.prototype.go = function(next) {
4797 // Figure out what the next slide is.
4798 while (this.count > 0 && next >= this.count) { next -= this.count; }
4799 while (next < 0) { next += this.count; }
4800
4801 // Cancel if we're already on that slide.
4802 if (next === this.current) { return; }
4803
4804 // Prepare next slide.
4805 this.frames.eq(next).removeClass('out');
4806
4807 // Recalculate styles before starting slide transition.
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004808 this.el.resolveStyles();
4809 // Update pagination
4810 this.pagination.removeClass('active').eq(next).addClass('active');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004811
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004812 // Transition out current frame
4813 this.frames.eq(this.current).toggleClass('active out');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004814
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004815 // Transition in a new frame
4816 this.frames.eq(next).toggleClass('active');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004817
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004818 this.current = next;
Dirk Dougherty29e93432015-05-05 18:17:13 -07004819 };
4820
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004821 // Helper which resolves new styles for an element, so it can start transitioning
4822 // from the new values.
4823 $.fn.resolveStyles = function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004824 /*jshint expr:true*/
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004825 this[0] && this[0].offsetTop;
4826 return this;
4827 };
Dirk Dougherty29e93432015-05-05 18:17:13 -07004828
4829 // jQuery plugin
4830 $.fn.dacCarousel = function() {
4831 this.each(function() {
4832 var $el = $(this);
4833 $el.data('dac-carousel', new DacCarousel(this));
4834 });
4835 return this;
4836 };
4837
4838 // Data API
4839 $(function() {
4840 $('[data-carousel]').dacCarousel();
4841 });
4842})(jQuery);
4843
4844(function($) {
4845 'use strict';
4846
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004847 function Modal(el, options) {
4848 this.el = $(el);
4849 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
4850 this.isOpen = false;
4851
4852 this.el.on('click', function(event) {
4853 if (!$.contains($('.dac-modal-window')[0], event.target)) {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004854 return this.el.trigger('modal-close');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004855 }
4856 }.bind(this));
4857
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004858 this.el.on('modal-open', this.open_.bind(this));
4859 this.el.on('modal-close', this.close_.bind(this));
4860 this.el.on('modal-toggle', this.toggle_.bind(this));
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004861 }
4862
4863 Modal.prototype.toggle_ = function() {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004864 this.el.trigger('modal-' + (this.isOpen ? 'close' : 'open'));
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004865 };
4866
4867 Modal.prototype.close_ = function() {
4868 this.el.removeClass('dac-active');
4869 $('body').removeClass('dac-modal-open');
4870 this.isOpen = false;
4871 };
4872
4873 Modal.prototype.open_ = function() {
4874 this.el.addClass('dac-active');
4875 $('body').addClass('dac-modal-open');
4876 this.isOpen = true;
4877 };
4878
Dirk Dougherty29e93432015-05-05 18:17:13 -07004879 function ToggleModal(el, options) {
4880 this.el = $(el);
4881 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004882 this.modal = this.options.modalToggle ? $('[data-modal="' + this.options.modalToggle + '"]') :
4883 this.el.closest('[data-modal]');
4884
Dirk Dougherty29e93432015-05-05 18:17:13 -07004885 this.el.on('click', this.clickHandler_.bind(this));
4886 }
4887
Dirk Dougherty29e93432015-05-05 18:17:13 -07004888 ToggleModal.prototype.clickHandler_ = function(event) {
4889 event.preventDefault();
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004890 this.modal.trigger('modal-toggle');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004891 };
4892
4893 /**
4894 * jQuery plugin
4895 * @param {object} options - Override default options.
4896 */
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004897 $.fn.dacModal = function(options) {
4898 return this.each(function() {
4899 new Modal(this, options);
4900 });
4901 };
4902
Dirk Dougherty29e93432015-05-05 18:17:13 -07004903 $.fn.dacToggleModal = function(options) {
4904 return this.each(function() {
4905 new ToggleModal(this, options);
4906 });
4907 };
4908
4909 /**
4910 * Data Attribute API
4911 */
4912 $(document).on('ready.aranja', function() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004913 $('[data-modal]').each(function() {
4914 $(this).dacModal($(this).data());
4915 });
4916
4917 $('[data-modal-toggle]').each(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004918 $(this).dacToggleModal($(this).data());
4919 });
4920 });
4921})(jQuery);
4922
4923(function($) {
4924 'use strict';
4925
4926 /**
4927 * Toggle the visabilty of the mobile navigation.
4928 * @param {HTMLElement} el - The DOM element.
4929 * @param options
4930 * @constructor
4931 */
4932 function ToggleNav(el, options) {
4933 this.el = $(el);
4934 this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
4935 this.options.target = [this.options.navigation];
4936
4937 if (this.options.body) {this.options.target.push('body')}
4938 if (this.options.dimmer) {this.options.target.push(this.options.dimmer)}
4939
4940 this.el.on('click', this.clickHandler_.bind(this));
4941 }
4942
4943 /**
4944 * ToggleNav Default Settings
4945 * @type {{body: boolean, dimmer: string, navigation: string, toggleClass: string}}
4946 * @private
4947 */
4948 ToggleNav.DEFAULTS_ = {
4949 body: true,
4950 dimmer: '.dac-nav-dimmer',
4951 navigation: '[data-dac-nav]',
4952 toggleClass: 'dac-nav-open'
4953 };
4954
4955 /**
4956 * The actual toggle logic.
4957 * @param event
4958 * @private
4959 */
4960 ToggleNav.prototype.clickHandler_ = function(event) {
4961 event.preventDefault();
4962 $(this.options.target.join(', ')).toggleClass(this.options.toggleClass);
4963 };
4964
4965 /**
4966 * jQuery plugin
4967 * @param {object} options - Override default options.
4968 */
4969 $.fn.dacToggleMobileNav = function(options) {
4970 return this.each(function() {
4971 new ToggleNav(this, options);
4972 });
4973 };
4974
4975 /**
4976 * Data Attribute API
4977 */
4978 $(window).on('load.aranja', function() {
4979 $('[data-dac-toggle-nav]').each(function() {
4980 $(this).dacToggleMobileNav($(this).data());
4981 });
4982 });
4983})(jQuery);
4984
4985(function($) {
4986 'use strict';
4987
4988 /**
4989 * Submit the newsletter form to a Google Form.
4990 * @param {HTMLElement} el - The Form DOM element.
4991 * @constructor
4992 */
4993 function NewsletterForm(el) {
4994 this.el = $(el);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004995 this.form = this.el.find('form');
4996 $('<iframe/>').hide()
4997 .attr('name', 'dac-newsletter-iframe')
4998 .attr('src', '')
4999 .insertBefore(this.form);
5000 this.form.on('submit', this.submitHandler_.bind(this));
Dirk Dougherty29e93432015-05-05 18:17:13 -07005001 }
5002
5003 /**
Dirk Doughertycbe032f2015-05-22 11:41:40 -07005004 * Milliseconds until modal has vanished after modal-close is triggered.
5005 * @type {number}
5006 * @private
5007 */
5008 NewsletterForm.CLOSE_DELAY_ = 300;
5009
5010 /**
5011 * Switch view to display form after close.
5012 * @private
5013 */
5014 NewsletterForm.prototype.closeHandler_ = function() {
5015 setTimeout(function() {
5016 this.el.trigger('swap-reset');
5017 }.bind(this), NewsletterForm.CLOSE_DELAY_);
5018 };
5019
5020 /**
5021 * Reset the modal to initial state.
5022 * @private
5023 */
5024 NewsletterForm.prototype.reset_ = function() {
5025 this.form.trigger('reset');
5026 this.el.one('modal-close', this.closeHandler_.bind(this));
5027 };
5028
5029 /**
5030 * Display a success view on submit.
Dirk Dougherty29e93432015-05-05 18:17:13 -07005031 * @private
5032 */
5033 NewsletterForm.prototype.submitHandler_ = function() {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07005034 this.el.one('swap-complete', this.reset_.bind(this));
5035 this.el.trigger('swap-content');
Dirk Dougherty29e93432015-05-05 18:17:13 -07005036 };
5037
5038 /**
5039 * jQuery plugin
5040 * @param {object} options - Override default options.
5041 */
5042 $.fn.dacNewsletterForm = function(options) {
5043 return this.each(function() {
5044 new NewsletterForm(this, options);
5045 });
5046 };
5047
5048 /**
5049 * Data Attribute API
5050 */
5051 $(document).on('ready.aranja', function() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005052 $('[data-newsletter]').each(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07005053 $(this).dacNewsletterForm();
5054 });
5055 });
5056})(jQuery);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005057
5058(function($) {
5059 'use strict';
5060
5061 /**
5062 * Smoothly scroll to location on current page.
5063 * @param el
5064 * @param options
5065 * @constructor
5066 */
5067 function ScrollButton(el, options) {
5068 this.el = $(el);
5069 this.target = $(this.el.attr('href'));
5070 this.options = $.extend({}, ScrollButton.DEFAULTS_, options);
5071
5072 if (typeof this.options.offset === 'string') {
5073 this.options.offset = $(this.options.offset).height();
5074 }
5075
5076 this.el.on('click', this.clickHandler_.bind(this));
5077 }
5078
5079 /**
5080 * Default options
5081 * @type {{duration: number, easing: string, offset: number, scrollContainer: string}}
5082 * @private
5083 */
5084 ScrollButton.DEFAULTS_ = {
5085 duration: 300,
5086 easing: 'swing',
5087 offset: 0,
5088 scrollContainer: 'html, body'
5089 };
5090
5091 /**
5092 * Scroll logic
5093 * @param event
5094 * @private
5095 */
5096 ScrollButton.prototype.clickHandler_ = function(event) {
5097 if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
5098 return;
5099 }
5100
5101 event.preventDefault();
5102
5103 $(this.options.scrollContainer).animate({
5104 scrollTop: this.target.offset().top - this.options.offset
5105 }, this.options);
5106 };
5107
5108 /**
5109 * jQuery plugin
5110 * @param {object} options - Override default options.
5111 */
5112 $.fn.dacScrollButton = function(options) {
5113 return this.each(function() {
5114 new ScrollButton(this, options);
5115 });
5116 };
5117
5118 /**
5119 * Data Attribute API
5120 */
5121 $(document).on('ready.aranja', function() {
5122 $('[data-scroll-button]').each(function() {
5123 $(this).dacScrollButton($(this).data());
5124 });
5125 });
5126})(jQuery);
5127
5128(function($) {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07005129 'use strict';
5130
5131 /**
5132 * A component that swaps two dynamic height views with an animation.
5133 * Listens for the following events:
5134 * * swap-content: triggers SwapContent.swap_()
5135 * * swap-reset: triggers SwapContent.reset()
5136 * @param el
5137 * @param options
5138 * @constructor
5139 */
5140 function SwapContent(el, options) {
5141 this.el = $(el);
5142 this.options = $.extend({}, SwapContent.DEFAULTS_, options);
5143 this.containers = this.el.find(this.options.container);
5144 this.initiallyActive = this.containers.children('.' + this.options.activeClass).eq(0);
5145 this.el.on('swap-content', this.swap.bind(this));
5146 this.el.on('swap-reset', this.reset.bind(this));
5147 }
5148
5149 /**
5150 * SwapContent's default settings.
5151 * @type {{activeClass: string, container: string, transitionSpeed: number}}
5152 * @private
5153 */
5154 SwapContent.DEFAULTS_ = {
5155 activeClass: 'dac-active',
5156 container: '[data-swap-container]',
5157 transitionSpeed: 500
5158 };
5159
5160 /**
5161 * Returns container's visible height.
5162 * @param container
5163 * @returns {number}
5164 */
5165 SwapContent.prototype.currentHeight = function(container) {
5166 return container.children('.' + this.options.activeClass).outerHeight();
5167 };
5168
5169 /**
5170 * Reset to show initial content
5171 */
5172 SwapContent.prototype.reset = function() {
5173 if (!this.initiallyActive.hasClass(this.initiallyActive)) {
5174 this.containers.children().toggleClass(this.options.activeClass);
5175 }
5176 };
5177
5178 /**
5179 * Complete the swap.
5180 */
5181 SwapContent.prototype.complete = function() {
5182 this.containers.height('auto');
5183 this.containers.trigger('swap-complete');
5184 };
5185
5186 /**
5187 * Perform the swap of content.
5188 */
5189 SwapContent.prototype.swap = function() {
5190 console.log(this.containers);
5191 this.containers.each(function(index, container) {
5192 container = $(container);
5193 container.height(this.currentHeight(container)).children().toggleClass(this.options.activeClass);
5194 container.animate({height: this.currentHeight(container)}, this.options.transitionSpeed,
5195 this.complete.bind(this));
5196 }.bind(this));
5197 };
5198
5199 /**
5200 * jQuery plugin
5201 * @param {object} options - Override default options.
5202 */
5203 $.fn.dacSwapContent = function(options) {
5204 return this.each(function() {
5205 new SwapContent(this, options);
5206 });
5207 };
5208
5209 /**
5210 * Data Attribute API
5211 */
5212 $(document).on('ready.aranja', function() {
5213 $('[data-swap]').each(function() {
5214 $(this).dacSwapContent($(this).data());
5215 });
5216 });
5217})(jQuery);
5218
5219(function($) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005220 function Toggle(el) {
5221 $(el).on('click.dac.togglesection', this.toggle);
5222 }
5223
5224 Toggle.prototype.toggle = function() {
5225 var $this = $(this);
5226
5227 var $parent = getParent($this);
5228 var isExpanded = $parent.hasClass('is-expanded');
5229
5230 transitionMaxHeight($parent.find('.dac-toggle-content'), !isExpanded);
5231 $parent.toggleClass('is-expanded');
5232
5233 return false;
5234 };
5235
5236 function getParent($this) {
5237 var selector = $this.attr('data-target');
5238
5239 if (!selector) {
5240 selector = $this.attr('href');
5241 selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '');
5242 }
5243
5244 var $parent = selector && $(selector);
5245
Dirk Doughertycbe032f2015-05-22 11:41:40 -07005246 $parent = $parent && $parent.length ? $parent : $this.closest('.dac-toggle');
5247
5248 return $parent.length ? $parent : $this.parent();
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005249 }
5250
5251 /**
5252 * Runs a transition of max-height along with responsive styles which hide or expand the element.
5253 * @param $el
5254 * @param visible
5255 */
5256 function transitionMaxHeight($el, visible) {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07005257 var contentHeight = $el.prop('scrollHeight');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005258 var targetHeight = visible ? contentHeight : 0;
5259 var duration = $el.transitionDuration();
5260
5261 // If we're hiding, first set the maxHeight we're transitioning from.
5262 if (!visible) {
5263 $el.css('maxHeight', contentHeight + 'px')
5264 .resolveStyles();
5265 }
5266
5267 // Transition to new state
5268 $el.css('maxHeight', targetHeight);
5269
5270 // Reset maxHeight to css value after transition.
5271 setTimeout(function() {
5272 $el.css('maxHeight', '');
5273 }, duration);
5274 }
5275
5276 // Utility to get the transition duration for the element.
5277 $.fn.transitionDuration = function() {
5278 var d = $(this).css('transitionDuration') || '0s';
5279
5280 return +(parseFloat(d) * (/ms/.test(d) ? 1 : 1000)).toFixed(0);
5281 };
5282
5283 // jQuery plugin
5284 $.fn.toggleSection = function(option) {
5285 return this.each(function() {
5286 var $this = $(this);
5287 var data = $this.data('dac.togglesection');
5288 if (!data) {$this.data('dac.togglesection', (data = new Toggle(this)));}
5289 if (typeof option === 'string') {data[option].call($this);}
5290 });
5291 };
5292
5293 // Data api
5294 $(document)
5295 .on('click.toggle', '[data-toggle="section"]', Toggle.prototype.toggle);
5296})(jQuery);