blob: e4740479d38eb746cf3cedfded6112e450071e3a [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
smain@google.comc0401872016-03-16 11:48:23 -07001389 + "assets/images/styles/disclosure_up.png");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001390 } 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
smain@google.comc0401872016-03-16 11:48:23 -07001397 + "assets/images/styles/disclosure_down.png");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001398 });
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
Dirk Dougherty49a56682016-03-09 14:07:31 -08002365 for (var i=0; i<PREVIEW_RESOURCES.length; i++) {
Dirk Dougherty86120022016-02-09 18:00:05 -08002366 // current search comparison, with counters for tag and title,
2367 // used later to improve ranking
Dirk Dougherty49a56682016-03-09 14:07:31 -08002368 var s = PREVIEW_RESOURCES[i];
Dirk Dougherty86120022016-02-09 18:00:05 -08002369 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;
Amanda Kassay843649b2016-03-17 14:11:13 -04002551 searchControl.query = query;
2552 searchControl.init();
2553 searchControl.trackSearchRequest(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002554 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002555 return false;
2556}
2557
Scott Mainf5089842012-08-14 16:31:07 -07002558function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002559 $("#searchResults").slideUp('fast', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002560 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002561 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002562
Scott Mainf5089842012-08-14 16:31:07 -07002563 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002564
Scott Mainf5089842012-08-14 16:31:07 -07002565 // reset the ajax search callback to nothing, so results don't appear unless ENTER
Amanda Kassay843649b2016-03-17 14:11:13 -04002566 searchControl.reset();
Scott Main0e76e7e2013-03-12 10:24:07 -07002567
Scott Mainf5089842012-08-14 16:31:07 -07002568 return false;
2569}
2570
Scott Mainf5089842012-08-14 16:31:07 -07002571/* ########################################################## */
2572/* ################ CUSTOM SEARCH ENGINE ################## */
2573/* ########################################################## */
Amanda Kassay843649b2016-03-17 14:11:13 -04002574var searchControl = null;
2575var dacsearch = dacsearch || {};
Scott Mainf5089842012-08-14 16:31:07 -07002576
Amanda Kassay843649b2016-03-17 14:11:13 -04002577/**
2578 * The custom search engine API.
2579 * @constructor
2580 */
2581dacsearch.CustomSearchEngine = function() {
2582 /**
2583 * The last response from Google CSE.
2584 * @private {Object}
2585 */
2586 this.resultQuery_ = {};
Scott Mainf5089842012-08-14 16:31:07 -07002587
Amanda Kassay843649b2016-03-17 14:11:13 -04002588 /** @private {?Element} */
2589 this.searchResultEl_ = null;
Scott Mainf5089842012-08-14 16:31:07 -07002590
Amanda Kassay843649b2016-03-17 14:11:13 -04002591 /** @private {?Element} */
2592 this.searchInputEl_ = null;
Scott Mainf5089842012-08-14 16:31:07 -07002593
Amanda Kassay843649b2016-03-17 14:11:13 -04002594 /** @private {string} */
2595 this.query = '';
2596};
Scott Mainf5089842012-08-14 16:31:07 -07002597
Amanda Kassay843649b2016-03-17 14:11:13 -04002598/**
2599 * Initializes DAC's Google custom search engine.
2600 * @export
2601 */
2602dacsearch.CustomSearchEngine.prototype.init = function() {
2603 this.searchResultEl_ = $('#leftSearchControl');
2604 this.searchResultEl_.empty();
2605 this.searchInputEl_ = $('#search_autocomplete');
2606 this.searchInputEl_.focus().val(this.query);
2607 this.getResults_();
2608 this.bindEvents_();
2609};
Scott Mainf5089842012-08-14 16:31:07 -07002610
Scott Mainf5089842012-08-14 16:31:07 -07002611
Amanda Kassay843649b2016-03-17 14:11:13 -04002612/**
2613 * Binds the keyup event to the search input.
2614 * @private
2615 */
2616dacsearch.CustomSearchEngine.prototype.bindEvents_ = function() {
2617 this.searchInputEl_.keyup(this.debounce_(function(e) {
2618 var code = e.which;
2619 if (code != 13) {
2620 this.query = this.searchInputEl_.val();
2621 location.hash = 'q=' + encodeURI(this.query);
2622 this.searchResultEl_.empty();
2623 this.getResults_();
2624 }
2625 }.bind(this), 250));
2626};
Scott Mainf5089842012-08-14 16:31:07 -07002627
Scott Mainf5089842012-08-14 16:31:07 -07002628
Amanda Kassay843649b2016-03-17 14:11:13 -04002629/**
2630 * Resets the search control.
2631 */
2632dacsearch.CustomSearchEngine.prototype.reset = function() {
2633 this.query = '';
2634 this.searchInputEl_.off('keyup');
2635 this.searchResultEl_.empty();
2636 this.updateResultTitle_();
2637};
Scott Mainf5089842012-08-14 16:31:07 -07002638
Scott Mainf5089842012-08-14 16:31:07 -07002639
Amanda Kassay843649b2016-03-17 14:11:13 -04002640/**
2641 * Updates the search query text at the top of the results.
2642 * @private
2643 */
2644dacsearch.CustomSearchEngine.prototype.updateResultTitle_ = function() {
2645 $("#searchTitle").html("Results for <em>" + this.query + "</em>");
2646};
Scott Maindf08ada2012-12-03 08:54:37 -08002647
Scott Mainf5089842012-08-14 16:31:07 -07002648
Amanda Kassay843649b2016-03-17 14:11:13 -04002649/**
2650 * Makes the CSE api call and gets the results.
2651 * @param {number=} opt_start The optional start index.
2652 * @private
2653 */
2654dacsearch.CustomSearchEngine.prototype.getResults_ = function(opt_start) {
2655 var lang = getLangPref();
2656 // Fix zh-cn to be zh-CN.
2657 lang = lang.replace(/-\w+/, function(m) { return m.toUpperCase(); });
2658 var cseUrl = 'https://content.googleapis.com/customsearch/v1?';
2659 var searchParams = {
2660 cx: '000521750095050289010:zpcpi1ea4s8',
2661 key: 'AIzaSyCFhbGnjW06dYwvRCU8h_zjdpS4PYYbEe8',
2662 q: this.query,
2663 start: opt_start || 1,
2664 num: 6,
2665 hl: lang,
2666 fields: 'queries,items(pagemap,link,title,htmlSnippet,formattedUrl)'
2667 };
Scott Mainf5089842012-08-14 16:31:07 -07002668
Amanda Kassay843649b2016-03-17 14:11:13 -04002669 $.get(cseUrl + $.param(searchParams), function(data) {
2670 this.resultQuery_ = data;
2671 this.renderResults_(data);
2672 this.updateResultTitle_(this.query);
2673 }.bind(this));
2674};
Scott Mainf5089842012-08-14 16:31:07 -07002675
Scott Mainf5089842012-08-14 16:31:07 -07002676
Amanda Kassay843649b2016-03-17 14:11:13 -04002677/**
2678 * Renders the results.
2679 * @private
2680 */
2681dacsearch.CustomSearchEngine.prototype.renderResults_ = function(results) {
2682 var el = this.searchResultEl_;
Scott Mainde295272013-03-25 15:48:35 -07002683
Amanda Kassay843649b2016-03-17 14:11:13 -04002684 if (!results.items) {
2685 el.append($('<div>').text('No results'));
2686 return;
2687 }
Scott Mainf5089842012-08-14 16:31:07 -07002688
Amanda Kassay843649b2016-03-17 14:11:13 -04002689 for (var i = 0; i < results.items.length; i++) {
2690 var item = results.items[i];
2691 var hasImage = item.pagemap && item.pagemap.cse_thumbnail;
2692 var sectionMatch = item.link.match(/developer\.android\.com\/(\w*)/);
2693 var section = (sectionMatch && sectionMatch[1]) || 'blog';
Scott Mainf5089842012-08-14 16:31:07 -07002694
Amanda Kassay843649b2016-03-17 14:11:13 -04002695 var entry = $('<div>').addClass('dac-custom-search-entry cols');
2696
2697 if (hasImage) {
2698 var image = item.pagemap.cse_thumbnail[0];
2699 entry.append($('<div>').addClass('col-1of6')
2700 .append($('<div>').addClass('dac-custom-search-image').css(
2701 'background-image', 'url(' + image.src + ')')));
2702 }
2703
2704 var linkTitleEl = $('<a>').text(item.title).attr('href', item.link);
2705 linkTitleEl.click(function(e) {
2706 ga('send', 'event', 'Google Custom Search',
2707 'clicked: ' + linkTitleEl.attr('href'),
2708 'query: ' + $("#search_autocomplete").val().toLowerCase());
2709 });
2710
2711 var linkUrlEl = $('<a>').addClass('dac-custom-search-link').text(
2712 item.formattedUrl).attr('href', item.link);
2713 linkUrlEl.click(function(e) {
2714 ga('send', 'event', 'Google Custom Search',
2715 'clicked: ' + linkUrlEl.attr('href'),
2716 'query: ' + $("#search_autocomplete").val().toLowerCase());
2717 });
2718
2719
2720 entry.append($('<div>').addClass(hasImage ? 'col-5of6' : 'col-6of6')
2721 .append($('<p>').addClass('dac-custom-search-section').text(section))
2722 .append(
2723 linkTitleEl.wrap('<h2>').parent().addClass('dac-custom-search-title'))
2724 .append($('<p>').addClass('dac-custom-search-snippet')
2725 .html(item.htmlSnippet.replace(/<br>/g, ''))).append(linkUrlEl));
2726
2727 el.append(entry);
2728 }
2729
2730 if ($('#dac-custom-search-load-more')) {
2731 $('#dac-custom-search-load-more').remove();
2732 }
2733
2734 if (results.queries.nextPage) {
2735 var loadMoreButton = $('<button id="dac-custom-search-load-more">')
2736 .addClass('dac-custom-search-load-more')
2737 .text('Load more')
2738 .click(function() {
2739 this.loadMoreResults_();
2740 }.bind(this));
2741
2742 el.append(loadMoreButton);
2743 }
2744};
2745
2746
2747/**
2748 * Loads more results.
2749 * @private
2750 */
2751dacsearch.CustomSearchEngine.prototype.loadMoreResults_ = function() {
2752 this.query = this.resultQuery_.queries.request[0].searchTerms;
2753 var start = this.resultQuery_.queries.nextPage[0].startIndex;
2754 var loadMoreButton = this.searchResultEl_.find(
2755 '#dac-custom-search-load-more');
2756 loadMoreButton.text('Loading more...');
2757 this.getResults_(start);
2758 this.trackSearchRequest(this.query + ' startIndex = ' + start);
2759};
2760
2761
2762/**
2763 * Tracks a search request.
2764 * @param {string} query The query for the request,
2765 * includes start index if loading more results.
2766 */
2767dacsearch.CustomSearchEngine.prototype.trackSearchRequest = function(query) {
2768 ga('send', 'event', 'Google Custom Search Submit', 'submit search query',
2769 'query: ' + query);
2770};
2771
2772
2773/**
2774 * Returns a function, that, as long as it continues to be invoked, will not
2775 * be triggered. The function will be called after it stops being called for
2776 * N milliseconds.
2777 * @param {Function} func The function to debounce.
2778 * @param {number} wait The number of milliseconds to wait before calling the function.
2779 * @private
2780 */
2781dacsearch.CustomSearchEngine.prototype.debounce_ = function(func, wait) {
2782 var timeout;
2783 return function() {
2784 var context = this, args = arguments;
2785 var later = function() {
2786 timeout = null;
2787 func.apply(context, args);
2788 };
2789 clearTimeout(timeout);
2790 timeout = setTimeout(later, wait);
2791 };
2792};
Scott Mainf5089842012-08-14 16:31:07 -07002793
2794
2795google.setOnLoadCallback(function(){
Amanda Kassay843649b2016-03-17 14:11:13 -04002796 searchControl = new dacsearch.CustomSearchEngine();
Scott Mainf5089842012-08-14 16:31:07 -07002797 if (location.hash.indexOf("q=") == -1) {
2798 // if there's no query in the url, don't search and make sure results are hidden
2799 $('#searchResults').hide();
2800 return;
2801 } else {
2802 // first time loading search results for this page
Amanda Kassay843649b2016-03-17 14:11:13 -04002803 searchControl.query = decodeURI(location.hash.split('q=')[1]);
2804 searchControl.init();
2805 searchControl.trackSearchRequest(searchControl.query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002806 $('#searchResults').slideDown('slow', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002807 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002808 }
2809}, true);
2810
smain@google.com9a818f52014-10-03 09:25:59 -07002811/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2812 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002813function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002814 // Ignore if there's no search bar (some special pages have no header)
2815 if ($("#search-container").length < 1) return;
2816
smain@google.com3b77ab52014-06-17 11:57:27 -07002817 var hash = escape(location.hash.substr(1));
2818 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002819 // Sanity check that there's an element with that ID on the page
2820 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002821 // If the position of the target element is near the top of the page (<20px, where we expect it
2822 // to be because we need to move it down 60px to become in view), then move it down 60px
2823 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2824 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002825 }
2826 }
2827}
2828
Scott Mainf5089842012-08-14 16:31:07 -07002829// when an event on the browser history occurs (back, forward, load) requery hash and do search
2830$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002831 // Ignore if there's no search bar (some special pages have no header)
2832 if ($("#search-container").length < 1) return;
2833
Dirk Doughertyc3921652014-05-13 16:55:26 -07002834 // If the hash isn't a search query or there's an error in the query,
2835 // then adjust the scroll position to account for sticky header, then exit.
Amanda Kassay843649b2016-03-17 14:11:13 -04002836 if ((location.hash.indexOf("q=") == -1) || (searchControl.query == "undefined")) {
Scott Mainf5089842012-08-14 16:31:07 -07002837 // If the results pane is open, close it.
2838 if (!$("#searchResults").is(":hidden")) {
2839 hideResults();
2840 }
Scott Mainb16376f2014-05-21 20:35:47 -07002841 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002842 return;
2843 }
2844
Dirk Doughertyc3921652014-05-13 16:55:26 -07002845 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002846 $("#search_autocomplete").focus();
Dirk Dougherty29e93432015-05-05 18:17:13 -07002847 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002848});
2849
Scott Mainf5089842012-08-14 16:31:07 -07002850/* returns the given string with all HTML brackets converted to entities
2851 TODO: move this to the site's JS library */
2852function escapeHTML(string) {
2853 return string.replace(/</g,"&lt;")
2854 .replace(/>/g,"&gt;");
2855}
2856
2857
2858
2859
2860
2861
2862
2863/* ######################################################## */
2864/* ################# JAVADOC REFERENCE ################### */
2865/* ######################################################## */
2866
Scott Main65511c02012-09-07 15:51:32 -07002867/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002868if (location.pathname.indexOf("/reference") == 0) {
2869 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2870 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2871 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002872 $(document).ready(function() {
2873 // init available apis based on user pref
2874 changeApiLevel();
2875 initSidenavHeightResize()
2876 });
2877 }
Scott Main65511c02012-09-07 15:51:32 -07002878}
Scott Mainf5089842012-08-14 16:31:07 -07002879
2880var API_LEVEL_COOKIE = "api_level";
2881var minLevel = 1;
2882var maxLevel = 1;
2883
2884/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002885
Scott Mainf5089842012-08-14 16:31:07 -07002886 function initSidenavHeightResize() {
2887 // Change the drag bar size to nicely fit the scrollbar positions
2888 var $dragBar = $(".ui-resizable-s");
2889 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002890
2891 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002892 containment: "#nav-panels",
2893 handles: "s",
2894 alsoResize: "#packages-nav",
2895 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2896 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2897 });
Scott Main3b90aff2013-08-01 18:09:35 -07002898
Scott Mainf5089842012-08-14 16:31:07 -07002899 }
Scott Main3b90aff2013-08-01 18:09:35 -07002900
Scott Mainf5089842012-08-14 16:31:07 -07002901function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002902 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002903 $('#devdoc-nav').css({
2904 'width' : $('#side-nav').css('width'),
2905 'margin' : $('#side-nav').css('margin')
2906 });
2907 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002908
Scott Mainf5089842012-08-14 16:31:07 -07002909 initSidenavHeightResize();
2910}
2911
2912function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002913 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002914 $('#devdoc-nav').css({
2915 'width' : $('#side-nav').css('width'),
2916 'margin' : $('#side-nav').css('margin')
2917 });
2918 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002919
Scott Mainf5089842012-08-14 16:31:07 -07002920 initSidenavHeightResize();
2921}
2922
2923function buildApiLevelSelector() {
2924 maxLevel = SINCE_DATA.length;
2925 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2926 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2927
2928 minLevel = parseInt($("#doc-api-level").attr("class"));
2929 // Handle provisional api levels; the provisional level will always be the highest possible level
2930 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2931 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2932 if (isNaN(minLevel) && minLevel.length) {
2933 minLevel = maxLevel;
2934 }
2935 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2936 for (var i = maxLevel-1; i >= 0; i--) {
2937 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2938 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2939 select.append(option);
2940 }
2941
2942 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2943 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2944 selectedLevelItem.setAttribute('selected',true);
2945}
2946
2947function changeApiLevel() {
2948 maxLevel = SINCE_DATA.length;
2949 var selectedLevel = maxLevel;
2950
2951 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2952 toggleVisisbleApis(selectedLevel, "body");
2953
smain@google.com6bdcb982014-11-14 11:53:07 -08002954 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002955
2956 if (selectedLevel < minLevel) {
2957 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002958 $("#naMessage").show().html("<div><p><strong>This " + thing
2959 + " requires API level " + minLevel + " or higher.</strong></p>"
2960 + "<p>This document is hidden because your selected API level for the documentation is "
2961 + selectedLevel + ". You can change the documentation API level with the selector "
2962 + "above the left navigation.</p>"
2963 + "<p>For more information about specifying the API level your app requires, "
2964 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2965 + ">Supporting Different Platform Versions</a>.</p>"
2966 + "<input type='button' value='OK, make this page visible' "
2967 + "title='Change the API level to " + minLevel + "' "
2968 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2969 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002970 } else {
2971 $("#naMessage").hide();
2972 }
2973}
2974
2975function toggleVisisbleApis(selectedLevel, context) {
2976 var apis = $(".api",context);
2977 apis.each(function(i) {
2978 var obj = $(this);
2979 var className = obj.attr("class");
2980 var apiLevelIndex = className.lastIndexOf("-")+1;
2981 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2982 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2983 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2984 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2985 return;
2986 }
2987 apiLevel = parseInt(apiLevel);
2988
2989 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2990 var selectedLevelNum = parseInt(selectedLevel)
2991 var apiLevelNum = parseInt(apiLevel);
2992 if (isNaN(apiLevelNum)) {
2993 apiLevelNum = maxLevel;
2994 }
2995
2996 // Grey things out that aren't available and give a tooltip title
2997 if (apiLevelNum > selectedLevelNum) {
2998 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002999 + apiLevel + "\" or higher. To reveal, change the target API level "
3000 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07003001 }
Scott Mainf5089842012-08-14 16:31:07 -07003002 else obj.removeClass("absent").removeAttr("title");
3003 });
3004}
3005
3006
3007
3008
3009/* ################# SIDENAV TREE VIEW ################### */
3010
3011function new_node(me, mom, text, link, children_data, api_level)
3012{
3013 var node = new Object();
3014 node.children = Array();
3015 node.children_data = children_data;
3016 node.depth = mom.depth + 1;
3017
3018 node.li = document.createElement("li");
3019 mom.get_children_ul().appendChild(node.li);
3020
3021 node.label_div = document.createElement("div");
3022 node.label_div.className = "label";
3023 if (api_level != null) {
3024 $(node.label_div).addClass("api");
3025 $(node.label_div).addClass("api-level-"+api_level);
3026 }
3027 node.li.appendChild(node.label_div);
3028
3029 if (children_data != null) {
3030 node.expand_toggle = document.createElement("a");
3031 node.expand_toggle.href = "javascript:void(0)";
3032 node.expand_toggle.onclick = function() {
3033 if (node.expanded) {
3034 $(node.get_children_ul()).slideUp("fast");
3035 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
3036 node.expanded = false;
3037 } else {
3038 expand_node(me, node);
3039 }
3040 };
3041 node.label_div.appendChild(node.expand_toggle);
3042
3043 node.plus_img = document.createElement("img");
3044 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
3045 node.plus_img.className = "plus";
3046 node.plus_img.width = "8";
3047 node.plus_img.border = "0";
3048 node.expand_toggle.appendChild(node.plus_img);
3049
3050 node.expanded = false;
3051 }
3052
3053 var a = document.createElement("a");
3054 node.label_div.appendChild(a);
3055 node.label = document.createTextNode(text);
3056 a.appendChild(node.label);
3057 if (link) {
3058 a.href = me.toroot + link;
3059 } else {
3060 if (children_data != null) {
3061 a.className = "nolink";
3062 a.href = "javascript:void(0)";
3063 a.onclick = node.expand_toggle.onclick;
3064 // This next line shouldn't be necessary. I'll buy a beer for the first
3065 // person who figures out how to remove this line and have the link
3066 // toggle shut on the first try. --joeo@android.com
3067 node.expanded = false;
3068 }
3069 }
Scott Main3b90aff2013-08-01 18:09:35 -07003070
Scott Mainf5089842012-08-14 16:31:07 -07003071
3072 node.children_ul = null;
3073 node.get_children_ul = function() {
3074 if (!node.children_ul) {
3075 node.children_ul = document.createElement("ul");
3076 node.children_ul.className = "children_ul";
3077 node.children_ul.style.display = "none";
3078 node.li.appendChild(node.children_ul);
3079 }
3080 return node.children_ul;
3081 };
3082
3083 return node;
3084}
3085
Robert Lyd2dd6e52012-11-29 21:28:48 -08003086
3087
3088
Scott Mainf5089842012-08-14 16:31:07 -07003089function expand_node(me, node)
3090{
3091 if (node.children_data && !node.expanded) {
3092 if (node.children_visited) {
3093 $(node.get_children_ul()).slideDown("fast");
3094 } else {
3095 get_node(me, node);
3096 if ($(node.label_div).hasClass("absent")) {
3097 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07003098 }
Scott Mainf5089842012-08-14 16:31:07 -07003099 $(node.get_children_ul()).slideDown("fast");
3100 }
3101 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
3102 node.expanded = true;
3103
3104 // perform api level toggling because new nodes are new to the DOM
3105 var selectedLevel = $("#apiLevelSelector option:selected").val();
3106 toggleVisisbleApis(selectedLevel, "#side-nav");
3107 }
3108}
3109
3110function get_node(me, mom)
3111{
3112 mom.children_visited = true;
3113 for (var i in mom.children_data) {
3114 var node_data = mom.children_data[i];
3115 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
3116 node_data[2], node_data[3]);
3117 }
3118}
3119
3120function this_page_relative(toroot)
3121{
3122 var full = document.location.pathname;
3123 var file = "";
3124 if (toroot.substr(0, 1) == "/") {
3125 if (full.substr(0, toroot.length) == toroot) {
3126 return full.substr(toroot.length);
3127 } else {
3128 // the file isn't under toroot. Fail.
3129 return null;
3130 }
3131 } else {
3132 if (toroot != "./") {
3133 toroot = "./" + toroot;
3134 }
3135 do {
3136 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3137 var pos = full.lastIndexOf("/");
3138 file = full.substr(pos) + file;
3139 full = full.substr(0, pos);
3140 toroot = toroot.substr(0, toroot.length-3);
3141 }
3142 } while (toroot != "" && toroot != "/");
3143 return file.substr(1);
3144 }
3145}
3146
3147function find_page(url, data)
3148{
3149 var nodes = data;
3150 var result = null;
3151 for (var i in nodes) {
3152 var d = nodes[i];
3153 if (d[1] == url) {
3154 return new Array(i);
3155 }
3156 else if (d[2] != null) {
3157 result = find_page(url, d[2]);
3158 if (result != null) {
3159 return (new Array(i).concat(result));
3160 }
3161 }
3162 }
3163 return null;
3164}
3165
Scott Mainf5089842012-08-14 16:31:07 -07003166function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07003167 // load json file for navtree data
3168 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3169 // when the file is loaded, initialize the tree
3170 if(jqxhr.status === 200) {
3171 init_navtree("tree-list", toroot, NAVTREE_DATA);
3172 }
3173 });
Scott Main3b90aff2013-08-01 18:09:35 -07003174
Scott Mainf5089842012-08-14 16:31:07 -07003175 // perform api level toggling because because the whole tree is new to the DOM
3176 var selectedLevel = $("#apiLevelSelector option:selected").val();
3177 toggleVisisbleApis(selectedLevel, "#side-nav");
3178}
3179
3180function init_navtree(navtree_id, toroot, root_nodes)
3181{
3182 var me = new Object();
3183 me.toroot = toroot;
3184 me.node = new Object();
3185
3186 me.node.li = document.getElementById(navtree_id);
3187 me.node.children_data = root_nodes;
3188 me.node.children = new Array();
3189 me.node.children_ul = document.createElement("ul");
3190 me.node.get_children_ul = function() { return me.node.children_ul; };
3191 //me.node.children_ul.className = "children_ul";
3192 me.node.li.appendChild(me.node.children_ul);
3193 me.node.depth = 0;
3194
3195 get_node(me, me.node);
3196
3197 me.this_page = this_page_relative(toroot);
3198 me.breadcrumbs = find_page(me.this_page, root_nodes);
3199 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3200 var mom = me.node;
3201 for (var i in me.breadcrumbs) {
3202 var j = me.breadcrumbs[i];
3203 mom = mom.children[j];
3204 expand_node(me, mom);
3205 }
3206 mom.label_div.className = mom.label_div.className + " selected";
3207 addLoadEvent(function() {
3208 scrollIntoView("nav-tree");
3209 });
3210 }
3211}
3212
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003213
3214
3215
3216
3217
3218
3219
Robert Lyd2dd6e52012-11-29 21:28:48 -08003220/* TODO: eliminate redundancy with non-google functions */
3221function init_google_navtree(navtree_id, toroot, root_nodes)
3222{
3223 var me = new Object();
3224 me.toroot = toroot;
3225 me.node = new Object();
3226
3227 me.node.li = document.getElementById(navtree_id);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003228 if (!me.node.li) {
3229 return;
3230 }
3231
Robert Lyd2dd6e52012-11-29 21:28:48 -08003232 me.node.children_data = root_nodes;
3233 me.node.children = new Array();
3234 me.node.children_ul = document.createElement("ul");
3235 me.node.get_children_ul = function() { return me.node.children_ul; };
3236 //me.node.children_ul.className = "children_ul";
3237 me.node.li.appendChild(me.node.children_ul);
3238 me.node.depth = 0;
3239
3240 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003241}
3242
3243function new_google_node(me, mom, text, link, children_data, api_level)
3244{
3245 var node = new Object();
3246 var child;
3247 node.children = Array();
3248 node.children_data = children_data;
3249 node.depth = mom.depth + 1;
3250 node.get_children_ul = function() {
3251 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003252 node.children_ul = document.createElement("ul");
3253 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003254 node.li.appendChild(node.children_ul);
3255 }
3256 return node.children_ul;
3257 };
3258 node.li = document.createElement("li");
3259
3260 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003261
3262
Robert Lyd2dd6e52012-11-29 21:28:48 -08003263 if(link) {
3264 child = document.createElement("a");
3265
3266 }
3267 else {
3268 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003269 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003270
3271 }
3272 if (children_data != null) {
3273 node.li.className="nav-section";
3274 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003275 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003276 node.li.appendChild(node.label_div);
3277 get_google_node(me, node);
3278 node.label_div.appendChild(child);
3279 }
3280 else {
3281 node.li.appendChild(child);
3282 }
3283 if(link) {
3284 child.href = me.toroot + link;
3285 }
3286 node.label = document.createTextNode(text);
3287 child.appendChild(node.label);
3288
3289 node.children_ul = null;
3290
3291 return node;
3292}
3293
3294function get_google_node(me, mom)
3295{
3296 mom.children_visited = true;
3297 var linkText;
3298 for (var i in mom.children_data) {
3299 var node_data = mom.children_data[i];
3300 linkText = node_data[0];
3301
3302 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3303 linkText = linkText.substr(19, linkText.length);
3304 }
3305 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3306 node_data[2], node_data[3]);
3307 }
3308}
Scott Mainad08f072013-08-20 16:49:57 -07003309
3310
3311
3312
3313
3314
3315/****** NEW version of script to build google and sample navs dynamically ******/
3316// TODO: update Google reference docs to tolerate this new implementation
3317
Scott Maine624b3f2013-09-12 12:56:41 -07003318var NODE_NAME = 0;
3319var NODE_HREF = 1;
3320var NODE_GROUP = 2;
3321var NODE_TAGS = 3;
3322var NODE_CHILDREN = 4;
3323
Scott Mainad08f072013-08-20 16:49:57 -07003324function init_google_navtree2(navtree_id, data)
3325{
3326 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003327 for (var i in data) {
3328 var node_data = data[i];
3329 $containerUl.append(new_google_node2(node_data));
3330 }
3331
Scott Main70557ee2013-10-30 14:47:40 -07003332 // Make all third-generation list items 'sticky' to prevent them from collapsing
3333 $containerUl.find('li li li.nav-section').addClass('sticky');
3334
Scott Mainad08f072013-08-20 16:49:57 -07003335 initExpandableNavItems("#"+navtree_id);
3336}
3337
3338function new_google_node2(node_data)
3339{
Scott Maine624b3f2013-09-12 12:56:41 -07003340 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003341 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3342 linkText = linkText.substr(19, linkText.length);
3343 }
3344 var $li = $('<li>');
3345 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003346 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003347 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3348 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003349 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003350 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3351 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003352 }
3353 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003354 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003355 $li.addClass("nav-section");
3356 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003357 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003358
Scott Maine624b3f2013-09-12 12:56:41 -07003359 for (var i in node_data[NODE_CHILDREN]) {
3360 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003361 $childUl.append(new_google_node2(child_node_data));
3362 }
3363 $li.append($childUl);
3364 }
3365 $li.prepend($a);
3366
3367 return $li;
3368}
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
Robert Lyd2dd6e52012-11-29 21:28:48 -08003380function showGoogleRefTree() {
3381 init_default_google_navtree(toRoot);
3382 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003383}
3384
3385function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003386 // load json file for navtree data
3387 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3388 // when the file is loaded, initialize the tree
3389 if(jqxhr.status === 200) {
3390 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3391 highlightSidenav();
3392 resizeNav();
3393 }
3394 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003395}
3396
3397function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003398 // load json file for navtree data
3399 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3400 // when the file is loaded, initialize the tree
3401 if(jqxhr.status === 200) {
3402 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3403 highlightSidenav();
3404 resizeNav();
3405 }
3406 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003407}
3408
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003409function showSamplesRefTree() {
3410 init_default_samples_navtree(toRoot);
3411}
3412
3413function init_default_samples_navtree(toroot) {
3414 // load json file for navtree data
3415 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3416 // when the file is loaded, initialize the tree
3417 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003418 // hack to remove the "about the samples" link then put it back in
3419 // after we nuke the list to remove the dummy static list of samples
3420 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3421 $("#nav.samples-nav").empty();
3422 $("#nav.samples-nav").append($firstLi);
3423
Scott Mainad08f072013-08-20 16:49:57 -07003424 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003425 highlightSidenav();
3426 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003427 if ($("#jd-content #samples").length) {
3428 showSamples();
3429 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003430 }
3431 });
3432}
3433
Scott Mainf5089842012-08-14 16:31:07 -07003434/* TOGGLE INHERITED MEMBERS */
3435
3436/* Toggle an inherited class (arrow toggle)
3437 * @param linkObj The link that was clicked.
3438 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3439 * 'null' to simply toggle.
3440 */
3441function toggleInherited(linkObj, expand) {
3442 var base = linkObj.getAttribute("id");
3443 var list = document.getElementById(base + "-list");
3444 var summary = document.getElementById(base + "-summary");
3445 var trigger = document.getElementById(base + "-trigger");
3446 var a = $(linkObj);
3447 if ( (expand == null && a.hasClass("closed")) || expand ) {
3448 list.style.display = "none";
3449 summary.style.display = "block";
smain@google.comc0401872016-03-16 11:48:23 -07003450 trigger.src = toRoot + "assets/images/styles/disclosure_up.png";
Scott Mainf5089842012-08-14 16:31:07 -07003451 a.removeClass("closed");
3452 a.addClass("opened");
3453 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3454 list.style.display = "block";
3455 summary.style.display = "none";
smain@google.comc0401872016-03-16 11:48:23 -07003456 trigger.src = toRoot + "assets/images/styles/disclosure_down.png";
Scott Mainf5089842012-08-14 16:31:07 -07003457 a.removeClass("opened");
3458 a.addClass("closed");
3459 }
3460 return false;
3461}
3462
3463/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3464 * @param linkObj The link that was clicked.
3465 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3466 * 'null' to simply toggle.
3467 */
3468function toggleAllInherited(linkObj, expand) {
3469 var a = $(linkObj);
3470 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3471 var expandos = $(".jd-expando-trigger", table);
3472 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3473 expandos.each(function(i) {
3474 toggleInherited(this, true);
3475 });
3476 a.text("[Collapse]");
3477 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3478 expandos.each(function(i) {
3479 toggleInherited(this, false);
3480 });
3481 a.text("[Expand]");
3482 }
3483 return false;
3484}
3485
3486/* Toggle all inherited members in the class (link in the class title)
3487 */
3488function toggleAllClassInherited() {
3489 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3490 var toggles = $(".toggle-all", $("#body-content"));
3491 if (a.text() == "[Expand All]") {
3492 toggles.each(function(i) {
3493 toggleAllInherited(this, true);
3494 });
3495 a.text("[Collapse All]");
3496 } else {
3497 toggles.each(function(i) {
3498 toggleAllInherited(this, false);
3499 });
3500 a.text("[Expand All]");
3501 }
3502 return false;
3503}
3504
3505/* Expand all inherited members in the class. Used when initiating page search */
3506function ensureAllInheritedExpanded() {
3507 var toggles = $(".toggle-all", $("#body-content"));
3508 toggles.each(function(i) {
3509 toggleAllInherited(this, true);
3510 });
3511 $("#toggleAllClassInherited").text("[Collapse All]");
3512}
3513
3514
3515/* HANDLE KEY EVENTS
3516 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3517 */
3518var agent = navigator['userAgent'].toLowerCase();
3519var mac = agent.indexOf("macintosh") != -1;
3520
3521$(document).keydown( function(e) {
3522var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3523 if (control && e.which == 70) { // 70 is "F"
3524 ensureAllInheritedExpanded();
3525 }
3526});
Scott Main498d7102013-08-21 15:47:38 -07003527
3528
3529
3530
3531
3532
3533/* On-demand functions */
3534
3535/** Move sample code line numbers out of PRE block and into non-copyable column */
3536function initCodeLineNumbers() {
3537 var numbers = $("#codesample-block a.number");
3538 if (numbers.length) {
3539 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3540 }
3541
3542 $(document).ready(function() {
3543 // select entire line when clicked
3544 $("span.code-line").click(function() {
3545 if (!shifted) {
3546 selectText(this);
3547 }
3548 });
3549 // invoke line link on double click
3550 $(".code-line").dblclick(function() {
3551 document.location.hash = $(this).attr('id');
3552 });
3553 // highlight the line when hovering on the number
3554 $("#codesample-line-numbers a.number").mouseover(function() {
3555 var id = $(this).attr('href');
3556 $(id).css('background','#e7e7e7');
3557 });
3558 $("#codesample-line-numbers a.number").mouseout(function() {
3559 var id = $(this).attr('href');
3560 $(id).css('background','none');
3561 });
3562 });
3563}
3564
3565// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3566var shifted = false;
3567$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3568
3569// courtesy of jasonedelman.com
3570function selectText(element) {
3571 var doc = document
3572 , range, selection
3573 ;
3574 if (doc.body.createTextRange) { //ms
3575 range = doc.body.createTextRange();
3576 range.moveToElementText(element);
3577 range.select();
3578 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003579 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003580 range = doc.createRange();
3581 range.selectNodeContents(element);
3582 selection.removeAllRanges();
3583 selection.addRange(range);
3584 }
Scott Main285f0772013-08-22 23:22:09 +00003585}
Scott Main03aca9a2013-10-31 07:20:55 -07003586
3587
3588
3589
3590/** Display links and other information about samples that match the
3591 group specified by the URL */
3592function showSamples() {
3593 var group = $("#samples").attr('class');
3594 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3595
3596 var $ul = $("<ul>");
3597 $selectedLi = $("#nav li.selected");
3598
3599 $selectedLi.children("ul").children("li").each(function() {
3600 var $li = $("<li>").append($(this).find("a").first().clone());
3601 $ul.append($li);
3602 });
3603
3604 $("#samples").append($ul);
3605
3606}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003607
3608
3609
3610/* ########################################################## */
3611/* ################### RESOURCE CARDS ##################### */
3612/* ########################################################## */
3613
3614/** Handle resource queries, collections, and grids (sections). Requires
3615 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3616
3617(function() {
3618 // Prevent the same resource from being loaded more than once per page.
3619 var addedPageResources = {};
3620
3621 $(document).ready(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003622 // Need to initialize hero carousel before other sections for dedupe
3623 // to work correctly.
3624 $('[data-carousel-query]').dacCarouselQuery();
3625
Dirk Doughertyc3921652014-05-13 16:55:26 -07003626 $('.resource-widget').each(function() {
3627 initResourceWidget(this);
3628 });
3629
3630 /* Pass the line height to ellipsisfade() to adjust the height of the
3631 text container to show the max number of lines possible, without
3632 showing lines that are cut off. This works with the css ellipsis
3633 classes to fade last text line and apply an ellipsis char. */
3634
Dirk Dougherty29e93432015-05-05 18:17:13 -07003635 //card text currently uses 20px line height.
3636 var lineHeight = 20;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003637 $('.card-info .text').ellipsisfade(lineHeight);
3638 });
3639
3640 /*
3641 Three types of resource layouts:
3642 Flow - Uses a fixed row-height flow using float left style.
3643 Carousel - Single card slideshow all same dimension absolute.
3644 Stack - Uses fixed columns and flexible element height.
3645 */
3646 function initResourceWidget(widget) {
3647 var $widget = $(widget);
3648 var isFlow = $widget.hasClass('resource-flow-layout'),
3649 isCarousel = $widget.hasClass('resource-carousel-layout'),
3650 isStack = $widget.hasClass('resource-stack-layout');
3651
Dirk Dougherty29e93432015-05-05 18:17:13 -07003652 // remove illegal col-x class which is not relevant anymore thanks to responsive styles.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003653 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
Dirk Dougherty29e93432015-05-05 18:17:13 -07003654 if (m && !$widget.is('.cols > *')) {
3655 $widget.removeClass('col-' + m[1]);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003656 }
3657
3658 var opts = {
3659 cardSizes: ($widget.data('cardsizes') || '').split(','),
3660 maxResults: parseInt($widget.data('maxresults') || '100', 10),
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003661 initialResults: $widget.data('initialResults'),
Dirk Doughertyc3921652014-05-13 16:55:26 -07003662 itemsPerPage: $widget.data('itemsperpage'),
3663 sortOrder: $widget.data('sortorder'),
3664 query: $widget.data('query'),
3665 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003666 /* Added by LFL 6/6/14 */
3667 resourceStyle: $widget.data('resourcestyle') || 'card',
3668 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003669 };
3670
3671 // run the search for the set of resources to show
3672
3673 var resources = buildResourceList(opts);
3674
3675 if (isFlow) {
3676 drawResourcesFlowWidget($widget, opts, resources);
3677 } else if (isCarousel) {
3678 drawResourcesCarouselWidget($widget, opts, resources);
3679 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003680 /* Looks like this got removed and is not used, so repurposing for the
3681 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003682 Modified by LFL 6/6/14
3683 */
3684 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003685 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003686 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003687 }
3688 }
3689
3690 /* Initializes a Resource Carousel Widget */
3691 function drawResourcesCarouselWidget($widget, opts, resources) {
3692 $widget.empty();
Dirk Dougherty29e93432015-05-05 18:17:13 -07003693 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003694
3695 $widget.addClass('resource-card slideshow-container')
3696 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3697 .append($('<a>').addClass('slideshow-next').text('Next'));
3698
3699 var css = { 'width': $widget.width() + 'px',
3700 'height': $widget.height() + 'px' };
3701
3702 var $ul = $('<ul>');
3703
3704 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003705 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003706 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003707 .decorateResourceCard(resources[i],plusone);
3708
3709 $('<li>').css(css)
3710 .append($card)
3711 .appendTo($ul);
3712 }
3713
3714 $('<div>').addClass('frame')
3715 .append($ul)
3716 .appendTo($widget);
3717
3718 $widget.dacSlideshow({
3719 auto: true,
3720 btnPrev: '.slideshow-prev',
3721 btnNext: '.slideshow-next'
3722 });
3723 };
3724
Robert Lye7eeb402014-06-03 19:35:24 -07003725 /* Initializes a Resource Card Stack Widget (column-based layout)
3726 Modified by LFL 6/6/14
3727 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003728 function drawResourcesStackWidget($widget, opts, resources, sections) {
3729 // Don't empty widget, grab all items inside since they will be the first
3730 // items stacked, followed by the resource query
Dirk Dougherty29e93432015-05-05 18:17:13 -07003731 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003732 var cards = $widget.find('.resource-card').detach().toArray();
3733 var numStacks = opts.numStacks || 1;
3734 var $stacks = [];
3735 var urlString;
3736
3737 for (var i = 0; i < numStacks; ++i) {
3738 $stacks[i] = $('<div>').addClass('resource-card-stack')
3739 .appendTo($widget);
3740 }
3741
3742 var sectionResources = [];
3743
3744 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003745 if (sections) {
3746 for (var i = 0; i < sections.length; ++i) {
3747 if (!sections[i].sections || !sections[i].sections.length) {
3748 // Render it as a resource card
3749 sectionResources.push(
3750 $('<a>')
3751 .addClass('resource-card section-card')
3752 .attr('href', cleanUrl(sections[i].resource.url))
3753 .decorateResourceCard(sections[i].resource,plusone)[0]
3754 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003755
Robert Lye7eeb402014-06-03 19:35:24 -07003756 } else {
3757 cards.push(
3758 $('<div>')
3759 .addClass('resource-card section-card-menu')
3760 .decorateResourceSection(sections[i],plusone)[0]
3761 );
3762 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003763 }
3764 }
3765
3766 cards = cards.concat(sectionResources);
3767
3768 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003769 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003770
Robert Lye7eeb402014-06-03 19:35:24 -07003771 if (opts.resourceStyle.indexOf('related') > -1) {
3772 $card.addClass('related-card');
3773 }
smain@google.com95948b82014-06-16 19:24:25 -07003774
Dirk Doughertyc3921652014-05-13 16:55:26 -07003775 cards.push($card[0]);
3776 }
3777
Robert Lye7eeb402014-06-03 19:35:24 -07003778 if (opts.stackSort != 'false') {
3779 for (var i = 0; i < cards.length; ++i) {
3780 // Find the stack with the shortest height, but give preference to
3781 // left to right order.
3782 var minHeight = $stacks[0].height();
3783 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003784
Robert Lye7eeb402014-06-03 19:35:24 -07003785 for (var j = 1; j < numStacks; ++j) {
3786 var height = $stacks[j].height();
3787 if (height < minHeight - 45) {
3788 minHeight = height;
3789 minIndex = j;
3790 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003791 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003792
Robert Lye7eeb402014-06-03 19:35:24 -07003793 $stacks[minIndex].append($(cards[i]));
3794 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003795 }
3796
3797 };
smain@google.com95948b82014-06-16 19:24:25 -07003798
3799 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003800 Create a resource card using the given resource object and a list of html
3801 configured options. Returns a jquery object containing the element.
3802 */
smain@google.com95948b82014-06-16 19:24:25 -07003803 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003804 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003805
Robert Lye7eeb402014-06-03 19:35:24 -07003806 // The difference here is that generic cards are not entirely clickable
3807 // so its a div instead of an a tag, also the generic one is not given
3808 // the resource-card class so it appears with a transparent background
3809 // and can be styled in whatever way the css setup.
3810 if (opts.resourceStyle == 'generic') {
3811 $el = $('<div>')
3812 .addClass('resource')
3813 .attr('href', cleanUrl(resource.url))
3814 .decorateResource(resource, opts);
3815 } else {
3816 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003817
Robert Lye7eeb402014-06-03 19:35:24 -07003818 $el = $('<a>')
3819 .addClass(cls)
3820 .attr('href', cleanUrl(resource.url))
3821 .decorateResourceCard(resource, plusone);
3822 }
smain@google.com95948b82014-06-16 19:24:25 -07003823
Robert Lye7eeb402014-06-03 19:35:24 -07003824 return $el;
3825 }
Quddus Chong2cb2f682015-09-04 14:45:46 -07003826
Dirk Dougherty29e93432015-05-05 18:17:13 -07003827 function createResponsiveFlowColumn(cardSize) {
3828 var cardWidth = parseInt(cardSize.match(/(\d+)/)[1], 10);
3829 var column = $('<div>').addClass('col-' + (cardWidth / 3) + 'of6');
3830 if (cardWidth < 9) {
3831 column.addClass('col-tablet-1of2');
3832 } else if (cardWidth > 9 && cardWidth < 18) {
3833 column.addClass('col-tablet-1of1');
3834 }
3835 if (cardWidth < 18) {
3836 column.addClass('col-mobile-1of1')
3837 }
3838 return column;
3839 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003840
3841 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3842 function drawResourcesFlowWidget($widget, opts, resources) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003843 $widget.empty().addClass('cols');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003844 var cardSizes = opts.cardSizes || ['6x6'];
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003845 var initialResults = opts.initialResults || resources.length;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003846 var i = 0, j = 0;
Dirk Dougherty29e93432015-05-05 18:17:13 -07003847 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003848 var cardParent = $widget;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003849
3850 while (i < resources.length) {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003851
3852 if (i === initialResults && initialResults < resources.length) {
3853 // Toggle remaining cards
3854 cardParent = $('<div class="dac-toggle-content clearfix">').appendTo($widget);
3855 $widget.addClass('dac-toggle');
3856 $('<div class="col-1of1 dac-section-links dac-text-center">')
3857 .append(
3858 $('<div class="dac-section-link" data-toggle="section">')
3859 .append('<span class="dac-toggle-expand">More<i class="dac-sprite dac-auto-unfold-more"></i></span>')
3860 .append('<span class="dac-toggle-collapse">Less<i class="dac-sprite dac-auto-unfold-less"></i></span>')
3861 )
3862 .appendTo($widget)
3863 }
3864
Dirk Doughertyc3921652014-05-13 16:55:26 -07003865 var cardSize = cardSizes[j++ % cardSizes.length];
3866 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Quddus Chong2cb2f682015-09-04 14:45:46 -07003867
Dirk Doughertycbe032f2015-05-22 11:41:40 -07003868 var column = createResponsiveFlowColumn(cardSize).appendTo(cardParent);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003869
3870 // A stack has a third dimension which is the number of stacked items
3871 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3872 var stackCount = 0;
3873 var $stackDiv = null;
3874
3875 if (isStack) {
3876 // Create a stack container which should have the dimensions defined
3877 // by the product of the items inside.
3878 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
Dirk Dougherty29e93432015-05-05 18:17:13 -07003879 + 'x' + isStack[2] * isStack[3]) .appendTo(column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003880 }
3881
3882 // Build each stack item or just a single item
3883 do {
3884 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003885
Robert Lye7eeb402014-06-03 19:35:24 -07003886 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003887
3888 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003889 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003890
Dirk Doughertyc3921652014-05-13 16:55:26 -07003891 if (isStack) {
3892 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3893 if (++stackCount == parseInt(isStack[3])) {
3894 $card.addClass('resource-card-row-stack-last');
3895 stackCount = 0;
3896 }
3897 } else {
3898 stackCount = 0;
3899 }
3900
Dirk Dougherty29e93432015-05-05 18:17:13 -07003901 $card.appendTo($stackDiv || column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003902
3903 } while (++i < resources.length && stackCount > 0);
3904 }
3905 }
3906
3907 /* Build a site map of resources using a section as a root. */
3908 function buildSectionList(opts) {
3909 if (opts.section && SECTION_BY_ID[opts.section]) {
3910 return SECTION_BY_ID[opts.section].sections || [];
3911 }
3912 return [];
3913 }
3914
3915 function buildResourceList(opts) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003916 return $.queryResources(opts);
3917 }
3918
3919 $.queryResources = function(opts) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003920 var maxResults = opts.maxResults || 100;
3921
3922 var query = opts.query || '';
3923 var expressions = parseResourceQuery(query);
3924 var addedResourceIndices = {};
3925 var results = [];
3926
3927 for (var i = 0; i < expressions.length; i++) {
3928 var clauses = expressions[i];
3929
3930 // build initial set of resources from first clause
3931 var firstClause = clauses[0];
3932 var resources = [];
3933 switch (firstClause.attr) {
3934 case 'type':
3935 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3936 break;
3937 case 'lang':
3938 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3939 break;
3940 case 'tag':
3941 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3942 break;
3943 case 'collection':
3944 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3945 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3946 break;
3947 case 'section':
3948 var urls = SITE_MAP[firstClause.value].sections || [];
3949 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3950 break;
3951 }
3952 // console.log(firstClause.attr + ':' + firstClause.value);
3953 resources = resources || [];
3954
3955 // use additional clauses to filter corpus
3956 if (clauses.length > 1) {
3957 var otherClauses = clauses.slice(1);
3958 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3959 }
3960
3961 // filter out resources already added
3962 if (i > 1) {
3963 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3964 }
3965
3966 // add to list of already added indices
3967 for (var j = 0; j < resources.length; j++) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003968 if (resources[j]) {
3969 addedResourceIndices[resources[j].index] = 1;
3970 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003971 }
3972
3973 // concat to final results list
3974 results = results.concat(resources);
3975 }
3976
3977 if (opts.sortOrder && results.length) {
3978 var attr = opts.sortOrder;
3979
3980 if (opts.sortOrder == 'random') {
3981 var i = results.length, j, temp;
3982 while (--i) {
3983 j = Math.floor(Math.random() * (i + 1));
3984 temp = results[i];
3985 results[i] = results[j];
3986 results[j] = temp;
3987 }
3988 } else {
3989 var desc = attr.charAt(0) == '-';
3990 if (desc) {
3991 attr = attr.substring(1);
3992 }
3993 results = results.sort(function(x,y) {
3994 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3995 });
3996 }
3997 }
3998
3999 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
4000 results = results.slice(0, maxResults);
4001
4002 for (var j = 0; j < results.length; ++j) {
4003 addedPageResources[results[j].index] = 1;
4004 }
4005
4006 return results;
4007 }
4008
4009
4010 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
4011 return function(resource) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004012 return resource && !addedResourceIndices[resource.index];
Dirk Doughertyc3921652014-05-13 16:55:26 -07004013 };
4014 }
4015
4016
4017 function getResourceMatchesClausesFilter(clauses) {
4018 return function(resource) {
4019 return doesResourceMatchClauses(resource, clauses);
4020 };
4021 }
4022
4023
4024 function doesResourceMatchClauses(resource, clauses) {
4025 for (var i = 0; i < clauses.length; i++) {
4026 var map;
4027 switch (clauses[i].attr) {
4028 case 'type':
4029 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
4030 break;
4031 case 'lang':
4032 map = IS_RESOURCE_IN_LANG[clauses[i].value];
4033 break;
4034 case 'tag':
4035 map = IS_RESOURCE_TAGGED[clauses[i].value];
4036 break;
4037 }
4038
4039 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
4040 return clauses[i].negative;
4041 }
4042 }
4043 return true;
4044 }
smain@google.com95948b82014-06-16 19:24:25 -07004045
Robert Lye7eeb402014-06-03 19:35:24 -07004046 function cleanUrl(url)
4047 {
4048 if (url && url.indexOf('//') === -1) {
4049 url = toRoot + url;
4050 }
smain@google.com95948b82014-06-16 19:24:25 -07004051
Robert Lye7eeb402014-06-03 19:35:24 -07004052 return url;
4053 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004054
4055
4056 function parseResourceQuery(query) {
4057 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
4058 var expressions = [];
4059 var expressionStrs = query.split(',') || [];
4060 for (var i = 0; i < expressionStrs.length; i++) {
4061 var expr = expressionStrs[i] || '';
4062
4063 // Break expression into clauses (clause e.g. 'tag:foo')
4064 var clauses = [];
4065 var clauseStrs = expr.split(/(?=[\+\-])/);
4066 for (var j = 0; j < clauseStrs.length; j++) {
4067 var clauseStr = clauseStrs[j] || '';
4068
4069 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
4070 var parts = clauseStr.split(':');
4071 var clause = {};
4072
4073 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
4074 if (clause.attr) {
4075 if (clause.attr.charAt(0) == '+') {
4076 clause.attr = clause.attr.substring(1);
4077 } else if (clause.attr.charAt(0) == '-') {
4078 clause.negative = true;
4079 clause.attr = clause.attr.substring(1);
4080 }
4081 }
4082
4083 if (parts.length > 1) {
4084 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
4085 }
4086
4087 clauses.push(clause);
4088 }
4089
4090 if (!clauses.length) {
4091 continue;
4092 }
4093
4094 expressions.push(clauses);
4095 }
4096
4097 return expressions;
4098 }
4099})();
4100
4101(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07004102
smain@google.com95948b82014-06-16 19:24:25 -07004103 /*
Robert Lye7eeb402014-06-03 19:35:24 -07004104 Utility method for creating dom for the description area of a card.
4105 Used in decorateResourceCard and decorateResource.
4106 */
4107 function buildResourceCardDescription(resource, plusone) {
4108 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07004109
Robert Lye7eeb402014-06-03 19:35:24 -07004110 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07004111
Robert Lye7eeb402014-06-03 19:35:24 -07004112 if (resource.cta) {
4113 $description.append($('<a>').addClass('cta').html(resource.cta));
4114 }
smain@google.com95948b82014-06-16 19:24:25 -07004115
Robert Lye7eeb402014-06-03 19:35:24 -07004116 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07004117 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07004118 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004119
Robert Lye7eeb402014-06-03 19:35:24 -07004120 $description.append($('<div>').addClass('util')
4121 .append($('<div>').addClass('g-plusone')
4122 .attr('data-size', 'small')
4123 .attr('data-align', 'right')
4124 .attr('data-href', plusurl)));
4125 }
smain@google.com95948b82014-06-16 19:24:25 -07004126
Robert Lye7eeb402014-06-03 19:35:24 -07004127 return $description;
4128 }
smain@google.com95948b82014-06-16 19:24:25 -07004129
4130
Dirk Doughertyc3921652014-05-13 16:55:26 -07004131 /* Simple jquery function to create dom for a standard resource card */
4132 $.fn.decorateResourceCard = function(resource,plusone) {
4133 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07004134 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004135 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07004136
Robert Lye7eeb402014-06-03 19:35:24 -07004137 if (imgUrl.indexOf('//') === -1) {
4138 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07004139 }
Robert Lye7eeb402014-06-03 19:35:24 -07004140
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004141 if (resource.type === 'youtube') {
4142 $('<div>').addClass('play-button')
4143 .append($('<i class="dac-sprite dac-play-white">'))
4144 .appendTo(this);
4145 }
4146
Robert Lye7eeb402014-06-03 19:35:24 -07004147 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07004148 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07004149 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07004150 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07004151
Robert Lye7eeb402014-06-03 19:35:24 -07004152 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
4153 .append($('<div>').addClass('section').text(section))
4154 .append($('<div>').addClass('title').html(resource.title))
4155 .append(buildResourceCardDescription(resource, plusone))
4156 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07004157
4158 return this;
4159 };
4160
4161 /* Simple jquery function to create dom for a resource section card (menu) */
4162 $.fn.decorateResourceSection = function(section,plusone) {
4163 var resource = section.resource;
4164 //keep url clean for matching and offline mode handling
4165 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
4166 var $base = $('<a>')
4167 .addClass('card-bg')
4168 .attr('href', resource.url)
4169 .append($('<div>').addClass('card-section-icon')
4170 .append($('<div>').addClass('icon'))
4171 .append($('<div>').addClass('section').html(resource.title)))
4172 .appendTo(this);
4173
4174 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
4175
4176 if (section.sections && section.sections.length) {
4177 // Recurse the section sub-tree to find a resource image.
4178 var stack = [section];
4179
4180 while (stack.length) {
4181 if (stack[0].resource.image) {
4182 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4183 break;
4184 }
4185
4186 if (stack[0].sections) {
4187 stack = stack.concat(stack[0].sections);
4188 }
4189
4190 stack.shift();
4191 }
4192
4193 var $ul = $('<ul>')
4194 .appendTo($cardInfo);
4195
4196 var max = section.sections.length > 3 ? 3 : section.sections.length;
4197
4198 for (var i = 0; i < max; ++i) {
4199
4200 var subResource = section.sections[i];
4201 if (!plusone) {
4202 $('<li>')
4203 .append($('<a>').attr('href', subResource.url)
4204 .append($('<div>').addClass('title').html(subResource.title))
4205 .append($('<div>').addClass('description ellipsis')
4206 .append($('<div>').addClass('text').html(subResource.summary))
4207 .append($('<div>').addClass('util'))))
4208 .appendTo($ul);
4209 } else {
4210 $('<li>')
4211 .append($('<a>').attr('href', subResource.url)
4212 .append($('<div>').addClass('title').html(subResource.title))
4213 .append($('<div>').addClass('description ellipsis')
4214 .append($('<div>').addClass('text').html(subResource.summary))
4215 .append($('<div>').addClass('util')
4216 .append($('<div>').addClass('g-plusone')
4217 .attr('data-size', 'small')
4218 .attr('data-align', 'right')
4219 .attr('data-href', resource.url)))))
4220 .appendTo($ul);
4221 }
4222 }
4223
4224 // Add a more row
4225 if (max < section.sections.length) {
4226 $('<li>')
4227 .append($('<a>').attr('href', resource.url)
4228 .append($('<div>')
4229 .addClass('title')
4230 .text('More')))
4231 .appendTo($ul);
4232 }
4233 } else {
4234 // No sub-resources, just render description?
4235 }
4236
4237 return this;
4238 };
smain@google.com95948b82014-06-16 19:24:25 -07004239
4240
4241
4242
Robert Lye7eeb402014-06-03 19:35:24 -07004243 /* Render other types of resource styles that are not cards. */
4244 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07004245 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004246 'assets/images/resource-card-default-android.jpg';
4247 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004248
Robert Lye7eeb402014-06-03 19:35:24 -07004249 if (imgUrl.indexOf('//') === -1) {
4250 imgUrl = toRoot + imgUrl;
4251 }
smain@google.com95948b82014-06-16 19:24:25 -07004252
Robert Lye7eeb402014-06-03 19:35:24 -07004253 if (linkUrl && linkUrl.indexOf('//') === -1) {
4254 linkUrl = toRoot + linkUrl;
4255 }
4256
4257 $(this).append(
4258 $('<div>').addClass('image')
4259 .css('background-image', 'url(' + imgUrl + ')'),
4260 $('<div>').addClass('info').append(
4261 $('<h4>').addClass('title').html(resource.title),
4262 $('<p>').addClass('summary').html(resource.summary),
4263 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4264 )
4265 );
4266
4267 return this;
4268 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004269})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004270
4271
Dirk Doughertyc3921652014-05-13 16:55:26 -07004272/* Calculate the vertical area remaining */
4273(function($) {
4274 $.fn.ellipsisfade= function(lineHeight) {
4275 this.each(function() {
4276 // get element text
4277 var $this = $(this);
4278 var remainingHeight = $this.parent().parent().height();
4279 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004280 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004281 if ($(this).is(":visible")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004282 var h = $(this).outerHeight(true);
smain@google.comcda1a9a2014-06-19 17:07:46 -07004283 remainingHeight = remainingHeight - h;
4284 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004285 });
4286
4287 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4288 $this.parent().css({'height': adjustedRemainingHeight});
4289 $this.css({'height': "auto"});
4290 });
4291
4292 return this;
4293 };
4294}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004295
4296/*
4297 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004298
Robert Lye7eeb402014-06-03 19:35:24 -07004299 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004300 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004301 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004302
Robert Lye7eeb402014-06-03 19:35:24 -07004303 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004304
Robert Lye7eeb402014-06-03 19:35:24 -07004305 <div class="fullscreen-carousel">
4306 <div class="fullscreen-carousel-content">
4307 <!-- content here -->
4308 </div>
4309 <div class="fullscreen-carousel-content">
4310 <!-- content here -->
4311 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004312
Robert Lye7eeb402014-06-03 19:35:24 -07004313 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004314
Robert Lye7eeb402014-06-03 19:35:24 -07004315 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004316
Robert Lye7eeb402014-06-03 19:35:24 -07004317 Control over how the carousel takes over the screen can mostly be defined in
4318 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004319 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004320 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004321 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004322 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004323
Robert Lye7eeb402014-06-03 19:35:24 -07004324 There is limited functionality for having multiple sections since that request
4325 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4326 scroll between multiple content areas.
4327*/
4328
4329(function() {
4330 $(document).ready(function() {
4331 $('.fullscreen-carousel').each(function() {
4332 initWidget(this);
4333 });
4334 });
4335
4336 function initWidget(widget) {
4337 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004338
Robert Lye7eeb402014-06-03 19:35:24 -07004339 var topOffset = $widget.offset().top;
4340 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4341 var maxHeight = 0;
4342 var minHeight = 0;
4343 var $content = $widget.find('.fullscreen-carousel-content');
4344 var $nextArrow = $widget.find('.next-arrow');
4345 var $prevArrow = $widget.find('.prev-arrow');
4346 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004347
Robert Lye7eeb402014-06-03 19:35:24 -07004348 if ($content.length <= 1) {
4349 $nextArrow.hide();
4350 $prevArrow.hide();
4351 } else {
4352 $nextArrow.click(function() {
4353 var index = ($content.index($curSection) + 1);
4354 $curSection.hide();
4355 $curSection = $($content[index >= $content.length ? 0 : index]);
4356 $curSection.show();
4357 });
smain@google.com95948b82014-06-16 19:24:25 -07004358
Robert Lye7eeb402014-06-03 19:35:24 -07004359 $prevArrow.click(function() {
4360 var index = ($content.index($curSection) - 1);
4361 $curSection.hide();
4362 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4363 $curSection.show();
4364 });
4365 }
4366
4367 // Just hide all content sections except first.
4368 $content.each(function(index) {
4369 if ($(this).height() > minHeight) minHeight = $(this).height();
4370 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4371 });
4372
4373 // Register for changes to window size, and trigger.
4374 $(window).resize(resizeWidget);
4375 resizeWidget();
4376
4377 function resizeWidget() {
4378 var height = $(window).height() - topOffset - padBottom;
4379 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004380 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004381 (maxHeight && height > maxHeight ? maxHeight : height));
4382 }
smain@google.com95948b82014-06-16 19:24:25 -07004383 }
Robert Lye7eeb402014-06-03 19:35:24 -07004384})();
4385
4386
4387
4388
4389
4390/*
4391 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004392
Robert Lye7eeb402014-06-03 19:35:24 -07004393 The following allows tab widgets to be installed via the html below. Each
4394 tab content section should have a data-tab attribute matching one of the
4395 nav items'. Also each tab content section should have a width matching the
4396 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004397
Robert Lye7eeb402014-06-03 19:35:24 -07004398 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004399
Robert Lye7eeb402014-06-03 19:35:24 -07004400 <div class="tab-carousel">
4401 <ul class="tab-nav">
4402 <li><a href="#" data-tab="handsets">Handsets</a>
4403 <li><a href="#" data-tab="wearable">Wearable</a>
4404 <li><a href="#" data-tab="tv">TV</a>
4405 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004406
Robert Lye7eeb402014-06-03 19:35:24 -07004407 <div class="tab-carousel-content">
4408 <div data-tab="handsets">
4409 <!--Full width content here-->
4410 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004411
Robert Lye7eeb402014-06-03 19:35:24 -07004412 <div data-tab="wearable">
4413 <!--Full width content here-->
4414 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004415
Robert Lye7eeb402014-06-03 19:35:24 -07004416 <div data-tab="tv">
4417 <!--Full width content here-->
4418 </div>
4419 </div>
4420 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004421
Robert Lye7eeb402014-06-03 19:35:24 -07004422*/
4423(function() {
4424 $(document).ready(function() {
4425 $('.tab-carousel').each(function() {
4426 initWidget(this);
4427 });
4428 });
4429
4430 function initWidget(widget) {
4431 var $widget = $(widget);
4432 var $nav = $widget.find('.tab-nav');
4433 var $anchors = $nav.find('[data-tab]');
4434 var $li = $nav.find('li');
4435 var $contentContainer = $widget.find('.tab-carousel-content');
4436 var $tabs = $contentContainer.find('[data-tab]');
4437 var $curTab = $($tabs[0]); // Current tab is first tab.
4438 var width = $widget.width();
4439
4440 // Setup nav interactivity.
4441 $anchors.click(function(evt) {
4442 evt.preventDefault();
4443 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004444 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004445 });
smain@google.com95948b82014-06-16 19:24:25 -07004446
Robert Lye7eeb402014-06-03 19:35:24 -07004447 // Add highlight for navigation on first item.
4448 var $highlight = $('<div>').addClass('highlight')
4449 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4450 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004451
Robert Lye7eeb402014-06-03 19:35:24 -07004452 // Store height since we will change contents to absolute.
4453 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004454
Robert Lye7eeb402014-06-03 19:35:24 -07004455 // Absolutely position tabs so they're ready for transition.
4456 $tabs.each(function(index) {
4457 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4458 });
smain@google.com95948b82014-06-16 19:24:25 -07004459
Robert Lye7eeb402014-06-03 19:35:24 -07004460 function transitionWidget($toTab) {
4461 if (!$curTab.is($toTab)) {
4462 var curIndex = $tabs.index($curTab[0]);
4463 var toIndex = $tabs.index($toTab[0]);
4464 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004465
Robert Lye7eeb402014-06-03 19:35:24 -07004466 // Animate content sections.
4467 $toTab.css({left:(width * dir) + 'px'});
4468 $curTab.animate({left:(width * -dir) + 'px'});
4469 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004470
Robert Lye7eeb402014-06-03 19:35:24 -07004471 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004472 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004473 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004474
Robert Lye7eeb402014-06-03 19:35:24 -07004475 // Store new current section.
4476 $curTab = $toTab;
4477 }
4478 }
smain@google.com95948b82014-06-16 19:24:25 -07004479 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08004480})();
Dirk Dougherty29e93432015-05-05 18:17:13 -07004481
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004482/**
4483 * Auto TOC
4484 *
4485 * Upgrades h2s on the page to have a rule and be toggle-able on mobile.
4486 */
4487(function($) {
4488 var upgraded = false;
4489 var h2Titles;
4490
4491 function initWidget() {
4492 // add HRs below all H2s (except for a few other h2 variants)
4493 // Consider doing this with css instead.
4494 h2Titles = $('h2').not('#qv h2, #tb h2, .sidebox h2, #devdoc-nav h2, h2.norule');
4495 h2Titles.css({marginBottom:0}).after('<hr/>');
4496
4497 // Exit early if on older browser.
4498 if (!window.matchMedia) {
4499 return;
4500 }
4501
4502 // Only run logic in mobile layout.
4503 var query = window.matchMedia('(max-width: 719px)');
4504 if (query.matches) {
4505 makeTogglable();
4506 } else {
4507 query.addListener(makeTogglable);
4508 }
4509 }
4510
4511 function makeTogglable() {
4512 // Only run this logic once.
4513 if (upgraded) { return; }
4514 upgraded = true;
4515
4516 // Only make content h2s togglable.
4517 var contentTitles = h2Titles.filter('#jd-content *');
4518
4519 // If there are more than 1
4520 if (contentTitles.size() < 2) {
4521 return;
4522 }
4523
4524 contentTitles.each(function() {
4525 // Find all the relevant nodes.
4526 var $title = $(this);
4527 var $hr = $title.next();
4528 var $contents = $hr.nextUntil('h2, .next-docs');
4529 var $section = $($title)
4530 .add($hr)
4531 .add($title.prev('a[name]'))
4532 .add($contents);
4533 var $anchor = $section.first().prev();
4534 var anchorMethod = 'after';
4535 if ($anchor.length === 0) {
4536 $anchor = $title.parent();
4537 anchorMethod = 'prepend';
4538 }
4539
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004540 // Some h2s are in their own container making it pretty hard to find the end, so skip.
4541 if ($contents.length === 0) {
4542 return;
4543 }
4544
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004545 // Remove from DOM before messing with it. DOM is slow!
4546 $section.detach();
4547
4548 // Add mobile-only expand arrows.
4549 $title.prepend('<span class="dac-visible-mobile-inline-block">' +
4550 '<i class="dac-toggle-expand dac-sprite dac-expand-more-black"></i>' +
4551 '<i class="dac-toggle-collapse dac-sprite dac-expand-less-black"></i>' +
4552 '</span>')
4553 .attr('data-toggle', 'section');
4554
4555 // Wrap in magic markup.
4556 $section = $section.wrapAll('<div class="dac-toggle dac-mobile">').parent();
4557 $contents.wrapAll('<div class="dac-toggle-content"><div>'); // extra div used for max-height calculation.
4558
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004559 // Pre-expand section if requested.
4560 if ($title.hasClass('is-expanded')) {
4561 $section.addClass('is-expanded');
4562 }
4563
4564 // Pre-expand section if targetted by hash.
4565 if (location.hash && $section.find(location.hash).length) {
4566 $section.addClass('is-expanded');
4567 }
4568
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004569 // Add it back to the dom.
4570 $anchor[anchorMethod].call($anchor, $section);
4571 });
4572 }
4573
4574 $(function() {
4575 initWidget();
4576 });
4577})(jQuery);
4578
Dirk Dougherty29e93432015-05-05 18:17:13 -07004579(function($) {
4580 'use strict';
4581
4582 /**
4583 * Toggle Floating Label state.
4584 * @param {HTMLElement} el - The DOM element.
4585 * @param options
4586 * @constructor
4587 */
4588 function FloatingLabel(el, options) {
4589 this.el = $(el);
4590 this.options = $.extend({}, FloatingLabel.DEFAULTS_, options);
4591 this.group = this.el.closest('.dac-form-input-group');
4592 this.input = this.group.find('.dac-form-input');
4593
4594 this.checkValue_ = this.checkValue_.bind(this);
4595 this.checkValue_();
4596
4597 this.input.on('focus', function() {
4598 this.group.addClass('dac-focused');
4599 }.bind(this));
4600 this.input.on('blur', function() {
4601 this.group.removeClass('dac-focused');
4602 this.checkValue_();
4603 }.bind(this));
4604 this.input.on('keyup', this.checkValue_);
4605 }
4606
4607 /**
4608 * The label is moved out of the textbox when it has a value.
4609 */
4610 FloatingLabel.prototype.checkValue_ = function() {
4611 if (this.input.val().length) {
4612 this.group.addClass('dac-has-value');
4613 } else {
4614 this.group.removeClass('dac-has-value');
4615 }
4616 };
4617
4618 /**
4619 * jQuery plugin
4620 * @param {object} options - Override default options.
4621 */
4622 $.fn.dacFloatingLabel = function(options) {
4623 return this.each(function() {
4624 new FloatingLabel(this, options);
4625 });
4626 };
4627
4628 $(document).on('ready.aranja', function() {
4629 $('.dac-form-floatlabel').each(function() {
4630 $(this).dacFloatingLabel($(this).data());
4631 });
4632 });
4633})(jQuery);
4634
4635/* global toRoot, CAROUSEL_OVERRIDE */
4636(function($) {
4637 // Ordering matters
4638 var TAG_MAP = [
4639 {from: 'developerstory', to: 'Android Developer Story'},
4640 {from: 'googleplay', to: 'Google Play'}
4641 ];
4642
4643 function DacCarouselQuery(el) {
4644 this.el = $(el);
4645
4646 var opts = this.el.data();
4647 opts.maxResults = parseInt(opts.maxResults || '100', 10);
4648 opts.query = opts.carouselQuery;
4649 var resources = $.queryResources(opts);
4650
4651 this.el.empty();
4652 $(resources).map(function() {
4653 var resource = $.extend({}, this, CAROUSEL_OVERRIDE[this.url]);
4654 var slide = $('<article class="dac-expand dac-hero">');
4655 var image = cleanUrl(resource.heroImage || resource.image);
4656 var fullBleed = image && !resource.heroColor;
4657
4658 // Configure background
4659 slide.css({
4660 backgroundImage: fullBleed ? 'url(' + image + ')' : '',
4661 backgroundColor: resource.heroColor || ''
4662 });
4663
4664 // Should copy be inverted
4665 slide.toggleClass('dac-invert', resource.heroInvert || fullBleed);
4666 slide.toggleClass('dac-darken', fullBleed);
4667
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004668 // Should be clickable
4669 slide.append($('<a class="dac-hero-carousel-action">').attr('href', cleanUrl(resource.url)));
4670
Dirk Dougherty29e93432015-05-05 18:17:13 -07004671 var cols = $('<div class="cols dac-hero-content">');
4672
4673 // inline image column
4674 var rightCol = $('<div class="col-1of2 col-push-1of2 dac-hero-figure">')
4675 .appendTo(cols);
4676
4677 if (!fullBleed && image) {
4678 rightCol.append($('<img>').attr('src', image));
4679 }
4680
4681 // info column
4682 $('<div class="col-1of2 col-pull-1of2">')
4683 .append($('<div class="dac-hero-tag">').text(formatTag(resource)))
4684 .append($('<h1 class="dac-hero-title">').text(formatTitle(resource)))
4685 .append($('<p class="dac-hero-description">').text(resource.summary))
4686 .append($('<a class="dac-hero-cta">')
4687 .text(formatCTA(resource))
4688 .attr('href', cleanUrl(resource.url))
4689 .prepend($('<span class="dac-sprite dac-auto-chevron">'))
4690 )
4691 .appendTo(cols);
4692
4693 slide.append(cols.wrap('<div class="wrap">').parent());
4694 return slide[0];
4695 }).prependTo(this.el);
4696
4697 // Pagination element.
4698 this.el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>');
4699
4700 this.el.dacCarousel();
4701 }
4702
4703 function cleanUrl(url) {
4704 if (url && url.indexOf('//') === -1) {
4705 url = toRoot + url;
4706 }
4707 return url;
4708 }
4709
4710 function formatTag(resource) {
4711 // Hmm, need a better more scalable solution for this.
4712 for (var i = 0, mapping; mapping = TAG_MAP[i]; i++) {
4713 if (resource.tags.indexOf(mapping.from) > -1) {
4714 return mapping.to;
4715 }
4716 }
4717 return resource.type;
4718 }
4719
4720 function formatTitle(resource) {
4721 return resource.title.replace(/android developer story: /i, '');
4722 }
4723
4724 function formatCTA(resource) {
4725 return resource.type === 'youtube' ? 'Watch the video' : 'Learn more';
4726 }
4727
4728 // jQuery plugin
4729 $.fn.dacCarouselQuery = function() {
4730 return this.each(function() {
4731 var el = $(this);
4732 var data = el.data('dac.carouselQuery');
4733
4734 if (!data) { el.data('dac.carouselQuery', (data = new DacCarouselQuery(el))); }
4735 });
4736 };
4737
4738 // Data API
4739 $(function() {
4740 $('[data-carousel-query]').dacCarouselQuery();
4741 });
4742})(jQuery);
4743
4744(function($) {
4745 /**
4746 * A CSS based carousel, inspired by SequenceJS.
4747 * @param {jQuery} el
4748 * @param {object} options
4749 * @constructor
4750 */
4751 function DacCarousel(el, options) {
4752 this.el = $(el);
4753 this.options = options = $.extend({}, DacCarousel.OPTIONS, this.el.data(), options || {});
4754 this.frames = this.el.find(options.frameSelector);
4755 this.count = this.frames.size();
4756 this.current = options.start;
4757
4758 this.initPagination();
4759 this.initEvents();
4760 this.initFrame();
4761 }
4762
4763 DacCarousel.OPTIONS = {
4764 auto: true,
4765 autoTime: 10000,
4766 autoMinTime: 5000,
4767 btnPrev: '[data-carousel-prev]',
4768 btnNext: '[data-carousel-next]',
4769 frameSelector: 'article',
4770 loop: true,
4771 start: 0,
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004772 swipeThreshold: 160,
Dirk Dougherty29e93432015-05-05 18:17:13 -07004773 pagination: '[data-carousel-pagination]'
4774 };
4775
4776 DacCarousel.prototype.initPagination = function() {
4777 this.pagination = $([]);
4778 if (!this.options.pagination) { return; }
4779
4780 var pagination = $('<ul class="dac-pagination">');
4781 var parent = this.el;
4782 if (typeof this.options.pagination === 'string') { parent = this.el.find(this.options.pagination); }
4783
4784 if (this.count > 1) {
4785 for (var i = 0; i < this.count; i++) {
4786 var li = $('<li class="dac-pagination-item">').text(i);
4787 if (i === this.options.start) { li.addClass('active'); }
4788 li.click(this.go.bind(this, i));
4789
4790 pagination.append(li);
4791 }
4792 this.pagination = pagination.children();
4793 parent.append(pagination);
4794 }
4795 };
4796
4797 DacCarousel.prototype.initEvents = function() {
4798 var that = this;
4799
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004800 this.touch = {
4801 start: {x: 0, y: 0},
4802 end: {x: 0, y: 0}
4803 };
4804
4805 this.el.on('touchstart', this.touchstart_.bind(this));
4806 this.el.on('touchend', this.touchend_.bind(this));
4807 this.el.on('touchmove', this.touchmove_.bind(this));
4808
Dirk Dougherty29e93432015-05-05 18:17:13 -07004809 this.el.hover(function() {
4810 that.pauseRotateTimer();
4811 }, function() {
4812 that.startRotateTimer();
4813 });
4814
4815 $(this.options.btnPrev).click(function(e) {
4816 e.preventDefault();
4817 that.prev();
4818 });
4819
4820 $(this.options.btnNext).click(function(e) {
4821 e.preventDefault();
4822 that.next();
4823 });
4824 };
4825
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004826 DacCarousel.prototype.touchstart_ = function(event) {
4827 var t = event.originalEvent.touches[0];
4828 this.touch.start = {x: t.screenX, y: t.screenY};
4829 };
4830
4831 DacCarousel.prototype.touchend_ = function() {
4832 var deltaX = this.touch.end.x - this.touch.start.x;
4833 var deltaY = Math.abs(this.touch.end.y - this.touch.start.y);
4834 var shouldSwipe = (deltaY < Math.abs(deltaX)) && (Math.abs(deltaX) >= this.options.swipeThreshold);
4835
4836 if (shouldSwipe) {
4837 if (deltaX > 0) {
4838 this.prev();
4839 } else {
4840 this.next();
4841 }
4842 }
4843 };
4844
4845 DacCarousel.prototype.touchmove_ = function(event) {
4846 var t = event.originalEvent.touches[0];
4847 this.touch.end = {x: t.screenX, y: t.screenY};
4848 };
4849
Dirk Dougherty29e93432015-05-05 18:17:13 -07004850 DacCarousel.prototype.initFrame = function() {
4851 this.frames.removeClass('active').eq(this.options.start).addClass('active');
4852 };
4853
4854 DacCarousel.prototype.startRotateTimer = function() {
4855 if (!this.options.auto || this.rotateTimer) { return; }
4856 this.rotateTimer = setTimeout(this.next.bind(this), this.options.autoTime);
4857 };
4858
4859 DacCarousel.prototype.pauseRotateTimer = function() {
4860 clearTimeout(this.rotateTimer);
4861 this.rotateTimer = null;
4862 };
4863
4864 DacCarousel.prototype.prev = function() {
4865 this.go(this.current - 1);
4866 };
4867
4868 DacCarousel.prototype.next = function() {
4869 this.go(this.current + 1);
4870 };
4871
4872 DacCarousel.prototype.go = function(next) {
4873 // Figure out what the next slide is.
4874 while (this.count > 0 && next >= this.count) { next -= this.count; }
4875 while (next < 0) { next += this.count; }
4876
4877 // Cancel if we're already on that slide.
4878 if (next === this.current) { return; }
4879
4880 // Prepare next slide.
4881 this.frames.eq(next).removeClass('out');
4882
4883 // Recalculate styles before starting slide transition.
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004884 this.el.resolveStyles();
4885 // Update pagination
4886 this.pagination.removeClass('active').eq(next).addClass('active');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004887
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004888 // Transition out current frame
4889 this.frames.eq(this.current).toggleClass('active out');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004890
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004891 // Transition in a new frame
4892 this.frames.eq(next).toggleClass('active');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004893
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004894 this.current = next;
Dirk Dougherty29e93432015-05-05 18:17:13 -07004895 };
4896
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004897 // Helper which resolves new styles for an element, so it can start transitioning
4898 // from the new values.
4899 $.fn.resolveStyles = function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004900 /*jshint expr:true*/
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004901 this[0] && this[0].offsetTop;
4902 return this;
4903 };
Dirk Dougherty29e93432015-05-05 18:17:13 -07004904
4905 // jQuery plugin
4906 $.fn.dacCarousel = function() {
4907 this.each(function() {
4908 var $el = $(this);
4909 $el.data('dac-carousel', new DacCarousel(this));
4910 });
4911 return this;
4912 };
4913
4914 // Data API
4915 $(function() {
4916 $('[data-carousel]').dacCarousel();
4917 });
4918})(jQuery);
4919
4920(function($) {
4921 'use strict';
4922
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004923 function Modal(el, options) {
4924 this.el = $(el);
4925 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
4926 this.isOpen = false;
4927
4928 this.el.on('click', function(event) {
4929 if (!$.contains($('.dac-modal-window')[0], event.target)) {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004930 return this.el.trigger('modal-close');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004931 }
4932 }.bind(this));
4933
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004934 this.el.on('modal-open', this.open_.bind(this));
4935 this.el.on('modal-close', this.close_.bind(this));
4936 this.el.on('modal-toggle', this.toggle_.bind(this));
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004937 }
4938
4939 Modal.prototype.toggle_ = function() {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004940 this.el.trigger('modal-' + (this.isOpen ? 'close' : 'open'));
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004941 };
4942
4943 Modal.prototype.close_ = function() {
4944 this.el.removeClass('dac-active');
4945 $('body').removeClass('dac-modal-open');
4946 this.isOpen = false;
4947 };
4948
4949 Modal.prototype.open_ = function() {
4950 this.el.addClass('dac-active');
4951 $('body').addClass('dac-modal-open');
4952 this.isOpen = true;
4953 };
4954
Dirk Dougherty29e93432015-05-05 18:17:13 -07004955 function ToggleModal(el, options) {
4956 this.el = $(el);
4957 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004958 this.modal = this.options.modalToggle ? $('[data-modal="' + this.options.modalToggle + '"]') :
4959 this.el.closest('[data-modal]');
4960
Dirk Dougherty29e93432015-05-05 18:17:13 -07004961 this.el.on('click', this.clickHandler_.bind(this));
4962 }
4963
Dirk Dougherty29e93432015-05-05 18:17:13 -07004964 ToggleModal.prototype.clickHandler_ = function(event) {
4965 event.preventDefault();
Dirk Doughertycbe032f2015-05-22 11:41:40 -07004966 this.modal.trigger('modal-toggle');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004967 };
4968
4969 /**
4970 * jQuery plugin
4971 * @param {object} options - Override default options.
4972 */
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004973 $.fn.dacModal = function(options) {
4974 return this.each(function() {
4975 new Modal(this, options);
4976 });
4977 };
4978
Dirk Dougherty29e93432015-05-05 18:17:13 -07004979 $.fn.dacToggleModal = function(options) {
4980 return this.each(function() {
4981 new ToggleModal(this, options);
4982 });
4983 };
4984
4985 /**
4986 * Data Attribute API
4987 */
4988 $(document).on('ready.aranja', function() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004989 $('[data-modal]').each(function() {
4990 $(this).dacModal($(this).data());
4991 });
4992
4993 $('[data-modal-toggle]').each(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004994 $(this).dacToggleModal($(this).data());
4995 });
4996 });
4997})(jQuery);
4998
4999(function($) {
5000 'use strict';
5001
5002 /**
5003 * Toggle the visabilty of the mobile navigation.
5004 * @param {HTMLElement} el - The DOM element.
5005 * @param options
5006 * @constructor
5007 */
5008 function ToggleNav(el, options) {
5009 this.el = $(el);
5010 this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
5011 this.options.target = [this.options.navigation];
5012
5013 if (this.options.body) {this.options.target.push('body')}
5014 if (this.options.dimmer) {this.options.target.push(this.options.dimmer)}
5015
5016 this.el.on('click', this.clickHandler_.bind(this));
5017 }
5018
5019 /**
5020 * ToggleNav Default Settings
5021 * @type {{body: boolean, dimmer: string, navigation: string, toggleClass: string}}
5022 * @private
5023 */
5024 ToggleNav.DEFAULTS_ = {
5025 body: true,
5026 dimmer: '.dac-nav-dimmer',
5027 navigation: '[data-dac-nav]',
5028 toggleClass: 'dac-nav-open'
5029 };
5030
5031 /**
5032 * The actual toggle logic.
5033 * @param event
5034 * @private
5035 */
5036 ToggleNav.prototype.clickHandler_ = function(event) {
5037 event.preventDefault();
5038 $(this.options.target.join(', ')).toggleClass(this.options.toggleClass);
5039 };
5040
5041 /**
5042 * jQuery plugin
5043 * @param {object} options - Override default options.
5044 */
5045 $.fn.dacToggleMobileNav = function(options) {
5046 return this.each(function() {
5047 new ToggleNav(this, options);
5048 });
5049 };
5050
5051 /**
5052 * Data Attribute API
5053 */
5054 $(window).on('load.aranja', function() {
5055 $('[data-dac-toggle-nav]').each(function() {
5056 $(this).dacToggleMobileNav($(this).data());
5057 });
5058 });
5059})(jQuery);
5060
5061(function($) {
5062 'use strict';
5063
5064 /**
5065 * Submit the newsletter form to a Google Form.
5066 * @param {HTMLElement} el - The Form DOM element.
5067 * @constructor
5068 */
5069 function NewsletterForm(el) {
5070 this.el = $(el);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005071 this.form = this.el.find('form');
5072 $('<iframe/>').hide()
5073 .attr('name', 'dac-newsletter-iframe')
5074 .attr('src', '')
5075 .insertBefore(this.form);
5076 this.form.on('submit', this.submitHandler_.bind(this));
Dirk Dougherty29e93432015-05-05 18:17:13 -07005077 }
5078
5079 /**
Dirk Doughertycbe032f2015-05-22 11:41:40 -07005080 * Milliseconds until modal has vanished after modal-close is triggered.
5081 * @type {number}
5082 * @private
5083 */
5084 NewsletterForm.CLOSE_DELAY_ = 300;
5085
5086 /**
5087 * Switch view to display form after close.
5088 * @private
5089 */
5090 NewsletterForm.prototype.closeHandler_ = function() {
5091 setTimeout(function() {
5092 this.el.trigger('swap-reset');
5093 }.bind(this), NewsletterForm.CLOSE_DELAY_);
5094 };
5095
5096 /**
5097 * Reset the modal to initial state.
5098 * @private
5099 */
5100 NewsletterForm.prototype.reset_ = function() {
5101 this.form.trigger('reset');
5102 this.el.one('modal-close', this.closeHandler_.bind(this));
5103 };
5104
5105 /**
5106 * Display a success view on submit.
Dirk Dougherty29e93432015-05-05 18:17:13 -07005107 * @private
5108 */
5109 NewsletterForm.prototype.submitHandler_ = function() {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07005110 this.el.one('swap-complete', this.reset_.bind(this));
5111 this.el.trigger('swap-content');
Dirk Dougherty29e93432015-05-05 18:17:13 -07005112 };
5113
5114 /**
5115 * jQuery plugin
5116 * @param {object} options - Override default options.
5117 */
5118 $.fn.dacNewsletterForm = function(options) {
5119 return this.each(function() {
5120 new NewsletterForm(this, options);
5121 });
5122 };
5123
5124 /**
5125 * Data Attribute API
5126 */
5127 $(document).on('ready.aranja', function() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005128 $('[data-newsletter]').each(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07005129 $(this).dacNewsletterForm();
5130 });
5131 });
5132})(jQuery);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005133
5134(function($) {
5135 'use strict';
5136
5137 /**
5138 * Smoothly scroll to location on current page.
5139 * @param el
5140 * @param options
5141 * @constructor
5142 */
5143 function ScrollButton(el, options) {
5144 this.el = $(el);
5145 this.target = $(this.el.attr('href'));
5146 this.options = $.extend({}, ScrollButton.DEFAULTS_, options);
5147
5148 if (typeof this.options.offset === 'string') {
5149 this.options.offset = $(this.options.offset).height();
5150 }
5151
5152 this.el.on('click', this.clickHandler_.bind(this));
5153 }
5154
5155 /**
5156 * Default options
5157 * @type {{duration: number, easing: string, offset: number, scrollContainer: string}}
5158 * @private
5159 */
5160 ScrollButton.DEFAULTS_ = {
5161 duration: 300,
5162 easing: 'swing',
5163 offset: 0,
5164 scrollContainer: 'html, body'
5165 };
5166
5167 /**
5168 * Scroll logic
5169 * @param event
5170 * @private
5171 */
5172 ScrollButton.prototype.clickHandler_ = function(event) {
5173 if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
5174 return;
5175 }
5176
5177 event.preventDefault();
5178
5179 $(this.options.scrollContainer).animate({
5180 scrollTop: this.target.offset().top - this.options.offset
5181 }, this.options);
5182 };
5183
5184 /**
5185 * jQuery plugin
5186 * @param {object} options - Override default options.
5187 */
5188 $.fn.dacScrollButton = function(options) {
5189 return this.each(function() {
5190 new ScrollButton(this, options);
5191 });
5192 };
5193
5194 /**
5195 * Data Attribute API
5196 */
5197 $(document).on('ready.aranja', function() {
5198 $('[data-scroll-button]').each(function() {
5199 $(this).dacScrollButton($(this).data());
5200 });
5201 });
5202})(jQuery);
5203
5204(function($) {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07005205 'use strict';
5206
5207 /**
5208 * A component that swaps two dynamic height views with an animation.
5209 * Listens for the following events:
5210 * * swap-content: triggers SwapContent.swap_()
5211 * * swap-reset: triggers SwapContent.reset()
5212 * @param el
5213 * @param options
5214 * @constructor
5215 */
5216 function SwapContent(el, options) {
5217 this.el = $(el);
5218 this.options = $.extend({}, SwapContent.DEFAULTS_, options);
5219 this.containers = this.el.find(this.options.container);
5220 this.initiallyActive = this.containers.children('.' + this.options.activeClass).eq(0);
5221 this.el.on('swap-content', this.swap.bind(this));
5222 this.el.on('swap-reset', this.reset.bind(this));
5223 }
5224
5225 /**
5226 * SwapContent's default settings.
5227 * @type {{activeClass: string, container: string, transitionSpeed: number}}
5228 * @private
5229 */
5230 SwapContent.DEFAULTS_ = {
5231 activeClass: 'dac-active',
5232 container: '[data-swap-container]',
5233 transitionSpeed: 500
5234 };
5235
5236 /**
5237 * Returns container's visible height.
5238 * @param container
5239 * @returns {number}
5240 */
5241 SwapContent.prototype.currentHeight = function(container) {
5242 return container.children('.' + this.options.activeClass).outerHeight();
5243 };
5244
5245 /**
5246 * Reset to show initial content
5247 */
5248 SwapContent.prototype.reset = function() {
5249 if (!this.initiallyActive.hasClass(this.initiallyActive)) {
5250 this.containers.children().toggleClass(this.options.activeClass);
5251 }
5252 };
5253
5254 /**
5255 * Complete the swap.
5256 */
5257 SwapContent.prototype.complete = function() {
5258 this.containers.height('auto');
5259 this.containers.trigger('swap-complete');
5260 };
5261
5262 /**
5263 * Perform the swap of content.
5264 */
5265 SwapContent.prototype.swap = function() {
5266 console.log(this.containers);
5267 this.containers.each(function(index, container) {
5268 container = $(container);
5269 container.height(this.currentHeight(container)).children().toggleClass(this.options.activeClass);
5270 container.animate({height: this.currentHeight(container)}, this.options.transitionSpeed,
5271 this.complete.bind(this));
5272 }.bind(this));
5273 };
5274
5275 /**
5276 * jQuery plugin
5277 * @param {object} options - Override default options.
5278 */
5279 $.fn.dacSwapContent = function(options) {
5280 return this.each(function() {
5281 new SwapContent(this, options);
5282 });
5283 };
5284
5285 /**
5286 * Data Attribute API
5287 */
5288 $(document).on('ready.aranja', function() {
5289 $('[data-swap]').each(function() {
5290 $(this).dacSwapContent($(this).data());
5291 });
5292 });
5293})(jQuery);
5294
5295(function($) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005296 function Toggle(el) {
5297 $(el).on('click.dac.togglesection', this.toggle);
5298 }
5299
5300 Toggle.prototype.toggle = function() {
5301 var $this = $(this);
5302
5303 var $parent = getParent($this);
5304 var isExpanded = $parent.hasClass('is-expanded');
5305
5306 transitionMaxHeight($parent.find('.dac-toggle-content'), !isExpanded);
5307 $parent.toggleClass('is-expanded');
5308
5309 return false;
5310 };
5311
5312 function getParent($this) {
5313 var selector = $this.attr('data-target');
5314
5315 if (!selector) {
5316 selector = $this.attr('href');
5317 selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '');
5318 }
5319
5320 var $parent = selector && $(selector);
5321
Dirk Doughertycbe032f2015-05-22 11:41:40 -07005322 $parent = $parent && $parent.length ? $parent : $this.closest('.dac-toggle');
5323
5324 return $parent.length ? $parent : $this.parent();
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005325 }
5326
5327 /**
5328 * Runs a transition of max-height along with responsive styles which hide or expand the element.
5329 * @param $el
5330 * @param visible
5331 */
5332 function transitionMaxHeight($el, visible) {
Dirk Doughertycbe032f2015-05-22 11:41:40 -07005333 var contentHeight = $el.prop('scrollHeight');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07005334 var targetHeight = visible ? contentHeight : 0;
5335 var duration = $el.transitionDuration();
5336
5337 // If we're hiding, first set the maxHeight we're transitioning from.
5338 if (!visible) {
5339 $el.css('maxHeight', contentHeight + 'px')
5340 .resolveStyles();
5341 }
5342
5343 // Transition to new state
5344 $el.css('maxHeight', targetHeight);
5345
5346 // Reset maxHeight to css value after transition.
5347 setTimeout(function() {
5348 $el.css('maxHeight', '');
5349 }, duration);
5350 }
5351
5352 // Utility to get the transition duration for the element.
5353 $.fn.transitionDuration = function() {
5354 var d = $(this).css('transitionDuration') || '0s';
5355
5356 return +(parseFloat(d) * (/ms/.test(d) ? 1 : 1000)).toFixed(0);
5357 };
5358
5359 // jQuery plugin
5360 $.fn.toggleSection = function(option) {
5361 return this.each(function() {
5362 var $this = $(this);
5363 var data = $this.data('dac.togglesection');
5364 if (!data) {$this.data('dac.togglesection', (data = new Toggle(this)));}
5365 if (typeof option === 'string') {data[option].call($this);}
5366 });
5367 };
5368
5369 // Data api
5370 $(document)
5371 .on('click.toggle', '[data-toggle="section"]', Toggle.prototype.toggle);
5372})(jQuery);