blob: 30a29fc855b047f47a74f282b4f9f4ac53e9d442 [file] [log] [blame]
Scott Maine4d8f1b2012-06-21 18:03:05 -07001var classesNav;
2var devdocNav;
3var sidenav;
4var cookie_namespace = 'android_developer';
5var NAV_PREF_TREE = "tree";
6var NAV_PREF_PANELS = "panels";
7var nav_pref;
Scott Maine4d8f1b2012-06-21 18:03:05 -07008var isMobile = false; // true if mobile, so we can adjust some layout
Scott Mainf6145542013-04-01 16:38:11 -07009var mPagePath; // initialized in ready() function
Scott Maine4d8f1b2012-06-21 18:03:05 -070010
Scott Main1b3db112012-07-03 14:06:22 -070011var basePath = getBaseUri(location.pathname);
12var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
Scott Main7e447ed2013-02-19 17:22:37 -080013var GOOGLE_DATA; // combined data for google service apis, used for search suggest
Scott Main3b90aff2013-08-01 18:09:35 -070014
Scott Main25e73002013-03-27 15:24:06 -070015// Ensure that all ajax getScript() requests allow caching
16$.ajaxSetup({
17 cache: true
18});
Scott Maine4d8f1b2012-06-21 18:03:05 -070019
20/****** ON LOAD SET UP STUFF *********/
21
Scott Maine4d8f1b2012-06-21 18:03:05 -070022$(document).ready(function() {
smain@google.com698fff02014-11-20 20:39:33 -080023
Dirk Doughertyb87e3002014-11-18 19:34:34 -080024 // show lang dialog if the URL includes /intl/
25 //if (location.pathname.substring(0,6) == "/intl/") {
26 // var lang = location.pathname.split('/')[2];
27 // if (lang != getLangPref()) {
28 // $("#langMessage a.yes").attr("onclick","changeLangPref('" + lang
29 // + "', true); $('#langMessage').hide(); return false;");
30 // $("#langMessage .lang." + lang).show();
31 // $("#langMessage").show();
32 // }
33 //}
Scott Main7e447ed2013-02-19 17:22:37 -080034
Scott Main0e76e7e2013-03-12 10:24:07 -070035 // load json file for JD doc search suggestions
Scott Main719acb42013-12-05 16:05:09 -080036 $.getScript(toRoot + 'jd_lists_unified.js');
Scott Main7e447ed2013-02-19 17:22:37 -080037 // load json file for Android API search suggestions
38 $.getScript(toRoot + 'reference/lists.js');
39 // load json files for Google services API suggestions
Scott Main9f2971d2013-02-26 13:07:41 -080040 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080041 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
42 if(jqxhr.status === 200) {
Scott Main9f2971d2013-02-26 13:07:41 -080043 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080044 if(jqxhr.status === 200) {
45 // combine GCM and GMS data
46 GOOGLE_DATA = GMS_DATA;
47 var start = GOOGLE_DATA.length;
48 for (var i=0; i<GCM_DATA.length; i++) {
49 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
50 link:GCM_DATA[i].link, type:GCM_DATA[i].type});
51 }
52 }
53 });
54 }
55 });
56
Scott Main0e76e7e2013-03-12 10:24:07 -070057 // setup keyboard listener for search shortcut
58 $('body').keyup(function(event) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -070059 if (event.which == 191 && $(event.target).is(':not(:input)')) {
Scott Main0e76e7e2013-03-12 10:24:07 -070060 $('#search_autocomplete').focus();
61 }
62 });
Scott Main015d6162013-01-29 09:01:52 -080063
Scott Maine4d8f1b2012-06-21 18:03:05 -070064 // init the fullscreen toggle click event
65 $('#nav-swap .fullscreen').click(function(){
66 if ($(this).hasClass('disabled')) {
67 toggleFullscreen(true);
68 } else {
69 toggleFullscreen(false);
70 }
71 });
Scott Main3b90aff2013-08-01 18:09:35 -070072
Scott Maine4d8f1b2012-06-21 18:03:05 -070073 // initialize the divs with custom scrollbars
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -070074 if (window.innerWidth >= 720) {
75 $('.scroll-pane').jScrollPane({verticalGutter: 0});
76 }
Scott Maine4d8f1b2012-06-21 18:03:05 -070077
78 // set up the search close button
Dirk Dougherty29e93432015-05-05 18:17:13 -070079 $('#search-close').click(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -070080 $searchInput = $('#search_autocomplete');
81 $searchInput.attr('value', '');
82 $(this).addClass("hide");
83 $("#search-container").removeClass('active');
84 $("#search_autocomplete").blur();
Scott Main0e76e7e2013-03-12 10:24:07 -070085 search_focus_changed($searchInput.get(), false);
86 hideResults();
Scott Maine4d8f1b2012-06-21 18:03:05 -070087 });
88
Scott Main3b90aff2013-08-01 18:09:35 -070089
Scott Maine4d8f1b2012-06-21 18:03:05 -070090 //Set up search
91 $("#search_autocomplete").focus(function() {
92 $("#search-container").addClass('active');
93 })
94 $("#search-container").mouseover(function() {
95 $("#search-container").addClass('active');
96 $("#search_autocomplete").focus();
97 })
98 $("#search-container").mouseout(function() {
99 if ($("#search_autocomplete").is(":focus")) return;
100 if ($("#search_autocomplete").val() == '') {
101 setTimeout(function(){
102 $("#search-container").removeClass('active');
103 $("#search_autocomplete").blur();
104 },250);
105 }
106 })
107 $("#search_autocomplete").blur(function() {
108 if ($("#search_autocomplete").val() == '') {
109 $("#search-container").removeClass('active');
110 }
111 })
112
Scott Main3b90aff2013-08-01 18:09:35 -0700113
Scott Maine4d8f1b2012-06-21 18:03:05 -0700114 // prep nav expandos
115 var pagePath = document.location.pathname;
116 // account for intl docs by removing the intl/*/ path
117 if (pagePath.indexOf("/intl/") == 0) {
118 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
119 }
Scott Mainac2aef52013-02-12 14:15:23 -0800120
Scott Maine4d8f1b2012-06-21 18:03:05 -0700121 if (pagePath.indexOf(SITE_ROOT) == 0) {
122 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
123 pagePath += 'index.html';
124 }
125 }
126
Scott Main01a25452013-02-12 17:32:27 -0800127 // Need a copy of the pagePath before it gets changed in the next block;
128 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
129 var pagePathOriginal = pagePath;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700130 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
131 // If running locally, SITE_ROOT will be a relative path, so account for that by
132 // finding the relative URL to this page. This will allow us to find links on the page
133 // leading back to this page.
134 var pathParts = pagePath.split('/');
135 var relativePagePathParts = [];
136 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
137 for (var i = 0; i < upDirs; i++) {
138 relativePagePathParts.push('..');
139 }
140 for (var i = 0; i < upDirs; i++) {
141 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
142 }
143 relativePagePathParts.push(pathParts[pathParts.length - 1]);
144 pagePath = relativePagePathParts.join('/');
145 } else {
146 // Otherwise the page path is already an absolute URL
147 }
148
Scott Mainac2aef52013-02-12 14:15:23 -0800149 // Highlight the header tabs...
150 // highlight Design tab
Dirk Dougherty29e93432015-05-05 18:17:13 -0700151 var urlSegments = pagePathOriginal.split('/');
152 var navEl = $(".dac-nav-list");
153 var subNavEl = navEl.find(".dac-nav-secondary");
154 var parentNavEl;
Scott Mainac2aef52013-02-12 14:15:23 -0800155
Dirk Dougherty29e93432015-05-05 18:17:13 -0700156 if ($("body").hasClass("design")) {
157 navEl.find("> li.design > a").addClass("selected");
smain@google.com6040ffa2014-06-13 15:06:23 -0700158 // highlight About tabs
159 } else if ($("body").hasClass("about")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700160 if (urlSegments[1] == "about" || urlSegments[1] == "wear" || urlSegments[1] == "tv" || urlSegments[1] == "auto") {
161 navEl.find("> li.home > a").addClass('has-subnav');
162 subNavEl.find("li." + urlSegments[1] + " > a").addClass("selected");
163 } else {
164 navEl.find("> li.home > a").addClass('selected');
smain@google.com6040ffa2014-06-13 15:06:23 -0700165 }
Dirk Doughertycf7a3b92015-05-21 00:52:33 -0700166
167// highlight NDK tabs
168 } else if ($("body").hasClass("ndk")) {
169 parentNavEl = navEl.find("> li.ndk > a");
170 parentNavEl.addClass('has-subnav');
171 if ($("body").hasClass("guide")) {
172 navEl.find("> li.guides > a").addClass("selected ndk");
173 } else if ($("body").hasClass("reference")) {
174 navEl.find("> li.reference > a").addClass("selected ndk");
175 } else if ($("body").hasClass("samples")) {
176 navEl.find("> li.samples > a").addClass("selected ndk");
177 } else if ($("body").hasClass("downloads")) {
178 navEl.find("> li.downloads > a").addClass("selected ndk");
179 }
180
Scott Mainac2aef52013-02-12 14:15:23 -0800181 // highlight Develop tab
182 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700183 parentNavEl = navEl.find("> li.develop > a");
184 parentNavEl.addClass('has-subnav');
185
Scott Mainac2aef52013-02-12 14:15:23 -0800186 // In Develop docs, also highlight appropriate sub-tab
Dirk Dougherty29e93432015-05-05 18:17:13 -0700187 if (urlSegments[1] == "training") {
188 subNavEl.find("li.training > a").addClass("selected");
189 } else if (urlSegments[1] == "guide") {
190 subNavEl.find("li.guide > a").addClass("selected");
191 } else if (urlSegments[1] == "reference") {
Scott Mainac2aef52013-02-12 14:15:23 -0800192 // If the root is reference, but page is also part of Google Services, select Google
193 if ($("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700194 subNavEl.find("li.google > a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800195 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700196 subNavEl.find("li.reference > a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800197 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700198 } else if ((urlSegments[1] == "tools") || (urlSegments[1] == "sdk")) {
199 subNavEl.find("li.tools > a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800200 } else if ($("body").hasClass("google")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700201 subNavEl.find("li.google > a").addClass("selected");
Dirk Dougherty4f7e5152010-09-16 10:43:40 -0700202 } else if ($("body").hasClass("samples")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700203 subNavEl.find("li.samples > a").addClass("selected");
Dirk Doughertya8bbfca2015-05-14 22:58:28 -0700204 } else if ($("body").hasClass("preview")) {
205 subNavEl.find("li.preview > a").addClass("selected");
Dirk Dougherty29e93432015-05-05 18:17:13 -0700206 } else {
207 parentNavEl.removeClass('has-subnav').addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800208 }
Scott Mainac2aef52013-02-12 14:15:23 -0800209 // highlight Distribute tab
210 } else if ($("body").hasClass("distribute")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700211 parentNavEl = navEl.find("> li.distribute > a");
212 parentNavEl.addClass('has-subnav');
Dirk Doughertyc3921652014-05-13 16:55:26 -0700213
Dirk Dougherty29e93432015-05-05 18:17:13 -0700214 if (urlSegments[2] == "users") {
215 subNavEl.find("li.users > a").addClass("selected");
216 } else if (urlSegments[2] == "engage") {
217 subNavEl.find("li.engage > a").addClass("selected");
218 } else if (urlSegments[2] == "monetize") {
219 subNavEl.find("li.monetize > a").addClass("selected");
220 } else if (urlSegments[2] == "analyze") {
221 subNavEl.find("li.analyze > a").addClass("selected");
222 } else if (urlSegments[2] == "tools") {
223 subNavEl.find("li.disttools > a").addClass("selected");
224 } else if (urlSegments[2] == "stories") {
225 subNavEl.find("li.stories > a").addClass("selected");
226 } else if (urlSegments[2] == "essentials") {
227 subNavEl.find("li.essentials > a").addClass("selected");
228 } else if (urlSegments[2] == "googleplay") {
229 subNavEl.find("li.googleplay > a").addClass("selected");
230 } else {
231 parentNavEl.removeClass('has-subnav').addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700232 }
Scott Mainb16376f2014-05-21 20:35:47 -0700233 }
Scott Mainac2aef52013-02-12 14:15:23 -0800234
Scott Mainf6145542013-04-01 16:38:11 -0700235 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
236 // and highlight the sidenav
237 mPagePath = pagePath;
238 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700239 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800240
Scott Mainf6145542013-04-01 16:38:11 -0700241 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700242 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700243 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700244 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800245 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700246
247 // set up prev links
248 var $prevLink = [];
249 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700250
Scott Maine4d8f1b2012-06-21 18:03:05 -0700251 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
252false; // navigate across topic boundaries only in design docs
253 if ($prevListItem.length) {
smain@google.comc91ecb72014-06-23 10:22:23 -0700254 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Scott Main5a1123e2012-09-26 12:51:28 -0700255 // jump to last topic of previous section
256 $prevLink = $prevListItem.find('a:last');
257 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700258 // jump to previous topic in this section
259 $prevLink = $prevListItem.find('a:eq(0)');
260 }
261 } else {
262 // jump to this section's index page (if it exists)
263 var $parentListItem = $selListItem.parents('li');
264 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700265
Scott Maine4d8f1b2012-06-21 18:03:05 -0700266 // except if cross boundaries aren't allowed, and we're at the top of a section already
267 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700268 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700269 && $selListItem.hasClass('nav-section')) {
270 $prevLink = [];
271 }
272 }
273
Scott Maine4d8f1b2012-06-21 18:03:05 -0700274 // set up next links
275 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700276 var startClass = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700277 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700278
Scott Main1a00f7f2013-10-29 11:11:19 -0700279 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700280 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700281 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700282
283 // if there aren't any children, go to the next section (required for About pages)
284 if($nextLink.length == 0) {
285 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700286 } else if ($('.topic-start-link').length) {
287 // as long as there's a child link and there is a "topic start link" (we're on a landing)
288 // then set the landing page "start link" text to be the first doc title
289 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700290 }
Scott Main3b90aff2013-08-01 18:09:35 -0700291
Scott Main5a1123e2012-09-26 12:51:28 -0700292 // If the selected page has a description, then it's a class or article homepage
293 if ($selListItem.find('a[description]').length) {
294 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700295 startClass = true;
296 }
297 } else {
298 // jump to the next topic in this section (if it exists)
299 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700300 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700301 isCrossingBoundary = true;
302 // no more topics in this section, jump to the first topic in the next section
smain@google.comabf34112014-06-23 11:39:02 -0700303 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Scott Main5a1123e2012-09-26 12:51:28 -0700304 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
305 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700306 if ($nextLink.length == 0) {
307 // if that doesn't work, we're at the end of the list, so disable NEXT link
308 $('.next-page-link').attr('href','').addClass("disabled")
309 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700310 // and completely hide the one in the footer
311 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700312 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700313 }
314 }
315 }
Scott Main5a1123e2012-09-26 12:51:28 -0700316
317 if (startClass) {
318 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
319
Scott Main3b90aff2013-08-01 18:09:35 -0700320 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700321 // then we need to add a bottom border to button
322 if (!$("#tb").length) {
323 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700324 }
Scott Main5a1123e2012-09-26 12:51:28 -0700325 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
326 $('.content-footer.next-class').show();
327 $('.next-page-link').attr('href','')
328 .removeClass("hide").addClass("disabled")
329 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700330 // and completely hide the one in the footer
331 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700332 if ($nextLink.length) {
333 $('.next-class-link').attr('href',$nextLink.attr('href'))
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700334 .removeClass("hide")
335 .append(": " + $nextLink.html());
Scott Main1a00f7f2013-10-29 11:11:19 -0700336 $('.next-class-link').find('.new').empty();
337 }
Scott Main5a1123e2012-09-26 12:51:28 -0700338 } else {
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700339 $('.next-page-link').attr('href', $nextLink.attr('href'))
340 .removeClass("hide");
341 // for the footer link, also add the next page title
342 $('.content-footer .next-page-link').append(": " + $nextLink.html());
Scott Main5a1123e2012-09-26 12:51:28 -0700343 }
344
345 if (!startClass && $prevLink.length) {
346 var prevHref = $prevLink.attr('href');
347 if (prevHref == SITE_ROOT + 'index.html') {
348 // Don't show Previous when it leads to the homepage
349 } else {
350 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
351 }
Scott Main3b90aff2013-08-01 18:09:35 -0700352 }
Scott Main5a1123e2012-09-26 12:51:28 -0700353
Scott Maine4d8f1b2012-06-21 18:03:05 -0700354 }
Scott Main3b90aff2013-08-01 18:09:35 -0700355
356
357
Scott Main5a1123e2012-09-26 12:51:28 -0700358 // Set up the course landing pages for Training with class names and descriptions
359 if ($('body.trainingcourse').length) {
360 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700361
362 // create an array for all the class descriptions
363 var $classDescriptions = new Array($classLinks.length);
364 var lang = getLangPref();
365 $classLinks.each(function(index) {
366 var langDescr = $(this).attr(lang + "-description");
367 if (typeof langDescr !== 'undefined' && langDescr !== false) {
368 // if there's a class description in the selected language, use that
369 $classDescriptions[index] = langDescr;
370 } else {
371 // otherwise, use the default english description
372 $classDescriptions[index] = $(this).attr("description");
373 }
374 });
Scott Main3b90aff2013-08-01 18:09:35 -0700375
Scott Main5a1123e2012-09-26 12:51:28 -0700376 var $olClasses = $('<ol class="class-list"></ol>');
377 var $liClass;
Scott Main5a1123e2012-09-26 12:51:28 -0700378 var $h2Title;
379 var $pSummary;
380 var $olLessons;
381 var $liLesson;
382 $classLinks.each(function(index) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700383 $liClass = $('<li class="clearfix"></li>');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700384 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2 class="norule">' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700385 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700386
Scott Main5a1123e2012-09-26 12:51:28 -0700387 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700388
Scott Main5a1123e2012-09-26 12:51:28 -0700389 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700390
Scott Main5a1123e2012-09-26 12:51:28 -0700391 if ($lessons.length) {
Scott Main5a1123e2012-09-26 12:51:28 -0700392 $lessons.each(function(index) {
393 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
394 });
395 } else {
Scott Main5a1123e2012-09-26 12:51:28 -0700396 $pSummary.addClass('article');
397 }
398
Dirk Dougherty29e93432015-05-05 18:17:13 -0700399 $liClass.append($h2Title).append($pSummary).append($olLessons);
Scott Main5a1123e2012-09-26 12:51:28 -0700400 $olClasses.append($liClass);
401 });
402 $('.jd-descr').append($olClasses);
403 }
404
Scott Maine4d8f1b2012-06-21 18:03:05 -0700405 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700406 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700407
Scott Main3b90aff2013-08-01 18:09:35 -0700408
Scott Maine4d8f1b2012-06-21 18:03:05 -0700409 $(".scroll-pane").scroll(function(event) {
410 event.preventDefault();
411 return false;
412 });
413
414 /* Resize nav height when window height changes */
415 $(window).resize(function() {
416 if ($('#side-nav').length == 0) return;
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700417 setNavBarDimensions(); // do this even if sidenav isn't fixed because it could become fixed
Scott Maine4d8f1b2012-06-21 18:03:05 -0700418 // make sidenav behave when resizing the window and side-scolling is a concern
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700419 updateSideNavDimensions();
420 checkSticky();
421 resizeNav(250);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700422 });
423
Scott Maine4d8f1b2012-06-21 18:03:05 -0700424 if ($('#devdoc-nav').length) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700425 setNavBarDimensions();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700426 }
427
428
Scott Maine4d8f1b2012-06-21 18:03:05 -0700429 // Set up play-on-hover <video> tags.
430 $('video.play-on-hover').bind('click', function(){
431 $(this).get(0).load(); // in case the video isn't seekable
432 $(this).get(0).play();
433 });
434
435 // Set up tooltips
436 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700437 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700438 var $target = $(this);
439 var $tooltip = $('<div>')
440 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700441 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700442 .hide()
443 .appendTo('body');
444 $target.removeAttr('title');
445
446 $target.hover(function() {
447 // in
448 var targetRect = $target.offset();
449 targetRect.width = $target.width();
450 targetRect.height = $target.height();
451
452 $tooltip.css({
453 left: targetRect.left,
454 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
455 });
456 $tooltip.addClass('below');
457 $tooltip.show();
458 }, function() {
459 // out
460 $tooltip.hide();
461 });
462 });
463
464 // Set up <h2> deeplinks
465 $('h2').click(function() {
466 var id = $(this).attr('id');
467 if (id) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700468 if (history && history.replaceState) {
469 // Change url without scrolling.
470 history.replaceState({}, '', '#' + id);
471 } else {
472 document.location.hash = id;
473 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700474 }
475 });
476
477 //Loads the +1 button
478 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
479 po.src = 'https://apis.google.com/js/plusone.js';
480 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
481
Scott Maine4d8f1b2012-06-21 18:03:05 -0700482 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700483
Scott Maine4d8f1b2012-06-21 18:03:05 -0700484 if ($(".scroll-pane").length > 1) {
485 // Check if there's a user preference for the panel heights
486 var cookieHeight = readCookie("reference_height");
487 if (cookieHeight) {
488 restoreHeight(cookieHeight);
489 }
490 }
Scott Main3b90aff2013-08-01 18:09:35 -0700491
Scott Main06f3f2c2014-05-30 11:23:00 -0700492 // Resize once loading is finished
Scott Maine4d8f1b2012-06-21 18:03:05 -0700493 resizeNav();
Scott Main06f3f2c2014-05-30 11:23:00 -0700494 // Check if there's an anchor that we need to scroll into view.
495 // A delay is needed, because some browsers do not immediately scroll down to the anchor
496 window.setTimeout(offsetScrollForSticky, 100);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700497
Scott Main015d6162013-01-29 09:01:52 -0800498 /* init the language selector based on user cookie for lang */
499 loadLangPref();
500 changeNavLang(getLangPref());
501
502 /* setup event handlers to ensure the overflow menu is visible while picking lang */
503 $("#language select")
504 .mousedown(function() {
505 $("div.morehover").addClass("hover"); })
506 .blur(function() {
507 $("div.morehover").removeClass("hover"); });
508
509 /* some global variable setup */
510 resizePackagesNav = $("#resize-packages-nav");
511 classesNav = $("#classes-nav");
512 devdocNav = $("#devdoc-nav");
513
514 var cookiePath = "";
515 if (location.href.indexOf("/reference/") != -1) {
516 cookiePath = "reference_";
517 } else if (location.href.indexOf("/guide/") != -1) {
518 cookiePath = "guide_";
519 } else if (location.href.indexOf("/tools/") != -1) {
520 cookiePath = "tools_";
521 } else if (location.href.indexOf("/training/") != -1) {
522 cookiePath = "training_";
523 } else if (location.href.indexOf("/design/") != -1) {
524 cookiePath = "design_";
525 } else if (location.href.indexOf("/distribute/") != -1) {
526 cookiePath = "distribute_";
527 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700528
smain@google.com698fff02014-11-20 20:39:33 -0800529
530 /* setup shadowbox for any videos that want it */
smain@google.comf75ee212014-11-24 09:42:59 -0800531 var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
smain@google.com698fff02014-11-20 20:39:33 -0800532 if ($videoLinks.length) {
533 // if there's at least one, add the shadowbox HTML to the body
534 $('body').prepend(
535'<div id="video-container">'+
536 '<div id="video-frame">'+
537 '<div class="video-close">'+
538 '<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
539 '</div>'+
540 '<div id="youTubePlayer"></div>'+
541 '</div>'+
542'</div>');
543
544 // loads the IFrame Player API code asynchronously.
545 $.getScript("https://www.youtube.com/iframe_api");
546
547 $videoLinks.each(function() {
548 var videoId = $(this).attr('href').split('?v=')[1];
549 $(this).click(function(event) {
550 event.preventDefault();
551 startYouTubePlayer(videoId);
552 });
553 });
smain@google.com698fff02014-11-20 20:39:33 -0800554 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700555});
Scott Main7e447ed2013-02-19 17:22:37 -0800556// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700557
558
smain@google.com698fff02014-11-20 20:39:33 -0800559var youTubePlayer;
560function onYouTubeIframeAPIReady() {
561}
562
smain@google.com3de83c12014-12-12 19:06:52 -0800563/* Returns the height the shadowbox video should be. It's based on the current
564 height of the "video-frame" element, which is 100% height for the window.
565 Then minus the margin so the video isn't actually the full window height. */
566function getVideoHeight() {
567 var frameHeight = $("#video-frame").height();
568 var marginTop = $("#video-frame").css('margin-top').split('px')[0];
569 return frameHeight - (marginTop * 2);
570}
571
smain@google.comd162be52015-02-05 13:27:16 -0800572var mPlayerPaused = false;
573
smain@google.com698fff02014-11-20 20:39:33 -0800574function startYouTubePlayer(videoId) {
smain@google.com3de83c12014-12-12 19:06:52 -0800575 $("#video-container").show();
576 $("#video-frame").show();
smain@google.comd162be52015-02-05 13:27:16 -0800577 mPlayerPaused = false;
smain@google.com3de83c12014-12-12 19:06:52 -0800578
579 // compute the size of the player so it's centered in window
580 var maxWidth = 940; // the width of the web site content
581 var videoAspect = .5625; // based on 1280x720 resolution
582 var maxHeight = maxWidth * videoAspect;
583 var videoHeight = getVideoHeight();
584 var videoWidth = videoHeight / videoAspect;
585 if (videoWidth > maxWidth) {
586 videoWidth = maxWidth;
587 videoHeight = maxHeight;
smain@google.com570c2212014-11-25 19:01:40 -0800588 }
smain@google.com3de83c12014-12-12 19:06:52 -0800589 $("#video-frame").css('width', videoWidth);
590
591 // check if we've already created this player
smain@google.com698fff02014-11-20 20:39:33 -0800592 if (youTubePlayer == null) {
smain@google.com3de83c12014-12-12 19:06:52 -0800593 // check if there's a start time specified
594 var idAndHash = videoId.split("#");
595 var startTime = 0;
596 if (idAndHash.length > 1) {
597 startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0;
598 }
599 // enable localized player
600 var lang = getLangPref();
601 var captionsOn = lang == 'en' ? 0 : 1;
602
smain@google.com698fff02014-11-20 20:39:33 -0800603 youTubePlayer = new YT.Player('youTubePlayer', {
smain@google.com3de83c12014-12-12 19:06:52 -0800604 height: videoHeight,
605 width: videoWidth,
smain@google.com570c2212014-11-25 19:01:40 -0800606 videoId: idAndHash[0],
smain@google.com481f15c2014-12-12 11:57:27 -0800607 playerVars: {start: startTime, hl: lang, cc_load_policy: captionsOn},
smain@google.com698fff02014-11-20 20:39:33 -0800608 events: {
smain@google.comf75ee212014-11-24 09:42:59 -0800609 'onReady': onPlayerReady,
610 'onStateChange': onPlayerStateChange
smain@google.com698fff02014-11-20 20:39:33 -0800611 }
612 });
613 } else {
smain@google.com3de83c12014-12-12 19:06:52 -0800614 // reset the size in case the user adjusted the window since last play
615 youTubePlayer.setSize(videoWidth, videoHeight);
smain@google.com94e0b532014-12-16 19:07:08 -0800616 // if a video different from the one already playing was requested, cue it up
smain@google.comd162be52015-02-05 13:27:16 -0800617 if (videoId != youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]) {
smain@google.com94e0b532014-12-16 19:07:08 -0800618 youTubePlayer.cueVideoById(videoId);
619 }
smain@google.com698fff02014-11-20 20:39:33 -0800620 youTubePlayer.playVideo();
621 }
smain@google.com698fff02014-11-20 20:39:33 -0800622}
623
624function onPlayerReady(event) {
625 event.target.playVideo();
smain@google.comd162be52015-02-05 13:27:16 -0800626 mPlayerPaused = false;
smain@google.com698fff02014-11-20 20:39:33 -0800627}
628
629function closeVideo() {
630 try {
smain@google.comf75ee212014-11-24 09:42:59 -0800631 youTubePlayer.pauseVideo();
smain@google.com698fff02014-11-20 20:39:33 -0800632 } catch(e) {
smain@google.com698fff02014-11-20 20:39:33 -0800633 }
smain@google.com3de83c12014-12-12 19:06:52 -0800634 $("#video-container").fadeOut(200);
smain@google.com698fff02014-11-20 20:39:33 -0800635}
636
smain@google.comf75ee212014-11-24 09:42:59 -0800637/* Track youtube playback for analytics */
638function onPlayerStateChange(event) {
639 // Video starts, send the video ID
640 if (event.data == YT.PlayerState.PLAYING) {
smain@google.comd162be52015-02-05 13:27:16 -0800641 if (mPlayerPaused) {
642 ga('send', 'event', 'Videos', 'Resume',
643 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0]);
644 } else {
645 // track the start playing event so we know from which page the video was selected
646 ga('send', 'event', 'Videos', 'Start: ' +
647 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
648 'on: ' + document.location.href);
649 }
650 mPlayerPaused = false;
smain@google.comf75ee212014-11-24 09:42:59 -0800651 }
652 // Video paused, send video ID and video elapsed time
653 if (event.data == YT.PlayerState.PAUSED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800654 ga('send', 'event', 'Videos', 'Paused',
smain@google.comd162be52015-02-05 13:27:16 -0800655 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
656 youTubePlayer.getCurrentTime());
657 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800658 }
659 // Video finished, send video ID and video elapsed time
660 if (event.data == YT.PlayerState.ENDED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800661 ga('send', 'event', 'Videos', 'Finished',
smain@google.comd162be52015-02-05 13:27:16 -0800662 youTubePlayer.getVideoUrl().split('?v=')[1].split('&')[0].split('%')[0],
663 youTubePlayer.getCurrentTime());
664 mPlayerPaused = true;
smain@google.comf75ee212014-11-24 09:42:59 -0800665 }
666}
667
smain@google.com698fff02014-11-20 20:39:33 -0800668
669
Scott Mainad08f072013-08-20 16:49:57 -0700670function initExpandableNavItems(rootTag) {
671 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
672 var section = $(this).closest('li.nav-section');
673 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700674 /* hide me and descendants */
675 section.find('ul').slideUp(250, function() {
676 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700677 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700678 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700679 resizeNav();
680 });
681 } else {
682 /* show me */
683 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700684 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700685 $others.removeClass('expanded').children('ul').slideUp(250);
686
687 // now expand me
688 section.closest('li').addClass('expanded');
689 section.children('ul').slideDown(250, function() {
690 resizeNav();
691 });
692 }
693 });
Scott Mainf0093852013-08-22 11:37:11 -0700694
695 // Stop expand/collapse behavior when clicking on nav section links
696 // (since we're navigating away from the page)
697 // This selector captures the first instance of <a>, but not those with "#" as the href.
698 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
699 window.location.href = $(this).attr('href');
700 return false;
701 });
Scott Mainad08f072013-08-20 16:49:57 -0700702}
703
Dirk Doughertyc3921652014-05-13 16:55:26 -0700704
705/** Create the list of breadcrumb links in the sticky header */
706function buildBreadcrumbs() {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700707 var $breadcrumbUl = $(".dac-header-crumbs");
708 var primaryNavLink = ".dac-nav-list > .dac-nav-item > .dac-nav-link";
709
Dirk Doughertyc3921652014-05-13 16:55:26 -0700710 // Add the secondary horizontal nav item, if provided
Dirk Dougherty29e93432015-05-05 18:17:13 -0700711 var $selectedSecondNav = $(".dac-nav-secondary .dac-nav-link.selected").clone()
712 .attr('class', 'dac-header-crumbs-link');
713
Dirk Doughertyc3921652014-05-13 16:55:26 -0700714 if ($selectedSecondNav.length) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700715 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedSecondNav));
Dirk Doughertyc3921652014-05-13 16:55:26 -0700716 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700717
Dirk Doughertyc3921652014-05-13 16:55:26 -0700718 // Add the primary horizontal nav
Dirk Dougherty29e93432015-05-05 18:17:13 -0700719 var $selectedFirstNav = $(primaryNavLink + ".selected, " + primaryNavLink + ".has-subnav").clone()
720 .attr('class', 'dac-header-crumbs-link');
721
Dirk Doughertyc3921652014-05-13 16:55:26 -0700722 // If there's no header nav item, use the logo link and title from alt text
723 if ($selectedFirstNav.length < 1) {
Dirk Dougherty29e93432015-05-05 18:17:13 -0700724 $selectedFirstNav = $('<a class="dac-header-crumbs-link">')
Dirk Doughertyc3921652014-05-13 16:55:26 -0700725 .attr('href', $("div#header .logo a").attr('href'))
726 .text($("div#header .logo img").attr('alt'));
727 }
Dirk Dougherty29e93432015-05-05 18:17:13 -0700728 $breadcrumbUl.prepend($('<li class="dac-header-crumbs-item">').append($selectedFirstNav));
Dirk Doughertyc3921652014-05-13 16:55:26 -0700729}
730
731
732
Scott Maine624b3f2013-09-12 12:56:41 -0700733/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700734function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700735 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
736 if ($("ul#nav li.selected").length) {
737 unHighlightSidenav();
738 }
739 // look for URL in sidenav, including the hash
740 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
741
742 // If the selNavLink is still empty, look for it without the hash
743 if ($selNavLink.length == 0) {
744 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
745 }
746
Scott Mainf6145542013-04-01 16:38:11 -0700747 var $selListItem;
748 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700749 // Find this page's <li> in sidenav and set selected
750 $selListItem = $selNavLink.closest('li');
751 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700752
Scott Mainf6145542013-04-01 16:38:11 -0700753 // Traverse up the tree and expand all parent nav-sections
754 $selNavLink.parents('li.nav-section').each(function() {
755 $(this).addClass('expanded');
756 $(this).children('ul').show();
757 });
758 }
759}
760
Scott Maine624b3f2013-09-12 12:56:41 -0700761function unHighlightSidenav() {
762 $("ul#nav li.selected").removeClass("selected");
763 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
764}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700765
766function toggleFullscreen(enable) {
767 var delay = 20;
768 var enabled = true;
769 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
770 if (enable) {
771 // Currently NOT USING fullscreen; enable fullscreen
772 stylesheet.removeAttr('disabled');
773 $('#nav-swap .fullscreen').removeClass('disabled');
774 $('#devdoc-nav').css({left:''});
775 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
776 enabled = true;
777 } else {
778 // Currently USING fullscreen; disable fullscreen
779 stylesheet.attr('disabled', 'disabled');
780 $('#nav-swap .fullscreen').addClass('disabled');
781 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
782 enabled = false;
783 }
smain@google.com6bdcb982014-11-14 11:53:07 -0800784 writeCookie("fullscreen", enabled, null);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700785 setNavBarDimensions();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700786 resizeNav(delay);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700787 updateSideNavDimensions();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700788 setTimeout(initSidenavHeightResize,delay);
789}
790
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700791// TODO: Refactor into a closure.
792var navBarLeftPos;
793var navBarWidth;
794function setNavBarDimensions() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700795 navBarLeftPos = $('#body-content').offset().left;
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700796 navBarWidth = $('#side-nav').width();
Scott Maine4d8f1b2012-06-21 18:03:05 -0700797}
798
799
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700800function updateSideNavDimensions() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700801 var newLeft = $(window).scrollLeft() - navBarLeftPos;
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700802 $('#devdoc-nav').css({left: -newLeft, width: navBarWidth});
Dirk Dougherty29e93432015-05-05 18:17:13 -0700803 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('padding-left')))});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700804}
Scott Main3b90aff2013-08-01 18:09:35 -0700805
Scott Maine4d8f1b2012-06-21 18:03:05 -0700806// TODO: use $(document).ready instead
807function addLoadEvent(newfun) {
808 var current = window.onload;
809 if (typeof window.onload != 'function') {
810 window.onload = newfun;
811 } else {
812 window.onload = function() {
813 current();
814 newfun();
815 }
816 }
817}
818
819var agent = navigator['userAgent'].toLowerCase();
820// If a mobile phone, set flag and do mobile setup
821if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
822 (agent.indexOf("blackberry") != -1) ||
823 (agent.indexOf("webos") != -1) ||
824 (agent.indexOf("mini") != -1)) { // opera mini browsers
825 isMobile = true;
826}
827
828
Scott Main498d7102013-08-21 15:47:38 -0700829$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700830 $("pre:not(.no-pretty-print)").addClass("prettyprint");
831 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700832});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700833
Scott Maine4d8f1b2012-06-21 18:03:05 -0700834
835
836
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700837/* ######### RESIZE THE SIDENAV ########## */
Scott Maine4d8f1b2012-06-21 18:03:05 -0700838
839function resizeNav(delay) {
840 var $nav = $("#devdoc-nav");
841 var $window = $(window);
842 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700843
Scott Maine4d8f1b2012-06-21 18:03:05 -0700844 // Get the height of entire window and the total header height.
845 // Then figure out based on scroll position whether the header is visible
846 var windowHeight = $window.height();
847 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700848 var headerHeight = $('#header-wrapper').outerHeight();
849 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700850
851 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700852 // Could be either margin or top position, depending on whether the nav is fixed.
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700853 var topMargin = (parseInt($nav.css('top')) || 20) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700854 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700855
Scott Maine4d8f1b2012-06-21 18:03:05 -0700856 // Depending on whether the header is visible, set the side nav's height.
857 if (headerVisible) {
858 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700859 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700860 } else {
861 // Once header is off screen, the nav height is almost full window height
862 navHeight = windowHeight - topMargin;
863 }
Scott Main3b90aff2013-08-01 18:09:35 -0700864
865
866
Scott Maine4d8f1b2012-06-21 18:03:05 -0700867 $scrollPanes = $(".scroll-pane");
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700868 if ($window.width() < 720) {
869 $nav.css('height', '');
870 } else if ($scrollPanes.length > 1) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700871 // subtract the height of the api level widget and nav swapper from the available nav height
872 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700873
Scott Maine4d8f1b2012-06-21 18:03:05 -0700874 $("#swapper").css({height:navHeight + "px"});
875 if ($("#nav-tree").is(":visible")) {
876 $("#nav-tree").css({height:navHeight});
877 }
Scott Main3b90aff2013-08-01 18:09:35 -0700878
879 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700880 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700881
882 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700883 // then the package panel should begin to shrink
884 if (parseInt(classesHeight) <= 0) {
885 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
886 $("#packages-nav").css({height:navHeight - 10});
887 }
Scott Main3b90aff2013-08-01 18:09:35 -0700888
Scott Maine4d8f1b2012-06-21 18:03:05 -0700889 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
890 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700891
892
Scott Maine4d8f1b2012-06-21 18:03:05 -0700893 } else {
894 $nav.height(navHeight);
895 }
Scott Main3b90aff2013-08-01 18:09:35 -0700896
Scott Maine4d8f1b2012-06-21 18:03:05 -0700897 if (delay) {
898 updateFromResize = true;
899 delayedReInitScrollbars(delay);
900 } else {
901 reInitScrollbars();
902 }
Scott Main3b90aff2013-08-01 18:09:35 -0700903
Scott Maine4d8f1b2012-06-21 18:03:05 -0700904}
905
906var updateScrollbars = false;
907var updateFromResize = false;
908
909/* Re-initialize the scrollbars to account for changed nav size.
910 * This method postpones the actual update by a 1/4 second in order to optimize the
911 * scroll performance while the header is still visible, because re-initializing the
912 * scroll panes is an intensive process.
913 */
914function delayedReInitScrollbars(delay) {
915 // If we're scheduled for an update, but have received another resize request
916 // before the scheduled resize has occured, just ignore the new request
917 // (and wait for the scheduled one).
918 if (updateScrollbars && updateFromResize) {
919 updateFromResize = false;
920 return;
921 }
Scott Main3b90aff2013-08-01 18:09:35 -0700922
Scott Maine4d8f1b2012-06-21 18:03:05 -0700923 // We're scheduled for an update and the update request came from this method's setTimeout
924 if (updateScrollbars && !updateFromResize) {
925 reInitScrollbars();
926 updateScrollbars = false;
927 } else {
928 updateScrollbars = true;
929 updateFromResize = false;
930 setTimeout('delayedReInitScrollbars()',delay);
931 }
932}
933
934/* Re-initialize the scrollbars to account for changed nav size. */
935function reInitScrollbars() {
936 var pane = $(".scroll-pane").each(function(){
937 var api = $(this).data('jsp');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700938 if (!api) {return;}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700939 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700940 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700941 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
942}
943
944
945/* Resize the height of the nav panels in the reference,
946 * and save the new size to a cookie */
947function saveNavPanels() {
948 var basePath = getBaseUri(location.pathname);
949 var section = basePath.substring(1,basePath.indexOf("/",1));
smain@google.com6bdcb982014-11-14 11:53:07 -0800950 writeCookie("height", resizePackagesNav.css("height"), section);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700951}
952
953
954
955function restoreHeight(packageHeight) {
956 $("#resize-packages-nav").height(packageHeight);
957 $("#packages-nav").height(packageHeight);
958 // var classesHeight = navHeight - packageHeight;
959 // $("#classes-nav").css({height:classesHeight});
960 // $("#classes-nav .jspContainer").css({height:classesHeight});
961}
962
963
964
965/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
966
967
968
969
970
Scott Main3b90aff2013-08-01 18:09:35 -0700971/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700972 This is called when the page finished loading. */
973function scrollIntoView(nav) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -0700974 return;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700975 var $nav = $("#"+nav);
976 var element = $nav.jScrollPane({/* ...settings... */});
977 var api = element.data('jsp');
978
979 if ($nav.is(':visible')) {
980 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700981 if ($selected.length == 0) {
982 // If no selected item found, exit
983 return;
984 }
Scott Main52dd2062013-08-15 12:22:28 -0700985 // get the selected item's offset from its container nav by measuring the item's offset
986 // relative to the document then subtract the container nav's offset relative to the document
987 var selectedOffset = $selected.offset().top - $nav.offset().top;
988 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
989 // if it's more than 80% down the nav
990 // scroll the item up by an amount equal to 80% the container nav's height
991 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700992 }
993 }
994}
995
996
997
998
999
1000
1001/* Show popup dialogs */
1002function showDialog(id) {
1003 $dialog = $("#"+id);
1004 $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>');
1005 $dialog.wrapInner('<div/>');
1006 $dialog.removeClass("hide");
1007}
1008
1009
1010
1011
1012
1013/* ######### COOKIES! ########## */
1014
1015function readCookie(cookie) {
1016 var myCookie = cookie_namespace+"_"+cookie+"=";
1017 if (document.cookie) {
1018 var index = document.cookie.indexOf(myCookie);
1019 if (index != -1) {
1020 var valStart = index + myCookie.length;
1021 var valEnd = document.cookie.indexOf(";", valStart);
1022 if (valEnd == -1) {
1023 valEnd = document.cookie.length;
1024 }
1025 var val = document.cookie.substring(valStart, valEnd);
1026 return val;
1027 }
1028 }
1029 return 0;
1030}
1031
smain@google.com6bdcb982014-11-14 11:53:07 -08001032function writeCookie(cookie, val, section) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001033 if (val==undefined) return;
1034 section = section == null ? "_" : "_"+section+"_";
smain@google.com6bdcb982014-11-14 11:53:07 -08001035 var age = 2*365*24*60*60; // set max-age to 2 years
Scott Main3b90aff2013-08-01 18:09:35 -07001036 var cookieValue = cookie_namespace + section + cookie + "=" + val
smain@google.com80e38f42014-11-03 10:47:12 -08001037 + "; max-age=" + age +"; path=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -07001038 document.cookie = cookieValue;
1039}
1040
1041/* ######### END COOKIES! ########## */
1042
1043
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001044var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -07001045var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001046var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -07001047/* Sets the vertical scoll position at which the sticky bar should appear.
1048 This method is called to reset the position when search results appear or hide */
1049function setStickyTop() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001050 stickyTop = $('#header-wrapper').outerHeight() - $('#header > .dac-header-inner').outerHeight();
Dirk Doughertyc3921652014-05-13 16:55:26 -07001051}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001052
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001053/*
Scott Mainb16376f2014-05-21 20:35:47 -07001054 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -07001055 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001056$(window).scroll(function(event) {
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001057 // Exit if the mouse target is a DIV, because that means the event is coming
1058 // from a scrollable div and so there's no need to make adjustments to our layout
1059 if ($(event.target).nodeName == "DIV") {
1060 return;
1061 }
1062
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001063 checkSticky();
1064});
1065
1066function checkSticky() {
1067 setStickyTop();
1068 var $headerEl = $('#header');
1069 // Exit if there's no sidenav
1070 if ($('#side-nav').length == 0) return;
1071
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001072 var top = $(window).scrollTop();
1073 // we set the navbar fixed when the scroll position is beyond the height of the site header...
Dirk Doughertycf7a3b92015-05-21 00:52:33 -07001074 var shouldBeSticky = top > stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001075 // ... except if the document content is shorter than the sidenav height.
1076 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1077 if ($("#doc-col").height() < $("#side-nav").height()) {
1078 shouldBeSticky = false;
1079 }
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001080 // Nor on mobile
1081 if (window.innerWidth < 720) {
1082 shouldBeSticky = false;
1083 }
Scott Mainf5257812014-05-22 17:26:38 -07001084 // Account for horizontal scroll
1085 var scrollLeft = $(window).scrollLeft();
1086 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1087 if (sticky && (scrollLeft != prevScrollLeft)) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001088 updateSideNavDimensions();
Scott Mainf5257812014-05-22 17:26:38 -07001089 prevScrollLeft = scrollLeft;
1090 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001091
1092 // Don't continue if the header is sufficently far away
1093 // (to avoid intensive resizing that slows scrolling)
1094 if (sticky == shouldBeSticky) {
1095 return;
1096 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001097
1098 // If sticky header visible and position is now near top, hide sticky
1099 if (sticky && !shouldBeSticky) {
1100 sticky = false;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001101 // make the sidenav static again
1102 $('#devdoc-nav')
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001103 .removeClass('fixed')
1104 .css({'width':'auto','margin':''});
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001105 // delay hide the sticky
Dirk Dougherty29e93432015-05-05 18:17:13 -07001106 $headerEl.removeClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001107
1108 // update the sidenaav position for side scrolling
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001109 updateSideNavDimensions();
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001110 } else if (!sticky && shouldBeSticky) {
1111 sticky = true;
Dirk Dougherty29e93432015-05-05 18:17:13 -07001112 $headerEl.addClass('is-sticky');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001113
1114 // make the sidenav fixed
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001115 $('#devdoc-nav')
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001116 .addClass('fixed');
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001117
1118 // update the sidenaav position for side scrolling
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001119 updateSideNavDimensions();
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001120
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001121 }
1122 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001123}
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001124
1125/*
1126 * Manages secion card states and nav resize to conclude loading
1127 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001128(function() {
1129 $(document).ready(function() {
1130
Dirk Doughertyc3921652014-05-13 16:55:26 -07001131 // Stack hover states
1132 $('.section-card-menu').each(function(index, el) {
1133 var height = $(el).height();
1134 $(el).css({height:height+'px', position:'relative'});
1135 var $cardInfo = $(el).find('.card-info');
1136
1137 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1138 });
1139
Dirk Doughertyc3921652014-05-13 16:55:26 -07001140 });
1141
1142})();
1143
Scott Maine4d8f1b2012-06-21 18:03:05 -07001144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
Scott Maind7026f72013-06-17 15:08:49 -07001157/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001158
1159
1160
1161
1162
1163function toggle(obj, slide) {
1164 var ul = $("ul:first", obj);
1165 var li = ul.parent();
1166 if (li.hasClass("closed")) {
1167 if (slide) {
1168 ul.slideDown("fast");
1169 } else {
1170 ul.show();
1171 }
1172 li.removeClass("closed");
1173 li.addClass("open");
1174 $(".toggle-img", li).attr("title", "hide pages");
1175 } else {
1176 ul.slideUp("fast");
1177 li.removeClass("open");
1178 li.addClass("closed");
1179 $(".toggle-img", li).attr("title", "show pages");
1180 }
1181}
1182
1183
Scott Maine4d8f1b2012-06-21 18:03:05 -07001184function buildToggleLists() {
1185 $(".toggle-list").each(
1186 function(i) {
1187 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1188 $(this).addClass("closed");
1189 });
1190}
1191
1192
1193
Scott Maind7026f72013-06-17 15:08:49 -07001194function hideNestedItems(list, toggle) {
1195 $list = $(list);
1196 // hide nested lists
1197 if($list.hasClass('showing')) {
1198 $("li ol", $list).hide('fast');
1199 $list.removeClass('showing');
1200 // show nested lists
1201 } else {
1202 $("li ol", $list).show('fast');
1203 $list.addClass('showing');
1204 }
1205 $(".more,.less",$(toggle)).toggle();
1206}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001207
1208
smain@google.com95948b82014-06-16 19:24:25 -07001209/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1210function setupIdeDocToggle() {
1211 $( "select.ide" ).change(function() {
1212 var selected = $(this).find("option:selected").attr("value");
1213 $(".select-ide").hide();
1214 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001215
smain@google.com95948b82014-06-16 19:24:25 -07001216 $("select.ide").val(selected);
1217 });
1218}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243/* REFERENCE NAV SWAP */
1244
1245
1246function getNavPref() {
1247 var v = readCookie('reference_nav');
1248 if (v != NAV_PREF_TREE) {
1249 v = NAV_PREF_PANELS;
1250 }
1251 return v;
1252}
1253
1254function chooseDefaultNav() {
1255 nav_pref = getNavPref();
1256 if (nav_pref == NAV_PREF_TREE) {
1257 $("#nav-panels").toggle();
1258 $("#panel-link").toggle();
1259 $("#nav-tree").toggle();
1260 $("#tree-link").toggle();
1261 }
1262}
1263
1264function swapNav() {
1265 if (nav_pref == NAV_PREF_TREE) {
1266 nav_pref = NAV_PREF_PANELS;
1267 } else {
1268 nav_pref = NAV_PREF_TREE;
1269 init_default_navtree(toRoot);
1270 }
smain@google.com6bdcb982014-11-14 11:53:07 -08001271 writeCookie("nav", nav_pref, "reference");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001272
1273 $("#nav-panels").toggle();
1274 $("#panel-link").toggle();
1275 $("#nav-tree").toggle();
1276 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001277
Scott Maine4d8f1b2012-06-21 18:03:05 -07001278 resizeNav();
1279
1280 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1281 $("#nav-tree .jspContainer:visible")
1282 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1283 // Another nasty hack to make the scrollbar appear now that we have height
1284 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001285
Scott Maine4d8f1b2012-06-21 18:03:05 -07001286 if ($("#nav-tree").is(':visible')) {
1287 scrollIntoView("nav-tree");
1288 } else {
1289 scrollIntoView("packages-nav");
1290 scrollIntoView("classes-nav");
1291 }
1292}
1293
1294
1295
Scott Mainf5089842012-08-14 16:31:07 -07001296/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001297/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001298/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001299
1300function getBaseUri(uri) {
1301 var intlUrl = (uri.substring(0,6) == "/intl/");
1302 if (intlUrl) {
1303 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1304 base = base.substring(base.indexOf('/')+1, base.length);
1305 //alert("intl, returning base url: /" + base);
1306 return ("/" + base);
1307 } else {
1308 //alert("not intl, returning uri as found.");
1309 return uri;
1310 }
1311}
1312
1313function requestAppendHL(uri) {
1314//append "?hl=<lang> to an outgoing request (such as to blog)
1315 var lang = getLangPref();
1316 if (lang) {
1317 var q = 'hl=' + lang;
1318 uri += '?' + q;
1319 window.location = uri;
1320 return false;
1321 } else {
1322 return true;
1323 }
1324}
1325
1326
Scott Maine4d8f1b2012-06-21 18:03:05 -07001327function changeNavLang(lang) {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001328 if (lang === 'en') { return; }
1329
1330 var $links = $('a[' + lang + '-lang]');
1331 $links.each(function(){ // for each link with a translation
Scott Main6eb95f12012-10-02 17:12:23 -07001332 var $link = $(this);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07001333 // put the desired language from the attribute as the text
1334 $link.text($link.attr(lang + '-lang'))
Scott Main6eb95f12012-10-02 17:12:23 -07001335 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001336}
1337
Scott Main015d6162013-01-29 09:01:52 -08001338function changeLangPref(lang, submit) {
smain@google.com6bdcb982014-11-14 11:53:07 -08001339 writeCookie("pref_lang", lang, null);
Scott Main015d6162013-01-29 09:01:52 -08001340
1341 // ####### TODO: Remove this condition once we're stable on devsite #######
1342 // This condition is only needed if we still need to support legacy GAE server
1343 if (devsite) {
1344 // Switch language when on Devsite server
1345 if (submit) {
1346 $("#setlang").submit();
1347 }
1348 } else {
1349 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001350 if (submit) {
1351 window.location = getBaseUri(location.pathname);
1352 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001353 }
1354}
1355
1356function loadLangPref() {
1357 var lang = readCookie("pref_lang");
1358 if (lang != 0) {
1359 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1360 }
1361}
1362
1363function getLangPref() {
1364 var lang = $("#language").find(":selected").attr("value");
1365 if (!lang) {
1366 lang = readCookie("pref_lang");
1367 }
1368 return (lang != 0) ? lang : 'en';
1369}
1370
1371/* ########## END LOCALIZATION ############ */
1372
1373
1374
1375
1376
1377
1378/* Used to hide and reveal supplemental content, such as long code samples.
1379 See the companion CSS in android-developer-docs.css */
1380function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001381 var div = $(obj).closest(".toggle-content");
1382 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001383 if (div.hasClass("closed")) { // if it's closed, open it
1384 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001385 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001386 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001387 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001388 + "assets/images/triangle-opened.png");
1389 } else { // if it's open, close it
1390 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001391 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001392 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001393 div.find(".toggle-content").removeClass("open").addClass("closed")
1394 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001395 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001396 + "assets/images/triangle-closed.png");
1397 });
1398 }
1399 return false;
1400}
Scott Mainf5089842012-08-14 16:31:07 -07001401
1402
Scott Maindb3678b2012-10-23 14:13:41 -07001403/* New version of expandable content */
1404function toggleExpandable(link,id) {
1405 if($(id).is(':visible')) {
1406 $(id).slideUp();
1407 $(link).removeClass('expanded');
1408 } else {
1409 $(id).slideDown();
1410 $(link).addClass('expanded');
1411 }
1412}
1413
1414function hideExpandable(ids) {
1415 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001416 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001417}
1418
Scott Mainf5089842012-08-14 16:31:07 -07001419
1420
1421
1422
Scott Main3b90aff2013-08-01 18:09:35 -07001423/*
Scott Mainf5089842012-08-14 16:31:07 -07001424 * Slideshow 1.0
1425 * Used on /index.html and /develop/index.html for carousel
1426 *
1427 * Sample usage:
1428 * HTML -
1429 * <div class="slideshow-container">
1430 * <a href="" class="slideshow-prev">Prev</a>
1431 * <a href="" class="slideshow-next">Next</a>
1432 * <ul>
1433 * <li class="item"><img src="images/marquee1.jpg"></li>
1434 * <li class="item"><img src="images/marquee2.jpg"></li>
1435 * <li class="item"><img src="images/marquee3.jpg"></li>
1436 * <li class="item"><img src="images/marquee4.jpg"></li>
1437 * </ul>
1438 * </div>
1439 *
1440 * <script type="text/javascript">
1441 * $('.slideshow-container').dacSlideshow({
1442 * auto: true,
1443 * btnPrev: '.slideshow-prev',
1444 * btnNext: '.slideshow-next'
1445 * });
1446 * </script>
1447 *
1448 * Options:
1449 * btnPrev: optional identifier for previous button
1450 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001451 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001452 * auto: whether or not to auto-proceed
1453 * speed: animation speed
1454 * autoTime: time between auto-rotation
1455 * easing: easing function for transition
1456 * start: item to select by default
1457 * scroll: direction to scroll in
1458 * pagination: whether or not to include dotted pagination
1459 *
1460 */
1461
1462 (function($) {
1463 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001464
Scott Mainf5089842012-08-14 16:31:07 -07001465 //Options - see above
1466 o = $.extend({
1467 btnPrev: null,
1468 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001469 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001470 auto: true,
1471 speed: 500,
1472 autoTime: 12000,
1473 easing: null,
1474 start: 0,
1475 scroll: 1,
1476 pagination: true
1477
1478 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001479
1480 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001481 return this.each(function() {
1482
1483 var running = false;
1484 var animCss = o.vertical ? "top" : "left";
1485 var sizeCss = o.vertical ? "height" : "width";
1486 var div = $(this);
1487 var ul = $("ul", div);
1488 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001489 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001490 var timer = null;
1491
1492 var li = $("li", ul);
1493 var itemLength = li.size();
1494 var curr = o.start;
1495
1496 li.css({float: o.vertical ? "none" : "left"});
1497 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1498 div.css({position: "relative", "z-index": "2", left: "0px"});
1499
1500 var liSize = o.vertical ? height(li) : width(li);
1501 var ulSize = liSize * itemLength;
1502 var divSize = liSize;
1503
1504 li.css({width: li.width(), height: li.height()});
1505 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1506
1507 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001508
Scott Mainf5089842012-08-14 16:31:07 -07001509 //Pagination
1510 if (o.pagination) {
1511 var pagination = $("<div class='pagination'></div>");
1512 var pag_ul = $("<ul></ul>");
1513 if (tl > 1) {
1514 for (var i=0;i<tl;i++) {
1515 var li = $("<li>"+i+"</li>");
1516 pag_ul.append(li);
1517 if (i==o.start) li.addClass('active');
1518 li.click(function() {
1519 go(parseInt($(this).text()));
1520 })
1521 }
1522 pagination.append(pag_ul);
1523 div.append(pagination);
1524 }
1525 }
Scott Main3b90aff2013-08-01 18:09:35 -07001526
Scott Mainf5089842012-08-14 16:31:07 -07001527 //Previous button
1528 if(o.btnPrev)
1529 $(o.btnPrev).click(function(e) {
1530 e.preventDefault();
1531 return go(curr-o.scroll);
1532 });
1533
1534 //Next button
1535 if(o.btnNext)
1536 $(o.btnNext).click(function(e) {
1537 e.preventDefault();
1538 return go(curr+o.scroll);
1539 });
Scott Maineb410352013-01-14 19:03:40 -08001540
1541 //Pause button
1542 if(o.btnPause)
1543 $(o.btnPause).click(function(e) {
1544 e.preventDefault();
1545 if ($(this).hasClass('paused')) {
1546 startRotateTimer();
1547 } else {
1548 pauseRotateTimer();
1549 }
1550 });
Scott Main3b90aff2013-08-01 18:09:35 -07001551
Scott Mainf5089842012-08-14 16:31:07 -07001552 //Auto rotation
1553 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001554
Scott Mainf5089842012-08-14 16:31:07 -07001555 function startRotateTimer() {
1556 clearInterval(timer);
1557 timer = setInterval(function() {
1558 if (curr == tl-1) {
1559 go(0);
1560 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001561 go(curr+o.scroll);
1562 }
Scott Mainf5089842012-08-14 16:31:07 -07001563 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001564 $(o.btnPause).removeClass('paused');
1565 }
1566
1567 function pauseRotateTimer() {
1568 clearInterval(timer);
1569 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001570 }
1571
1572 //Go to an item
1573 function go(to) {
1574 if(!running) {
1575
1576 if(to<0) {
1577 to = itemLength-1;
1578 } else if (to>itemLength-1) {
1579 to = 0;
1580 }
1581 curr = to;
1582
1583 running = true;
1584
1585 ul.animate(
1586 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1587 function() {
1588 running = false;
1589 }
1590 );
1591
1592 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1593 $( (curr-o.scroll<0 && o.btnPrev)
1594 ||
1595 (curr+o.scroll > itemLength && o.btnNext)
1596 ||
1597 []
1598 ).addClass("disabled");
1599
Scott Main3b90aff2013-08-01 18:09:35 -07001600
Scott Mainf5089842012-08-14 16:31:07 -07001601 var nav_items = $('li', pagination);
1602 nav_items.removeClass('active');
1603 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001604
Scott Mainf5089842012-08-14 16:31:07 -07001605
1606 }
1607 if(o.auto) startRotateTimer();
1608 return false;
1609 };
1610 });
1611 };
1612
1613 function css(el, prop) {
1614 return parseInt($.css(el[0], prop)) || 0;
1615 };
1616 function width(el) {
1617 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1618 };
1619 function height(el) {
1620 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1621 };
1622
1623 })(jQuery);
1624
1625
Scott Main3b90aff2013-08-01 18:09:35 -07001626/*
Scott Mainf5089842012-08-14 16:31:07 -07001627 * dacSlideshow 1.0
1628 * Used on develop/index.html for side-sliding tabs
1629 *
1630 * Sample usage:
1631 * HTML -
1632 * <div class="slideshow-container">
1633 * <a href="" class="slideshow-prev">Prev</a>
1634 * <a href="" class="slideshow-next">Next</a>
1635 * <ul>
1636 * <li class="item"><img src="images/marquee1.jpg"></li>
1637 * <li class="item"><img src="images/marquee2.jpg"></li>
1638 * <li class="item"><img src="images/marquee3.jpg"></li>
1639 * <li class="item"><img src="images/marquee4.jpg"></li>
1640 * </ul>
1641 * </div>
1642 *
1643 * <script type="text/javascript">
1644 * $('.slideshow-container').dacSlideshow({
1645 * auto: true,
1646 * btnPrev: '.slideshow-prev',
1647 * btnNext: '.slideshow-next'
1648 * });
1649 * </script>
1650 *
1651 * Options:
1652 * btnPrev: optional identifier for previous button
1653 * btnNext: optional identifier for next button
1654 * auto: whether or not to auto-proceed
1655 * speed: animation speed
1656 * autoTime: time between auto-rotation
1657 * easing: easing function for transition
1658 * start: item to select by default
1659 * scroll: direction to scroll in
1660 * pagination: whether or not to include dotted pagination
1661 *
1662 */
1663 (function($) {
1664 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001665
Scott Mainf5089842012-08-14 16:31:07 -07001666 //Options - see above
1667 o = $.extend({
1668 speed : 250,
1669 easing: null,
1670 nav_id: null,
1671 frame_id: null
1672 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001673
1674 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001675 return this.each(function() {
1676
1677 var curr = 0;
1678 var running = false;
1679 var animCss = "margin-left";
1680 var sizeCss = "width";
1681 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001682
Scott Mainf5089842012-08-14 16:31:07 -07001683 var nav = $(o.nav_id, div);
1684 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001685 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001686 var frame = div.find(o.frame_id);
1687 var content_width = $(frame).find('ul').width();
1688 //Buttons
1689 $(nav_li).click(function(e) {
1690 go($(nav_li).index($(this)));
1691 })
Scott Main3b90aff2013-08-01 18:09:35 -07001692
Scott Mainf5089842012-08-14 16:31:07 -07001693 //Go to an item
1694 function go(to) {
1695 if(!running) {
1696 curr = to;
1697 running = true;
1698
1699 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1700 function() {
1701 running = false;
1702 }
1703 );
1704
Scott Main3b90aff2013-08-01 18:09:35 -07001705
Scott Mainf5089842012-08-14 16:31:07 -07001706 nav_li.removeClass('active');
1707 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001708
Scott Mainf5089842012-08-14 16:31:07 -07001709
1710 }
1711 return false;
1712 };
1713 });
1714 };
1715
1716 function css(el, prop) {
1717 return parseInt($.css(el[0], prop)) || 0;
1718 };
1719 function width(el) {
1720 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1721 };
1722 function height(el) {
1723 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1724 };
1725
1726 })(jQuery);
1727
1728
1729
1730
1731
1732/* ######################################################## */
1733/* ################ SEARCH SUGGESTIONS ################## */
1734/* ######################################################## */
1735
1736
Scott Main7e447ed2013-02-19 17:22:37 -08001737
Scott Main0e76e7e2013-03-12 10:24:07 -07001738var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1739var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1740
Scott Mainf5089842012-08-14 16:31:07 -07001741var gMatches = new Array();
1742var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001743var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001744var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1745var gListLength = 0;
1746
1747
1748var gGoogleMatches = new Array();
1749var ROW_COUNT_GOOGLE = 15; // max number of results in list
1750var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001751
Scott Main0e76e7e2013-03-12 10:24:07 -07001752var gDocsMatches = new Array();
1753var ROW_COUNT_DOCS = 100; // max number of results in list
1754var gDocsListLength = 0;
1755
Scott Mainde295272013-03-25 15:48:35 -07001756function onSuggestionClick(link) {
1757 // When user clicks a suggested document, track it
smain@google.comed677d72014-12-12 10:19:17 -08001758 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
1759 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07001760}
1761
Scott Mainf5089842012-08-14 16:31:07 -07001762function set_item_selected($li, selected)
1763{
1764 if (selected) {
1765 $li.attr('class','jd-autocomplete jd-selected');
1766 } else {
1767 $li.attr('class','jd-autocomplete');
1768 }
1769}
1770
1771function set_item_values(toroot, $li, match)
1772{
1773 var $link = $('a',$li);
1774 $link.html(match.__hilabel || match.label);
1775 $link.attr('href',toroot + match.link);
1776}
1777
Scott Main719acb42013-12-05 16:05:09 -08001778function set_item_values_jd(toroot, $li, match)
1779{
1780 var $link = $('a',$li);
1781 $link.html(match.title);
1782 $link.attr('href',toroot + match.url);
1783}
1784
Scott Main0e76e7e2013-03-12 10:24:07 -07001785function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001786 var $li = $("<li class='jd-autocomplete'></li>");
1787 $list.append($li);
1788
1789 $li.mousedown(function() {
1790 window.location = this.firstChild.getAttribute("href");
1791 });
1792 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001793 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001794 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001795 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1796 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001797 });
Scott Mainde295272013-03-25 15:48:35 -07001798 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001799 $li.attr('class','show-item');
1800 return $li;
1801}
1802
Scott Mainf5089842012-08-14 16:31:07 -07001803function sync_selection_table(toroot)
1804{
Scott Mainf5089842012-08-14 16:31:07 -07001805 var $li; //list item jquery object
1806 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001807
Scott Main0e76e7e2013-03-12 10:24:07 -07001808 // if there are NO results at all, hide all columns
1809 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1810 $('.suggest-card').hide(300);
1811 return;
1812 }
1813
1814 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001815 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001816 // reveal suggestion list
Scott Main0e76e7e2013-03-12 10:24:07 -07001817 $('.suggest-card.reference').show();
1818 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001819
Scott Main0e76e7e2013-03-12 10:24:07 -07001820 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001821 $(".suggest-card.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001822
Scott Main0e76e7e2013-03-12 10:24:07 -07001823 // ########### ANDROID RESULTS #############
1824 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001825
Scott Main0e76e7e2013-03-12 10:24:07 -07001826 // determine android results to show
1827 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1828 gMatches.length : ROW_COUNT_FRAMEWORK;
1829 for (i=0; i<gListLength; i++) {
1830 var $li = new_suggestion($(".suggest-card.reference ul"));
1831 set_item_values(toroot, $li, gMatches[i]);
1832 set_item_selected($li, i == gSelectedIndex);
1833 }
1834 }
Scott Main7e447ed2013-02-19 17:22:37 -08001835
Scott Main0e76e7e2013-03-12 10:24:07 -07001836 // ########### GOOGLE RESULTS #############
1837 if (gGoogleMatches.length > 0) {
1838 // show header for list
1839 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001840
Scott Main0e76e7e2013-03-12 10:24:07 -07001841 // determine google results to show
1842 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1843 for (i=0; i<gGoogleListLength; i++) {
1844 var $li = new_suggestion($(".suggest-card.reference ul"));
1845 set_item_values(toroot, $li, gGoogleMatches[i]);
1846 set_item_selected($li, i == gSelectedIndex);
1847 }
1848 }
Scott Mainf5089842012-08-14 16:31:07 -07001849 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001850 $('.suggest-card.reference').hide();
Scott Main0e76e7e2013-03-12 10:24:07 -07001851 }
1852
1853 // ########### JD DOC RESULTS #############
1854 if (gDocsMatches.length > 0) {
1855 // reset the lists
Dirk Dougherty29e93432015-05-05 18:17:13 -07001856 $(".suggest-card:not(.reference) li").remove();
Scott Main0e76e7e2013-03-12 10:24:07 -07001857
1858 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001859 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1860 // The order must match the reverse order that each section appears as a card in
1861 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001862 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1863 for (i=0; i<gDocsListLength; i++) {
1864 var sugg = gDocsMatches[i];
1865 var $li;
1866 if (sugg.type == "design") {
1867 $li = new_suggestion($(".suggest-card.design ul"));
1868 } else
1869 if (sugg.type == "distribute") {
1870 $li = new_suggestion($(".suggest-card.distribute ul"));
1871 } else
Scott Main719acb42013-12-05 16:05:09 -08001872 if (sugg.type == "samples") {
1873 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1874 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001875 if (sugg.type == "training") {
1876 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1877 } else
Scott Main719acb42013-12-05 16:05:09 -08001878 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001879 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1880 } else {
1881 continue;
1882 }
1883
Scott Main719acb42013-12-05 16:05:09 -08001884 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001885 set_item_selected($li, i == gSelectedIndex);
1886 }
1887
1888 // add heading and show or hide card
1889 if ($(".suggest-card.design li").length > 0) {
1890 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1891 $(".suggest-card.design").show(300);
1892 } else {
1893 $('.suggest-card.design').hide(300);
1894 }
1895 if ($(".suggest-card.distribute li").length > 0) {
1896 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1897 $(".suggest-card.distribute").show(300);
1898 } else {
1899 $('.suggest-card.distribute').hide(300);
1900 }
1901 if ($(".child-card.guides li").length > 0) {
1902 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1903 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1904 }
1905 if ($(".child-card.training li").length > 0) {
1906 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1907 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1908 }
Scott Main719acb42013-12-05 16:05:09 -08001909 if ($(".child-card.samples li").length > 0) {
1910 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1911 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1912 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001913
1914 if ($(".suggest-card.develop li").length > 0) {
1915 $(".suggest-card.develop").show(300);
1916 } else {
1917 $('.suggest-card.develop').hide(300);
1918 }
1919
1920 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001921 $('.suggest-card:not(.reference)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001922 }
1923}
1924
Scott Main0e76e7e2013-03-12 10:24:07 -07001925/** Called by the search input's onkeydown and onkeyup events.
1926 * Handles navigation with keyboard arrows, Enter key to invoke search,
1927 * otherwise invokes search suggestions on key-up event.
1928 * @param e The JS event
1929 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001930 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001931 * @returns True if the event should bubble up
1932 */
Scott Mainf5089842012-08-14 16:31:07 -07001933function search_changed(e, kd, toroot)
1934{
Scott Main719acb42013-12-05 16:05:09 -08001935 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001936 var search = document.getElementById("search_autocomplete");
1937 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001938 // get the ul hosting the currently selected item
1939 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1940 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1941 var $selectedUl = $columns[gSelectedColumn];
1942
Scott Mainf5089842012-08-14 16:31:07 -07001943 // show/hide the close button
1944 if (text != '') {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001945 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07001946 } else {
Dirk Dougherty29e93432015-05-05 18:17:13 -07001947 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07001948 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001949 // 27 = esc
1950 if (e.keyCode == 27) {
1951 // close all search results
Dirk Dougherty29e93432015-05-05 18:17:13 -07001952 if (kd) $('#search-close').trigger('click');
Scott Main0e76e7e2013-03-12 10:24:07 -07001953 return true;
1954 }
Scott Mainf5089842012-08-14 16:31:07 -07001955 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001956 else if (e.keyCode == 13) {
1957 if (gSelectedIndex < 0) {
1958 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001959 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1960 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001961 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001962 return true;
1963 } else {
1964 // otherwise, results are already showing, so allow ajax to auto refresh the results
1965 // and ignore this Enter press to avoid the reload.
1966 return false;
1967 }
1968 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001969 // click the link corresponding to selected item
1970 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001971 return false;
1972 }
1973 }
Scott Mainb16376f2014-05-21 20:35:47 -07001974 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001975 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001976 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001977 if ((sticky ) && (search.value != "")) {
1978 $('body,html').animate({scrollTop:0}, '500', 'swing');
1979 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001980 return true;
1981 }
1982 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001983 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001984 // if the next item is a header, skip it
1985 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001986 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001987 }
1988 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001989 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001990 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001991 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1992 // If user reaches top, reset selected column
1993 if (gSelectedIndex < 0) {
1994 gSelectedColumn = -1;
1995 }
Scott Mainf5089842012-08-14 16:31:07 -07001996 }
1997 return false;
1998 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001999 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07002000 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002001 // if the next item is a header, skip it
2002 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07002003 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08002004 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002005 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
2006 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
2007 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08002008 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07002009 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07002010 }
2011 return false;
2012 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002013 // Consider left/right arrow navigation
2014 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
2015 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
2016 // 37 LEFT ARROW
2017 // go left only if current column is not left-most column (last column)
2018 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
2019 $('li', $selectedUl).removeClass('jd-selected');
2020 gSelectedColumn++;
2021 $selectedUl = $columns[gSelectedColumn];
2022 // keep or reset the selected item to last item as appropriate
2023 gSelectedIndex = gSelectedIndex >
2024 $("li", $selectedUl).length-1 ?
2025 $("li", $selectedUl).length-1 : gSelectedIndex;
2026 // if the corresponding item is a header, move down
2027 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2028 gSelectedIndex++;
2029 }
Scott Main3b90aff2013-08-01 18:09:35 -07002030 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002031 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2032 return false;
2033 }
2034 // 39 RIGHT ARROW
2035 // go right only if current column is not the right-most column (first column)
2036 else if (e.keyCode == 39 && gSelectedColumn > 0) {
2037 $('li', $selectedUl).removeClass('jd-selected');
2038 gSelectedColumn--;
2039 $selectedUl = $columns[gSelectedColumn];
2040 // keep or reset the selected item to last item as appropriate
2041 gSelectedIndex = gSelectedIndex >
2042 $("li", $selectedUl).length-1 ?
2043 $("li", $selectedUl).length-1 : gSelectedIndex;
2044 // if the corresponding item is a header, move down
2045 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2046 gSelectedIndex++;
2047 }
Scott Main3b90aff2013-08-01 18:09:35 -07002048 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002049 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2050 return false;
2051 }
2052 }
2053
Scott Main719acb42013-12-05 16:05:09 -08002054 // if key-up event and not arrow down/up/left/right,
2055 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07002056 else if (!kd && (e.keyCode != 40)
2057 && (e.keyCode != 38)
2058 && (e.keyCode != 37)
2059 && (e.keyCode != 39)) {
2060 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07002061 gMatches = new Array();
2062 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002063 gGoogleMatches = new Array();
2064 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07002065 gDocsMatches = new Array();
2066 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002067
2068 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07002069 for (var i=0; i<DATA.length; i++) {
2070 var s = DATA[i];
2071 if (text.length != 0 &&
2072 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2073 gMatches[matchedCount] = s;
2074 matchedCount++;
2075 }
2076 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002077 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07002078 for (var i=0; i<gMatches.length; i++) {
2079 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08002080 }
2081
2082
2083 // Search for Google matches
2084 for (var i=0; i<GOOGLE_DATA.length; i++) {
2085 var s = GOOGLE_DATA[i];
2086 if (text.length != 0 &&
2087 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2088 gGoogleMatches[matchedCountGoogle] = s;
2089 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07002090 }
2091 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002092 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08002093 for (var i=0; i<gGoogleMatches.length; i++) {
2094 var s = gGoogleMatches[i];
2095 }
2096
Scott Mainf5089842012-08-14 16:31:07 -07002097 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07002098
2099
2100
Scott Main719acb42013-12-05 16:05:09 -08002101 // Search for matching JD docs
Dirk Dougherty9b7f8f22014-11-01 17:08:56 -07002102 if (text.length >= 2) {
Scott Main719acb42013-12-05 16:05:09 -08002103 // Regex to match only the beginning of a word
2104 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
2105
2106
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
Scott Main719acb42013-12-05 16:05:09 -08002119 if (s.keywords[j].toLowerCase().match(textRegex)) {
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
Scott Main719acb42013-12-05 16:05:09 -08002129 if (s.title.toLowerCase().match(textRegex)) {
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
2153 if (s.keywords[j].toLowerCase().match(textRegex)) {
2154 matched = true;
2155 s.matched_tag = j + 1; // add 1 to index position
2156 }
2157 }
2158 // Check if query matches the doc title, but only for current language
2159 if (s.lang == currentLang) {
2160 // if query matches the doc title
2161 if (s.title.toLowerCase().match(textRegex)) {
2162 matched = true;
2163 s.matched_title = 1;
2164 }
2165 }
2166 if (matched) {
2167 gDocsMatches[matchedCountDocs] = s;
2168 matchedCountDocs++;
2169 }
2170 }
2171
2172
2173 // Search for Tools Guides
2174 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2175 // current search comparison, with counters for tag and title,
2176 // used later to improve ranking
2177 var s = TOOLS_RESOURCES[i];
2178 s.matched_tag = 0;
2179 s.matched_title = 0;
2180 var matched = false;
2181
2182 // Check if query matches any tags; work backwards toward 1 to assist ranking
2183 for (var j = s.keywords.length - 1; j >= 0; j--) {
2184 // it matches a tag
2185 if (s.keywords[j].toLowerCase().match(textRegex)) {
2186 matched = true;
2187 s.matched_tag = j + 1; // add 1 to index position
2188 }
2189 }
2190 // Check if query matches the doc title, but only for current language
2191 if (s.lang == currentLang) {
2192 // if query matches the doc title
2193 if (s.title.toLowerCase().match(textRegex)) {
2194 matched = true;
2195 s.matched_title = 1;
2196 }
2197 }
2198 if (matched) {
2199 gDocsMatches[matchedCountDocs] = s;
2200 matchedCountDocs++;
2201 }
2202 }
2203
2204
2205 // Search for About docs
2206 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2207 // current search comparison, with counters for tag and title,
2208 // used later to improve ranking
2209 var s = ABOUT_RESOURCES[i];
2210 s.matched_tag = 0;
2211 s.matched_title = 0;
2212 var matched = false;
2213
2214 // Check if query matches any tags; work backwards toward 1 to assist ranking
2215 for (var j = s.keywords.length - 1; j >= 0; j--) {
2216 // it matches a tag
2217 if (s.keywords[j].toLowerCase().match(textRegex)) {
2218 matched = true;
2219 s.matched_tag = j + 1; // add 1 to index position
2220 }
2221 }
2222 // Check if query matches the doc title, but only for current language
2223 if (s.lang == currentLang) {
2224 // if query matches the doc title
2225 if (s.title.toLowerCase().match(textRegex)) {
2226 matched = true;
2227 s.matched_title = 1;
2228 }
2229 }
2230 if (matched) {
2231 gDocsMatches[matchedCountDocs] = s;
2232 matchedCountDocs++;
2233 }
2234 }
2235
2236
2237 // Search for Design guides
2238 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2239 // current search comparison, with counters for tag and title,
2240 // used later to improve ranking
2241 var s = DESIGN_RESOURCES[i];
2242 s.matched_tag = 0;
2243 s.matched_title = 0;
2244 var matched = false;
2245
2246 // Check if query matches any tags; work backwards toward 1 to assist ranking
2247 for (var j = s.keywords.length - 1; j >= 0; j--) {
2248 // it matches a tag
2249 if (s.keywords[j].toLowerCase().match(textRegex)) {
2250 matched = true;
2251 s.matched_tag = j + 1; // add 1 to index position
2252 }
2253 }
2254 // Check if query matches the doc title, but only for current language
2255 if (s.lang == currentLang) {
2256 // if query matches the doc title
2257 if (s.title.toLowerCase().match(textRegex)) {
2258 matched = true;
2259 s.matched_title = 1;
2260 }
2261 }
2262 if (matched) {
2263 gDocsMatches[matchedCountDocs] = s;
2264 matchedCountDocs++;
2265 }
2266 }
2267
2268
2269 // Search for Distribute guides
2270 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2271 // current search comparison, with counters for tag and title,
2272 // used later to improve ranking
2273 var s = DISTRIBUTE_RESOURCES[i];
2274 s.matched_tag = 0;
2275 s.matched_title = 0;
2276 var matched = false;
2277
2278 // Check if query matches any tags; work backwards toward 1 to assist ranking
2279 for (var j = s.keywords.length - 1; j >= 0; j--) {
2280 // it matches a tag
2281 if (s.keywords[j].toLowerCase().match(textRegex)) {
2282 matched = true;
2283 s.matched_tag = j + 1; // add 1 to index position
2284 }
2285 }
2286 // Check if query matches the doc title, but only for current language
2287 if (s.lang == currentLang) {
2288 // if query matches the doc title
2289 if (s.title.toLowerCase().match(textRegex)) {
2290 matched = true;
2291 s.matched_title = 1;
2292 }
2293 }
2294 if (matched) {
2295 gDocsMatches[matchedCountDocs] = s;
2296 matchedCountDocs++;
2297 }
2298 }
2299
2300
2301 // Search for Google guides
2302 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2303 // current search comparison, with counters for tag and title,
2304 // used later to improve ranking
2305 var s = GOOGLE_RESOURCES[i];
2306 s.matched_tag = 0;
2307 s.matched_title = 0;
2308 var matched = false;
2309
2310 // Check if query matches any tags; work backwards toward 1 to assist ranking
2311 for (var j = s.keywords.length - 1; j >= 0; j--) {
2312 // it matches a tag
2313 if (s.keywords[j].toLowerCase().match(textRegex)) {
2314 matched = true;
2315 s.matched_tag = j + 1; // add 1 to index position
2316 }
2317 }
2318 // Check if query matches the doc title, but only for current language
2319 if (s.lang == currentLang) {
2320 // if query matches the doc title
2321 if (s.title.toLowerCase().match(textRegex)) {
2322 matched = true;
2323 s.matched_title = 1;
2324 }
2325 }
2326 if (matched) {
2327 gDocsMatches[matchedCountDocs] = s;
2328 matchedCountDocs++;
2329 }
2330 }
2331
2332
2333 // Search for Samples
2334 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2335 // current search comparison, with counters for tag and title,
2336 // used later to improve ranking
2337 var s = SAMPLES_RESOURCES[i];
2338 s.matched_tag = 0;
2339 s.matched_title = 0;
2340 var matched = false;
2341 // Check if query matches any tags; work backwards toward 1 to assist ranking
2342 for (var j = s.keywords.length - 1; j >= 0; j--) {
2343 // it matches a tag
2344 if (s.keywords[j].toLowerCase().match(textRegex)) {
2345 matched = true;
2346 s.matched_tag = j + 1; // add 1 to index position
2347 }
2348 }
2349 // Check if query matches the doc title, but only for current language
2350 if (s.lang == currentLang) {
2351 // if query matches the doc title.t
2352 if (s.title.toLowerCase().match(textRegex)) {
2353 matched = true;
2354 s.matched_title = 1;
2355 }
2356 }
2357 if (matched) {
2358 gDocsMatches[matchedCountDocs] = s;
2359 matchedCountDocs++;
2360 }
2361 }
2362
Joe Fernandeza9d796a2015-05-05 22:07:42 -07002363 // Search for Preview Guides
2364 for (var i=0; i<PREVIEW_RESOURCES.length; i++) {
2365 // current search comparison, with counters for tag and title,
2366 // used later to improve ranking
2367 var s = PREVIEW_RESOURCES[i];
2368 s.matched_tag = 0;
2369 s.matched_title = 0;
2370 var matched = false;
2371
2372 // Check if query matches any tags; work backwards toward 1 to assist ranking
2373 for (var j = s.keywords.length - 1; j >= 0; j--) {
2374 // it matches a tag
2375 if (s.keywords[j].toLowerCase().match(textRegex)) {
2376 matched = true;
2377 s.matched_tag = j + 1; // add 1 to index position
2378 }
2379 }
2380 // Check if query matches the doc title, but only for current language
2381 if (s.lang == currentLang) {
2382 // if query matches the doc title
2383 if (s.title.toLowerCase().match(textRegex)) {
2384 matched = true;
2385 s.matched_title = 1;
2386 }
2387 }
2388 if (matched) {
2389 gDocsMatches[matchedCountDocs] = s;
2390 matchedCountDocs++;
2391 }
2392 }
2393
Scott Main719acb42013-12-05 16:05:09 -08002394 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002395 rank_autocomplete_doc_results(text, gDocsMatches);
2396 }
2397
2398 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002399 sync_selection_table(toroot);
2400 return true; // allow the event to bubble up to the search api
2401 }
2402}
2403
Scott Main0e76e7e2013-03-12 10:24:07 -07002404/* Order the jd doc result list based on match quality */
2405function rank_autocomplete_doc_results(query, matches) {
2406 query = query || '';
2407 if (!matches || !matches.length)
2408 return;
2409
2410 var _resultScoreFn = function(match) {
2411 var score = 1.0;
2412
2413 // if the query matched a tag
2414 if (match.matched_tag > 0) {
2415 // multiply score by factor relative to position in tags list (max of 3)
2416 score *= 3 / match.matched_tag;
2417
2418 // if it also matched the title
2419 if (match.matched_title > 0) {
2420 score *= 2;
2421 }
2422 } else if (match.matched_title > 0) {
2423 score *= 3;
2424 }
2425
2426 return score;
2427 };
2428
2429 for (var i=0; i<matches.length; i++) {
2430 matches[i].__resultScore = _resultScoreFn(matches[i]);
2431 }
2432
2433 matches.sort(function(a,b){
2434 var n = b.__resultScore - a.__resultScore;
2435 if (n == 0) // lexicographical sort if scores are the same
2436 n = (a.label < b.label) ? -1 : 1;
2437 return n;
2438 });
2439}
2440
Scott Main7e447ed2013-02-19 17:22:37 -08002441/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002442function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002443 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002444 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002445 return;
2446
2447 // helper function that gets the last occurence index of the given regex
2448 // in the given string, or -1 if not found
2449 var _lastSearch = function(s, re) {
2450 if (s == '')
2451 return -1;
2452 var l = -1;
2453 var tmp;
2454 while ((tmp = s.search(re)) >= 0) {
2455 if (l < 0) l = 0;
2456 l += tmp;
2457 s = s.substr(tmp + 1);
2458 }
2459 return l;
2460 };
2461
2462 // helper function that counts the occurrences of a given character in
2463 // a given string
2464 var _countChar = function(s, c) {
2465 var n = 0;
2466 for (var i=0; i<s.length; i++)
2467 if (s.charAt(i) == c) ++n;
2468 return n;
2469 };
2470
2471 var queryLower = query.toLowerCase();
2472 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2473 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2474 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2475
2476 var _resultScoreFn = function(result) {
2477 // scores are calculated based on exact and prefix matches,
2478 // and then number of path separators (dots) from the last
2479 // match (i.e. favoring classes and deep package names)
2480 var score = 1.0;
2481 var labelLower = result.label.toLowerCase();
2482 var t;
2483 t = _lastSearch(labelLower, partExactAlnumRE);
2484 if (t >= 0) {
2485 // exact part match
2486 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2487 score *= 200 / (partsAfter + 1);
2488 } else {
2489 t = _lastSearch(labelLower, partPrefixAlnumRE);
2490 if (t >= 0) {
2491 // part prefix match
2492 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2493 score *= 20 / (partsAfter + 1);
2494 }
2495 }
2496
2497 return score;
2498 };
2499
Scott Main7e447ed2013-02-19 17:22:37 -08002500 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002501 // if the API is deprecated, default score is 0; otherwise, perform scoring
2502 if (matches[i].deprecated == "true") {
2503 matches[i].__resultScore = 0;
2504 } else {
2505 matches[i].__resultScore = _resultScoreFn(matches[i]);
2506 }
Scott Mainf5089842012-08-14 16:31:07 -07002507 }
2508
Scott Main7e447ed2013-02-19 17:22:37 -08002509 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002510 var n = b.__resultScore - a.__resultScore;
2511 if (n == 0) // lexicographical sort if scores are the same
2512 n = (a.label < b.label) ? -1 : 1;
2513 return n;
2514 });
2515}
2516
Scott Main7e447ed2013-02-19 17:22:37 -08002517/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002518function highlight_autocomplete_result_labels(query) {
2519 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002520 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002521 return;
2522
2523 var queryLower = query.toLowerCase();
2524 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2525 var queryRE = new RegExp(
2526 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2527 for (var i=0; i<gMatches.length; i++) {
2528 gMatches[i].__hilabel = gMatches[i].label.replace(
2529 queryRE, '<b>$1</b>');
2530 }
Scott Main7e447ed2013-02-19 17:22:37 -08002531 for (var i=0; i<gGoogleMatches.length; i++) {
2532 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2533 queryRE, '<b>$1</b>');
2534 }
Scott Mainf5089842012-08-14 16:31:07 -07002535}
2536
2537function search_focus_changed(obj, focused)
2538{
Scott Main3b90aff2013-08-01 18:09:35 -07002539 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002540 if(obj.value == ""){
Dirk Dougherty29e93432015-05-05 18:17:13 -07002541 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002542 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002543 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002544 }
2545}
2546
2547function submit_search() {
2548 var query = document.getElementById('search_autocomplete').value;
2549 location.hash = 'q=' + query;
2550 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002551 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002552 return false;
2553}
2554
2555
2556function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002557 $("#searchResults").slideUp('fast', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002558 $("#search-close").addClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002559 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002560
Scott Mainf5089842012-08-14 16:31:07 -07002561 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002562
Scott Mainf5089842012-08-14 16:31:07 -07002563 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2564 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002565
2566 // forcefully regain key-up event control (previously jacked by search api)
2567 $("#search_autocomplete").keyup(function(event) {
2568 return search_changed(event, false, toRoot);
2569 });
2570
Scott Mainf5089842012-08-14 16:31:07 -07002571 return false;
2572}
2573
2574
2575
2576/* ########################################################## */
2577/* ################ CUSTOM SEARCH ENGINE ################## */
2578/* ########################################################## */
2579
Scott Mainf5089842012-08-14 16:31:07 -07002580var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002581google.load('search', '1', {"callback" : function() {
2582 searchControl = new google.search.SearchControl();
2583 } });
Scott Mainf5089842012-08-14 16:31:07 -07002584
2585function loadSearchResults() {
2586 document.getElementById("search_autocomplete").style.color = "#000";
2587
Scott Mainf5089842012-08-14 16:31:07 -07002588 searchControl = new google.search.SearchControl();
2589
2590 // use our existing search form and use tabs when multiple searchers are used
2591 drawOptions = new google.search.DrawOptions();
2592 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2593 drawOptions.setInput(document.getElementById("search_autocomplete"));
2594
2595 // configure search result options
2596 searchOptions = new google.search.SearcherOptions();
2597 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2598
2599 // configure each of the searchers, for each tab
2600 devSiteSearcher = new google.search.WebSearch();
2601 devSiteSearcher.setUserDefinedLabel("All");
2602 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2603
2604 designSearcher = new google.search.WebSearch();
2605 designSearcher.setUserDefinedLabel("Design");
2606 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2607
2608 trainingSearcher = new google.search.WebSearch();
2609 trainingSearcher.setUserDefinedLabel("Training");
2610 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2611
2612 guidesSearcher = new google.search.WebSearch();
2613 guidesSearcher.setUserDefinedLabel("Guides");
2614 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2615
2616 referenceSearcher = new google.search.WebSearch();
2617 referenceSearcher.setUserDefinedLabel("Reference");
2618 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2619
Scott Maindf08ada2012-12-03 08:54:37 -08002620 googleSearcher = new google.search.WebSearch();
2621 googleSearcher.setUserDefinedLabel("Google Services");
2622 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2623
Scott Mainf5089842012-08-14 16:31:07 -07002624 blogSearcher = new google.search.WebSearch();
2625 blogSearcher.setUserDefinedLabel("Blog");
2626 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2627
2628 // add each searcher to the search control
2629 searchControl.addSearcher(devSiteSearcher, searchOptions);
2630 searchControl.addSearcher(designSearcher, searchOptions);
2631 searchControl.addSearcher(trainingSearcher, searchOptions);
2632 searchControl.addSearcher(guidesSearcher, searchOptions);
2633 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002634 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002635 searchControl.addSearcher(blogSearcher, searchOptions);
2636
2637 // configure result options
2638 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2639 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2640 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2641 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2642
2643 // upon ajax search, refresh the url and search title
2644 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2645 updateResultTitle(query);
2646 var query = document.getElementById('search_autocomplete').value;
2647 location.hash = 'q=' + query;
2648 });
2649
Scott Mainde295272013-03-25 15:48:35 -07002650 // once search results load, set up click listeners
2651 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2652 addResultClickListeners();
2653 });
2654
Scott Mainf5089842012-08-14 16:31:07 -07002655 // draw the search results box
2656 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2657
2658 // get query and execute the search
2659 searchControl.execute(decodeURI(getQuery(location.hash)));
2660
2661 document.getElementById("search_autocomplete").focus();
2662 addTabListeners();
2663}
2664// End of loadSearchResults
2665
2666
2667google.setOnLoadCallback(function(){
2668 if (location.hash.indexOf("q=") == -1) {
2669 // if there's no query in the url, don't search and make sure results are hidden
2670 $('#searchResults').hide();
2671 return;
2672 } else {
2673 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002674 $('#searchResults').slideDown('slow', setStickyTop);
Dirk Dougherty29e93432015-05-05 18:17:13 -07002675 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002676 loadSearchResults();
2677 }
2678}, true);
2679
smain@google.com9a818f52014-10-03 09:25:59 -07002680/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2681 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002682function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002683 // Ignore if there's no search bar (some special pages have no header)
2684 if ($("#search-container").length < 1) return;
2685
smain@google.com3b77ab52014-06-17 11:57:27 -07002686 var hash = escape(location.hash.substr(1));
2687 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002688 // Sanity check that there's an element with that ID on the page
2689 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002690 // If the position of the target element is near the top of the page (<20px, where we expect it
2691 // to be because we need to move it down 60px to become in view), then move it down 60px
2692 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2693 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002694 }
2695 }
2696}
2697
Scott Mainf5089842012-08-14 16:31:07 -07002698// when an event on the browser history occurs (back, forward, load) requery hash and do search
2699$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002700 // Ignore if there's no search bar (some special pages have no header)
2701 if ($("#search-container").length < 1) return;
2702
Dirk Doughertyc3921652014-05-13 16:55:26 -07002703 // If the hash isn't a search query or there's an error in the query,
2704 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002705 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2706 // If the results pane is open, close it.
2707 if (!$("#searchResults").is(":hidden")) {
2708 hideResults();
2709 }
Scott Mainb16376f2014-05-21 20:35:47 -07002710 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002711 return;
2712 }
2713
2714 // Otherwise, we have a search to do
2715 var query = decodeURI(getQuery(location.hash));
2716 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002717 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002718 $("#search_autocomplete").focus();
Dirk Dougherty29e93432015-05-05 18:17:13 -07002719 $("#search-close").removeClass("hide");
Scott Mainf5089842012-08-14 16:31:07 -07002720
2721 updateResultTitle(query);
2722});
2723
2724function updateResultTitle(query) {
2725 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2726}
2727
2728// forcefully regain key-up event control (previously jacked by search api)
2729$("#search_autocomplete").keyup(function(event) {
2730 return search_changed(event, false, toRoot);
2731});
2732
2733// add event listeners to each tab so we can track the browser history
2734function addTabListeners() {
2735 var tabHeaders = $(".gsc-tabHeader");
2736 for (var i = 0; i < tabHeaders.length; i++) {
2737 $(tabHeaders[i]).attr("id",i).click(function() {
2738 /*
2739 // make a copy of the page numbers for the search left pane
2740 setTimeout(function() {
2741 // remove any residual page numbers
2742 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002743 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002744 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002745 // and because we're going to remove it (previous line),
2746 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002747 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2748 .clone().appendTo('#searchResults .gsc-tabsArea');
2749 }, 200);
2750 */
2751 });
2752 }
2753 setTimeout(function(){$(tabHeaders[0]).click()},200);
2754}
2755
Scott Mainde295272013-03-25 15:48:35 -07002756// add analytics tracking events to each result link
2757function addResultClickListeners() {
2758 $("#searchResults a.gs-title").each(function(index, link) {
2759 // When user clicks enter for Google search results, track it
2760 $(link).click(function() {
smain@google.comed677d72014-12-12 10:19:17 -08002761 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'),
2762 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07002763 });
2764 });
2765}
2766
Scott Mainf5089842012-08-14 16:31:07 -07002767
2768function getQuery(hash) {
2769 var queryParts = hash.split('=');
2770 return queryParts[1];
2771}
2772
2773/* returns the given string with all HTML brackets converted to entities
2774 TODO: move this to the site's JS library */
2775function escapeHTML(string) {
2776 return string.replace(/</g,"&lt;")
2777 .replace(/>/g,"&gt;");
2778}
2779
2780
2781
2782
2783
2784
2785
2786/* ######################################################## */
2787/* ################# JAVADOC REFERENCE ################### */
2788/* ######################################################## */
2789
Scott Main65511c02012-09-07 15:51:32 -07002790/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002791if (location.pathname.indexOf("/reference") == 0) {
2792 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2793 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2794 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002795 $(document).ready(function() {
2796 // init available apis based on user pref
2797 changeApiLevel();
2798 initSidenavHeightResize()
2799 });
2800 }
Scott Main65511c02012-09-07 15:51:32 -07002801}
Scott Mainf5089842012-08-14 16:31:07 -07002802
2803var API_LEVEL_COOKIE = "api_level";
2804var minLevel = 1;
2805var maxLevel = 1;
2806
2807/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002808
Scott Mainf5089842012-08-14 16:31:07 -07002809 function initSidenavHeightResize() {
2810 // Change the drag bar size to nicely fit the scrollbar positions
2811 var $dragBar = $(".ui-resizable-s");
2812 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002813
2814 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002815 containment: "#nav-panels",
2816 handles: "s",
2817 alsoResize: "#packages-nav",
2818 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2819 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2820 });
Scott Main3b90aff2013-08-01 18:09:35 -07002821
Scott Mainf5089842012-08-14 16:31:07 -07002822 }
Scott Main3b90aff2013-08-01 18:09:35 -07002823
Scott Mainf5089842012-08-14 16:31:07 -07002824function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002825 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002826 $('#devdoc-nav').css({
2827 'width' : $('#side-nav').css('width'),
2828 'margin' : $('#side-nav').css('margin')
2829 });
2830 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002831
Scott Mainf5089842012-08-14 16:31:07 -07002832 initSidenavHeightResize();
2833}
2834
2835function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002836 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002837 $('#devdoc-nav').css({
2838 'width' : $('#side-nav').css('width'),
2839 'margin' : $('#side-nav').css('margin')
2840 });
2841 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002842
Scott Mainf5089842012-08-14 16:31:07 -07002843 initSidenavHeightResize();
2844}
2845
2846function buildApiLevelSelector() {
2847 maxLevel = SINCE_DATA.length;
2848 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2849 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2850
2851 minLevel = parseInt($("#doc-api-level").attr("class"));
2852 // Handle provisional api levels; the provisional level will always be the highest possible level
2853 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2854 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2855 if (isNaN(minLevel) && minLevel.length) {
2856 minLevel = maxLevel;
2857 }
2858 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2859 for (var i = maxLevel-1; i >= 0; i--) {
2860 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2861 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2862 select.append(option);
2863 }
2864
2865 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2866 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2867 selectedLevelItem.setAttribute('selected',true);
2868}
2869
2870function changeApiLevel() {
2871 maxLevel = SINCE_DATA.length;
2872 var selectedLevel = maxLevel;
2873
2874 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2875 toggleVisisbleApis(selectedLevel, "body");
2876
smain@google.com6bdcb982014-11-14 11:53:07 -08002877 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002878
2879 if (selectedLevel < minLevel) {
2880 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002881 $("#naMessage").show().html("<div><p><strong>This " + thing
2882 + " requires API level " + minLevel + " or higher.</strong></p>"
2883 + "<p>This document is hidden because your selected API level for the documentation is "
2884 + selectedLevel + ". You can change the documentation API level with the selector "
2885 + "above the left navigation.</p>"
2886 + "<p>For more information about specifying the API level your app requires, "
2887 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2888 + ">Supporting Different Platform Versions</a>.</p>"
2889 + "<input type='button' value='OK, make this page visible' "
2890 + "title='Change the API level to " + minLevel + "' "
2891 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2892 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002893 } else {
2894 $("#naMessage").hide();
2895 }
2896}
2897
2898function toggleVisisbleApis(selectedLevel, context) {
2899 var apis = $(".api",context);
2900 apis.each(function(i) {
2901 var obj = $(this);
2902 var className = obj.attr("class");
2903 var apiLevelIndex = className.lastIndexOf("-")+1;
2904 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2905 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2906 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2907 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2908 return;
2909 }
2910 apiLevel = parseInt(apiLevel);
2911
2912 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2913 var selectedLevelNum = parseInt(selectedLevel)
2914 var apiLevelNum = parseInt(apiLevel);
2915 if (isNaN(apiLevelNum)) {
2916 apiLevelNum = maxLevel;
2917 }
2918
2919 // Grey things out that aren't available and give a tooltip title
2920 if (apiLevelNum > selectedLevelNum) {
2921 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002922 + apiLevel + "\" or higher. To reveal, change the target API level "
2923 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002924 }
Scott Mainf5089842012-08-14 16:31:07 -07002925 else obj.removeClass("absent").removeAttr("title");
2926 });
2927}
2928
2929
2930
2931
2932/* ################# SIDENAV TREE VIEW ################### */
2933
2934function new_node(me, mom, text, link, children_data, api_level)
2935{
2936 var node = new Object();
2937 node.children = Array();
2938 node.children_data = children_data;
2939 node.depth = mom.depth + 1;
2940
2941 node.li = document.createElement("li");
2942 mom.get_children_ul().appendChild(node.li);
2943
2944 node.label_div = document.createElement("div");
2945 node.label_div.className = "label";
2946 if (api_level != null) {
2947 $(node.label_div).addClass("api");
2948 $(node.label_div).addClass("api-level-"+api_level);
2949 }
2950 node.li.appendChild(node.label_div);
2951
2952 if (children_data != null) {
2953 node.expand_toggle = document.createElement("a");
2954 node.expand_toggle.href = "javascript:void(0)";
2955 node.expand_toggle.onclick = function() {
2956 if (node.expanded) {
2957 $(node.get_children_ul()).slideUp("fast");
2958 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2959 node.expanded = false;
2960 } else {
2961 expand_node(me, node);
2962 }
2963 };
2964 node.label_div.appendChild(node.expand_toggle);
2965
2966 node.plus_img = document.createElement("img");
2967 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2968 node.plus_img.className = "plus";
2969 node.plus_img.width = "8";
2970 node.plus_img.border = "0";
2971 node.expand_toggle.appendChild(node.plus_img);
2972
2973 node.expanded = false;
2974 }
2975
2976 var a = document.createElement("a");
2977 node.label_div.appendChild(a);
2978 node.label = document.createTextNode(text);
2979 a.appendChild(node.label);
2980 if (link) {
2981 a.href = me.toroot + link;
2982 } else {
2983 if (children_data != null) {
2984 a.className = "nolink";
2985 a.href = "javascript:void(0)";
2986 a.onclick = node.expand_toggle.onclick;
2987 // This next line shouldn't be necessary. I'll buy a beer for the first
2988 // person who figures out how to remove this line and have the link
2989 // toggle shut on the first try. --joeo@android.com
2990 node.expanded = false;
2991 }
2992 }
Scott Main3b90aff2013-08-01 18:09:35 -07002993
Scott Mainf5089842012-08-14 16:31:07 -07002994
2995 node.children_ul = null;
2996 node.get_children_ul = function() {
2997 if (!node.children_ul) {
2998 node.children_ul = document.createElement("ul");
2999 node.children_ul.className = "children_ul";
3000 node.children_ul.style.display = "none";
3001 node.li.appendChild(node.children_ul);
3002 }
3003 return node.children_ul;
3004 };
3005
3006 return node;
3007}
3008
Robert Lyd2dd6e52012-11-29 21:28:48 -08003009
3010
3011
Scott Mainf5089842012-08-14 16:31:07 -07003012function expand_node(me, node)
3013{
3014 if (node.children_data && !node.expanded) {
3015 if (node.children_visited) {
3016 $(node.get_children_ul()).slideDown("fast");
3017 } else {
3018 get_node(me, node);
3019 if ($(node.label_div).hasClass("absent")) {
3020 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07003021 }
Scott Mainf5089842012-08-14 16:31:07 -07003022 $(node.get_children_ul()).slideDown("fast");
3023 }
3024 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
3025 node.expanded = true;
3026
3027 // perform api level toggling because new nodes are new to the DOM
3028 var selectedLevel = $("#apiLevelSelector option:selected").val();
3029 toggleVisisbleApis(selectedLevel, "#side-nav");
3030 }
3031}
3032
3033function get_node(me, mom)
3034{
3035 mom.children_visited = true;
3036 for (var i in mom.children_data) {
3037 var node_data = mom.children_data[i];
3038 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
3039 node_data[2], node_data[3]);
3040 }
3041}
3042
3043function this_page_relative(toroot)
3044{
3045 var full = document.location.pathname;
3046 var file = "";
3047 if (toroot.substr(0, 1) == "/") {
3048 if (full.substr(0, toroot.length) == toroot) {
3049 return full.substr(toroot.length);
3050 } else {
3051 // the file isn't under toroot. Fail.
3052 return null;
3053 }
3054 } else {
3055 if (toroot != "./") {
3056 toroot = "./" + toroot;
3057 }
3058 do {
3059 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3060 var pos = full.lastIndexOf("/");
3061 file = full.substr(pos) + file;
3062 full = full.substr(0, pos);
3063 toroot = toroot.substr(0, toroot.length-3);
3064 }
3065 } while (toroot != "" && toroot != "/");
3066 return file.substr(1);
3067 }
3068}
3069
3070function find_page(url, data)
3071{
3072 var nodes = data;
3073 var result = null;
3074 for (var i in nodes) {
3075 var d = nodes[i];
3076 if (d[1] == url) {
3077 return new Array(i);
3078 }
3079 else if (d[2] != null) {
3080 result = find_page(url, d[2]);
3081 if (result != null) {
3082 return (new Array(i).concat(result));
3083 }
3084 }
3085 }
3086 return null;
3087}
3088
Scott Mainf5089842012-08-14 16:31:07 -07003089function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07003090 // load json file for navtree data
3091 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3092 // when the file is loaded, initialize the tree
3093 if(jqxhr.status === 200) {
3094 init_navtree("tree-list", toroot, NAVTREE_DATA);
3095 }
3096 });
Scott Main3b90aff2013-08-01 18:09:35 -07003097
Scott Mainf5089842012-08-14 16:31:07 -07003098 // perform api level toggling because because the whole tree is new to the DOM
3099 var selectedLevel = $("#apiLevelSelector option:selected").val();
3100 toggleVisisbleApis(selectedLevel, "#side-nav");
3101}
3102
3103function init_navtree(navtree_id, toroot, root_nodes)
3104{
3105 var me = new Object();
3106 me.toroot = toroot;
3107 me.node = new Object();
3108
3109 me.node.li = document.getElementById(navtree_id);
3110 me.node.children_data = root_nodes;
3111 me.node.children = new Array();
3112 me.node.children_ul = document.createElement("ul");
3113 me.node.get_children_ul = function() { return me.node.children_ul; };
3114 //me.node.children_ul.className = "children_ul";
3115 me.node.li.appendChild(me.node.children_ul);
3116 me.node.depth = 0;
3117
3118 get_node(me, me.node);
3119
3120 me.this_page = this_page_relative(toroot);
3121 me.breadcrumbs = find_page(me.this_page, root_nodes);
3122 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3123 var mom = me.node;
3124 for (var i in me.breadcrumbs) {
3125 var j = me.breadcrumbs[i];
3126 mom = mom.children[j];
3127 expand_node(me, mom);
3128 }
3129 mom.label_div.className = mom.label_div.className + " selected";
3130 addLoadEvent(function() {
3131 scrollIntoView("nav-tree");
3132 });
3133 }
3134}
3135
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003136
3137
3138
3139
3140
3141
3142
Robert Lyd2dd6e52012-11-29 21:28:48 -08003143/* TODO: eliminate redundancy with non-google functions */
3144function init_google_navtree(navtree_id, toroot, root_nodes)
3145{
3146 var me = new Object();
3147 me.toroot = toroot;
3148 me.node = new Object();
3149
3150 me.node.li = document.getElementById(navtree_id);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003151 if (!me.node.li) {
3152 return;
3153 }
3154
Robert Lyd2dd6e52012-11-29 21:28:48 -08003155 me.node.children_data = root_nodes;
3156 me.node.children = new Array();
3157 me.node.children_ul = document.createElement("ul");
3158 me.node.get_children_ul = function() { return me.node.children_ul; };
3159 //me.node.children_ul.className = "children_ul";
3160 me.node.li.appendChild(me.node.children_ul);
3161 me.node.depth = 0;
3162
3163 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003164}
3165
3166function new_google_node(me, mom, text, link, children_data, api_level)
3167{
3168 var node = new Object();
3169 var child;
3170 node.children = Array();
3171 node.children_data = children_data;
3172 node.depth = mom.depth + 1;
3173 node.get_children_ul = function() {
3174 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003175 node.children_ul = document.createElement("ul");
3176 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003177 node.li.appendChild(node.children_ul);
3178 }
3179 return node.children_ul;
3180 };
3181 node.li = document.createElement("li");
3182
3183 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003184
3185
Robert Lyd2dd6e52012-11-29 21:28:48 -08003186 if(link) {
3187 child = document.createElement("a");
3188
3189 }
3190 else {
3191 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003192 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003193
3194 }
3195 if (children_data != null) {
3196 node.li.className="nav-section";
3197 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003198 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003199 node.li.appendChild(node.label_div);
3200 get_google_node(me, node);
3201 node.label_div.appendChild(child);
3202 }
3203 else {
3204 node.li.appendChild(child);
3205 }
3206 if(link) {
3207 child.href = me.toroot + link;
3208 }
3209 node.label = document.createTextNode(text);
3210 child.appendChild(node.label);
3211
3212 node.children_ul = null;
3213
3214 return node;
3215}
3216
3217function get_google_node(me, mom)
3218{
3219 mom.children_visited = true;
3220 var linkText;
3221 for (var i in mom.children_data) {
3222 var node_data = mom.children_data[i];
3223 linkText = node_data[0];
3224
3225 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3226 linkText = linkText.substr(19, linkText.length);
3227 }
3228 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3229 node_data[2], node_data[3]);
3230 }
3231}
Scott Mainad08f072013-08-20 16:49:57 -07003232
3233
3234
3235
3236
3237
3238/****** NEW version of script to build google and sample navs dynamically ******/
3239// TODO: update Google reference docs to tolerate this new implementation
3240
Scott Maine624b3f2013-09-12 12:56:41 -07003241var NODE_NAME = 0;
3242var NODE_HREF = 1;
3243var NODE_GROUP = 2;
3244var NODE_TAGS = 3;
3245var NODE_CHILDREN = 4;
3246
Scott Mainad08f072013-08-20 16:49:57 -07003247function init_google_navtree2(navtree_id, data)
3248{
3249 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003250 for (var i in data) {
3251 var node_data = data[i];
3252 $containerUl.append(new_google_node2(node_data));
3253 }
3254
Scott Main70557ee2013-10-30 14:47:40 -07003255 // Make all third-generation list items 'sticky' to prevent them from collapsing
3256 $containerUl.find('li li li.nav-section').addClass('sticky');
3257
Scott Mainad08f072013-08-20 16:49:57 -07003258 initExpandableNavItems("#"+navtree_id);
3259}
3260
3261function new_google_node2(node_data)
3262{
Scott Maine624b3f2013-09-12 12:56:41 -07003263 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003264 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3265 linkText = linkText.substr(19, linkText.length);
3266 }
3267 var $li = $('<li>');
3268 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003269 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003270 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3271 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003272 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003273 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3274 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003275 }
3276 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003277 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003278 $li.addClass("nav-section");
3279 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003280 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003281
Scott Maine624b3f2013-09-12 12:56:41 -07003282 for (var i in node_data[NODE_CHILDREN]) {
3283 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003284 $childUl.append(new_google_node2(child_node_data));
3285 }
3286 $li.append($childUl);
3287 }
3288 $li.prepend($a);
3289
3290 return $li;
3291}
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
Robert Lyd2dd6e52012-11-29 21:28:48 -08003303function showGoogleRefTree() {
3304 init_default_google_navtree(toRoot);
3305 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003306}
3307
3308function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003309 // load json file for navtree data
3310 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3311 // when the file is loaded, initialize the tree
3312 if(jqxhr.status === 200) {
3313 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3314 highlightSidenav();
3315 resizeNav();
3316 }
3317 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003318}
3319
3320function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003321 // load json file for navtree data
3322 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3323 // when the file is loaded, initialize the tree
3324 if(jqxhr.status === 200) {
3325 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3326 highlightSidenav();
3327 resizeNav();
3328 }
3329 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003330}
3331
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003332function showSamplesRefTree() {
3333 init_default_samples_navtree(toRoot);
3334}
3335
3336function init_default_samples_navtree(toroot) {
3337 // load json file for navtree data
3338 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3339 // when the file is loaded, initialize the tree
3340 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003341 // hack to remove the "about the samples" link then put it back in
3342 // after we nuke the list to remove the dummy static list of samples
3343 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3344 $("#nav.samples-nav").empty();
3345 $("#nav.samples-nav").append($firstLi);
3346
Scott Mainad08f072013-08-20 16:49:57 -07003347 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003348 highlightSidenav();
3349 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003350 if ($("#jd-content #samples").length) {
3351 showSamples();
3352 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003353 }
3354 });
3355}
3356
Scott Mainf5089842012-08-14 16:31:07 -07003357/* TOGGLE INHERITED MEMBERS */
3358
3359/* Toggle an inherited class (arrow toggle)
3360 * @param linkObj The link that was clicked.
3361 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3362 * 'null' to simply toggle.
3363 */
3364function toggleInherited(linkObj, expand) {
3365 var base = linkObj.getAttribute("id");
3366 var list = document.getElementById(base + "-list");
3367 var summary = document.getElementById(base + "-summary");
3368 var trigger = document.getElementById(base + "-trigger");
3369 var a = $(linkObj);
3370 if ( (expand == null && a.hasClass("closed")) || expand ) {
3371 list.style.display = "none";
3372 summary.style.display = "block";
3373 trigger.src = toRoot + "assets/images/triangle-opened.png";
3374 a.removeClass("closed");
3375 a.addClass("opened");
3376 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3377 list.style.display = "block";
3378 summary.style.display = "none";
3379 trigger.src = toRoot + "assets/images/triangle-closed.png";
3380 a.removeClass("opened");
3381 a.addClass("closed");
3382 }
3383 return false;
3384}
3385
3386/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3387 * @param linkObj The link that was clicked.
3388 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3389 * 'null' to simply toggle.
3390 */
3391function toggleAllInherited(linkObj, expand) {
3392 var a = $(linkObj);
3393 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3394 var expandos = $(".jd-expando-trigger", table);
3395 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3396 expandos.each(function(i) {
3397 toggleInherited(this, true);
3398 });
3399 a.text("[Collapse]");
3400 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3401 expandos.each(function(i) {
3402 toggleInherited(this, false);
3403 });
3404 a.text("[Expand]");
3405 }
3406 return false;
3407}
3408
3409/* Toggle all inherited members in the class (link in the class title)
3410 */
3411function toggleAllClassInherited() {
3412 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3413 var toggles = $(".toggle-all", $("#body-content"));
3414 if (a.text() == "[Expand All]") {
3415 toggles.each(function(i) {
3416 toggleAllInherited(this, true);
3417 });
3418 a.text("[Collapse All]");
3419 } else {
3420 toggles.each(function(i) {
3421 toggleAllInherited(this, false);
3422 });
3423 a.text("[Expand All]");
3424 }
3425 return false;
3426}
3427
3428/* Expand all inherited members in the class. Used when initiating page search */
3429function ensureAllInheritedExpanded() {
3430 var toggles = $(".toggle-all", $("#body-content"));
3431 toggles.each(function(i) {
3432 toggleAllInherited(this, true);
3433 });
3434 $("#toggleAllClassInherited").text("[Collapse All]");
3435}
3436
3437
3438/* HANDLE KEY EVENTS
3439 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3440 */
3441var agent = navigator['userAgent'].toLowerCase();
3442var mac = agent.indexOf("macintosh") != -1;
3443
3444$(document).keydown( function(e) {
3445var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3446 if (control && e.which == 70) { // 70 is "F"
3447 ensureAllInheritedExpanded();
3448 }
3449});
Scott Main498d7102013-08-21 15:47:38 -07003450
3451
3452
3453
3454
3455
3456/* On-demand functions */
3457
3458/** Move sample code line numbers out of PRE block and into non-copyable column */
3459function initCodeLineNumbers() {
3460 var numbers = $("#codesample-block a.number");
3461 if (numbers.length) {
3462 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3463 }
3464
3465 $(document).ready(function() {
3466 // select entire line when clicked
3467 $("span.code-line").click(function() {
3468 if (!shifted) {
3469 selectText(this);
3470 }
3471 });
3472 // invoke line link on double click
3473 $(".code-line").dblclick(function() {
3474 document.location.hash = $(this).attr('id');
3475 });
3476 // highlight the line when hovering on the number
3477 $("#codesample-line-numbers a.number").mouseover(function() {
3478 var id = $(this).attr('href');
3479 $(id).css('background','#e7e7e7');
3480 });
3481 $("#codesample-line-numbers a.number").mouseout(function() {
3482 var id = $(this).attr('href');
3483 $(id).css('background','none');
3484 });
3485 });
3486}
3487
3488// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3489var shifted = false;
3490$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3491
3492// courtesy of jasonedelman.com
3493function selectText(element) {
3494 var doc = document
3495 , range, selection
3496 ;
3497 if (doc.body.createTextRange) { //ms
3498 range = doc.body.createTextRange();
3499 range.moveToElementText(element);
3500 range.select();
3501 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003502 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003503 range = doc.createRange();
3504 range.selectNodeContents(element);
3505 selection.removeAllRanges();
3506 selection.addRange(range);
3507 }
Scott Main285f0772013-08-22 23:22:09 +00003508}
Scott Main03aca9a2013-10-31 07:20:55 -07003509
3510
3511
3512
3513/** Display links and other information about samples that match the
3514 group specified by the URL */
3515function showSamples() {
3516 var group = $("#samples").attr('class');
3517 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3518
3519 var $ul = $("<ul>");
3520 $selectedLi = $("#nav li.selected");
3521
3522 $selectedLi.children("ul").children("li").each(function() {
3523 var $li = $("<li>").append($(this).find("a").first().clone());
3524 $ul.append($li);
3525 });
3526
3527 $("#samples").append($ul);
3528
3529}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003530
3531
3532
3533/* ########################################################## */
3534/* ################### RESOURCE CARDS ##################### */
3535/* ########################################################## */
3536
3537/** Handle resource queries, collections, and grids (sections). Requires
3538 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3539
3540(function() {
3541 // Prevent the same resource from being loaded more than once per page.
3542 var addedPageResources = {};
3543
3544 $(document).ready(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003545 // Need to initialize hero carousel before other sections for dedupe
3546 // to work correctly.
3547 $('[data-carousel-query]').dacCarouselQuery();
3548
Dirk Doughertyc3921652014-05-13 16:55:26 -07003549 $('.resource-widget').each(function() {
3550 initResourceWidget(this);
3551 });
3552
3553 /* Pass the line height to ellipsisfade() to adjust the height of the
3554 text container to show the max number of lines possible, without
3555 showing lines that are cut off. This works with the css ellipsis
3556 classes to fade last text line and apply an ellipsis char. */
3557
Dirk Dougherty29e93432015-05-05 18:17:13 -07003558 //card text currently uses 20px line height.
3559 var lineHeight = 20;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003560 $('.card-info .text').ellipsisfade(lineHeight);
3561 });
3562
3563 /*
3564 Three types of resource layouts:
3565 Flow - Uses a fixed row-height flow using float left style.
3566 Carousel - Single card slideshow all same dimension absolute.
3567 Stack - Uses fixed columns and flexible element height.
3568 */
3569 function initResourceWidget(widget) {
3570 var $widget = $(widget);
3571 var isFlow = $widget.hasClass('resource-flow-layout'),
3572 isCarousel = $widget.hasClass('resource-carousel-layout'),
3573 isStack = $widget.hasClass('resource-stack-layout');
3574
Dirk Dougherty29e93432015-05-05 18:17:13 -07003575 // remove illegal col-x class which is not relevant anymore thanks to responsive styles.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003576 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
Dirk Dougherty29e93432015-05-05 18:17:13 -07003577 if (m && !$widget.is('.cols > *')) {
3578 $widget.removeClass('col-' + m[1]);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003579 }
3580
3581 var opts = {
3582 cardSizes: ($widget.data('cardsizes') || '').split(','),
3583 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3584 itemsPerPage: $widget.data('itemsperpage'),
3585 sortOrder: $widget.data('sortorder'),
3586 query: $widget.data('query'),
3587 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003588 /* Added by LFL 6/6/14 */
3589 resourceStyle: $widget.data('resourcestyle') || 'card',
3590 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003591 };
3592
3593 // run the search for the set of resources to show
3594
3595 var resources = buildResourceList(opts);
3596
3597 if (isFlow) {
3598 drawResourcesFlowWidget($widget, opts, resources);
3599 } else if (isCarousel) {
3600 drawResourcesCarouselWidget($widget, opts, resources);
3601 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003602 /* Looks like this got removed and is not used, so repurposing for the
3603 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003604 Modified by LFL 6/6/14
3605 */
3606 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003607 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003608 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003609 }
3610 }
3611
3612 /* Initializes a Resource Carousel Widget */
3613 function drawResourcesCarouselWidget($widget, opts, resources) {
3614 $widget.empty();
Dirk Dougherty29e93432015-05-05 18:17:13 -07003615 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003616
3617 $widget.addClass('resource-card slideshow-container')
3618 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3619 .append($('<a>').addClass('slideshow-next').text('Next'));
3620
3621 var css = { 'width': $widget.width() + 'px',
3622 'height': $widget.height() + 'px' };
3623
3624 var $ul = $('<ul>');
3625
3626 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003627 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003628 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003629 .decorateResourceCard(resources[i],plusone);
3630
3631 $('<li>').css(css)
3632 .append($card)
3633 .appendTo($ul);
3634 }
3635
3636 $('<div>').addClass('frame')
3637 .append($ul)
3638 .appendTo($widget);
3639
3640 $widget.dacSlideshow({
3641 auto: true,
3642 btnPrev: '.slideshow-prev',
3643 btnNext: '.slideshow-next'
3644 });
3645 };
3646
Robert Lye7eeb402014-06-03 19:35:24 -07003647 /* Initializes a Resource Card Stack Widget (column-based layout)
3648 Modified by LFL 6/6/14
3649 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003650 function drawResourcesStackWidget($widget, opts, resources, sections) {
3651 // Don't empty widget, grab all items inside since they will be the first
3652 // items stacked, followed by the resource query
Dirk Dougherty29e93432015-05-05 18:17:13 -07003653 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003654 var cards = $widget.find('.resource-card').detach().toArray();
3655 var numStacks = opts.numStacks || 1;
3656 var $stacks = [];
3657 var urlString;
3658
3659 for (var i = 0; i < numStacks; ++i) {
3660 $stacks[i] = $('<div>').addClass('resource-card-stack')
3661 .appendTo($widget);
3662 }
3663
3664 var sectionResources = [];
3665
3666 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003667 if (sections) {
3668 for (var i = 0; i < sections.length; ++i) {
3669 if (!sections[i].sections || !sections[i].sections.length) {
3670 // Render it as a resource card
3671 sectionResources.push(
3672 $('<a>')
3673 .addClass('resource-card section-card')
3674 .attr('href', cleanUrl(sections[i].resource.url))
3675 .decorateResourceCard(sections[i].resource,plusone)[0]
3676 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003677
Robert Lye7eeb402014-06-03 19:35:24 -07003678 } else {
3679 cards.push(
3680 $('<div>')
3681 .addClass('resource-card section-card-menu')
3682 .decorateResourceSection(sections[i],plusone)[0]
3683 );
3684 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003685 }
3686 }
3687
3688 cards = cards.concat(sectionResources);
3689
3690 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003691 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003692
Robert Lye7eeb402014-06-03 19:35:24 -07003693 if (opts.resourceStyle.indexOf('related') > -1) {
3694 $card.addClass('related-card');
3695 }
smain@google.com95948b82014-06-16 19:24:25 -07003696
Dirk Doughertyc3921652014-05-13 16:55:26 -07003697 cards.push($card[0]);
3698 }
3699
Robert Lye7eeb402014-06-03 19:35:24 -07003700 if (opts.stackSort != 'false') {
3701 for (var i = 0; i < cards.length; ++i) {
3702 // Find the stack with the shortest height, but give preference to
3703 // left to right order.
3704 var minHeight = $stacks[0].height();
3705 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003706
Robert Lye7eeb402014-06-03 19:35:24 -07003707 for (var j = 1; j < numStacks; ++j) {
3708 var height = $stacks[j].height();
3709 if (height < minHeight - 45) {
3710 minHeight = height;
3711 minIndex = j;
3712 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003713 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003714
Robert Lye7eeb402014-06-03 19:35:24 -07003715 $stacks[minIndex].append($(cards[i]));
3716 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003717 }
3718
3719 };
smain@google.com95948b82014-06-16 19:24:25 -07003720
3721 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003722 Create a resource card using the given resource object and a list of html
3723 configured options. Returns a jquery object containing the element.
3724 */
smain@google.com95948b82014-06-16 19:24:25 -07003725 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003726 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003727
Robert Lye7eeb402014-06-03 19:35:24 -07003728 // The difference here is that generic cards are not entirely clickable
3729 // so its a div instead of an a tag, also the generic one is not given
3730 // the resource-card class so it appears with a transparent background
3731 // and can be styled in whatever way the css setup.
3732 if (opts.resourceStyle == 'generic') {
3733 $el = $('<div>')
3734 .addClass('resource')
3735 .attr('href', cleanUrl(resource.url))
3736 .decorateResource(resource, opts);
3737 } else {
3738 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003739
Robert Lye7eeb402014-06-03 19:35:24 -07003740 $el = $('<a>')
3741 .addClass(cls)
3742 .attr('href', cleanUrl(resource.url))
3743 .decorateResourceCard(resource, plusone);
3744 }
smain@google.com95948b82014-06-16 19:24:25 -07003745
Robert Lye7eeb402014-06-03 19:35:24 -07003746 return $el;
3747 }
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003748
Dirk Dougherty29e93432015-05-05 18:17:13 -07003749 function createResponsiveFlowColumn(cardSize) {
3750 var cardWidth = parseInt(cardSize.match(/(\d+)/)[1], 10);
3751 var column = $('<div>').addClass('col-' + (cardWidth / 3) + 'of6');
3752 if (cardWidth < 9) {
3753 column.addClass('col-tablet-1of2');
3754 } else if (cardWidth > 9 && cardWidth < 18) {
3755 column.addClass('col-tablet-1of1');
3756 }
3757 if (cardWidth < 18) {
3758 column.addClass('col-mobile-1of1')
3759 }
3760 return column;
3761 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003762
3763 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3764 function drawResourcesFlowWidget($widget, opts, resources) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003765 $widget.empty().addClass('cols');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003766 var cardSizes = opts.cardSizes || ['6x6'];
3767 var i = 0, j = 0;
Dirk Dougherty29e93432015-05-05 18:17:13 -07003768 var plusone = false; // stop showing plusone buttons on cards
Dirk Doughertyc3921652014-05-13 16:55:26 -07003769
3770 while (i < resources.length) {
3771 var cardSize = cardSizes[j++ % cardSizes.length];
3772 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07003773
Dirk Dougherty29e93432015-05-05 18:17:13 -07003774 var column = createResponsiveFlowColumn(cardSize).appendTo($widget);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003775
3776 // A stack has a third dimension which is the number of stacked items
3777 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3778 var stackCount = 0;
3779 var $stackDiv = null;
3780
3781 if (isStack) {
3782 // Create a stack container which should have the dimensions defined
3783 // by the product of the items inside.
3784 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
Dirk Dougherty29e93432015-05-05 18:17:13 -07003785 + 'x' + isStack[2] * isStack[3]) .appendTo(column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003786 }
3787
3788 // Build each stack item or just a single item
3789 do {
3790 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003791
Robert Lye7eeb402014-06-03 19:35:24 -07003792 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003793
3794 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003795 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003796
Dirk Doughertyc3921652014-05-13 16:55:26 -07003797 if (isStack) {
3798 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3799 if (++stackCount == parseInt(isStack[3])) {
3800 $card.addClass('resource-card-row-stack-last');
3801 stackCount = 0;
3802 }
3803 } else {
3804 stackCount = 0;
3805 }
3806
Dirk Dougherty29e93432015-05-05 18:17:13 -07003807 $card.appendTo($stackDiv || column);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003808
3809 } while (++i < resources.length && stackCount > 0);
3810 }
3811 }
3812
3813 /* Build a site map of resources using a section as a root. */
3814 function buildSectionList(opts) {
3815 if (opts.section && SECTION_BY_ID[opts.section]) {
3816 return SECTION_BY_ID[opts.section].sections || [];
3817 }
3818 return [];
3819 }
3820
3821 function buildResourceList(opts) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003822 return $.queryResources(opts);
3823 }
3824
3825 $.queryResources = function(opts) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003826 var maxResults = opts.maxResults || 100;
3827
3828 var query = opts.query || '';
3829 var expressions = parseResourceQuery(query);
3830 var addedResourceIndices = {};
3831 var results = [];
3832
3833 for (var i = 0; i < expressions.length; i++) {
3834 var clauses = expressions[i];
3835
3836 // build initial set of resources from first clause
3837 var firstClause = clauses[0];
3838 var resources = [];
3839 switch (firstClause.attr) {
3840 case 'type':
3841 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3842 break;
3843 case 'lang':
3844 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3845 break;
3846 case 'tag':
3847 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3848 break;
3849 case 'collection':
3850 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3851 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3852 break;
3853 case 'section':
3854 var urls = SITE_MAP[firstClause.value].sections || [];
3855 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3856 break;
3857 }
3858 // console.log(firstClause.attr + ':' + firstClause.value);
3859 resources = resources || [];
3860
3861 // use additional clauses to filter corpus
3862 if (clauses.length > 1) {
3863 var otherClauses = clauses.slice(1);
3864 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3865 }
3866
3867 // filter out resources already added
3868 if (i > 1) {
3869 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3870 }
3871
3872 // add to list of already added indices
3873 for (var j = 0; j < resources.length; j++) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003874 if (resources[j]) {
3875 addedResourceIndices[resources[j].index] = 1;
3876 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003877 }
3878
3879 // concat to final results list
3880 results = results.concat(resources);
3881 }
3882
3883 if (opts.sortOrder && results.length) {
3884 var attr = opts.sortOrder;
3885
3886 if (opts.sortOrder == 'random') {
3887 var i = results.length, j, temp;
3888 while (--i) {
3889 j = Math.floor(Math.random() * (i + 1));
3890 temp = results[i];
3891 results[i] = results[j];
3892 results[j] = temp;
3893 }
3894 } else {
3895 var desc = attr.charAt(0) == '-';
3896 if (desc) {
3897 attr = attr.substring(1);
3898 }
3899 results = results.sort(function(x,y) {
3900 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3901 });
3902 }
3903 }
3904
3905 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3906 results = results.slice(0, maxResults);
3907
3908 for (var j = 0; j < results.length; ++j) {
3909 addedPageResources[results[j].index] = 1;
3910 }
3911
3912 return results;
3913 }
3914
3915
3916 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3917 return function(resource) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07003918 return resource && !addedResourceIndices[resource.index];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003919 };
3920 }
3921
3922
3923 function getResourceMatchesClausesFilter(clauses) {
3924 return function(resource) {
3925 return doesResourceMatchClauses(resource, clauses);
3926 };
3927 }
3928
3929
3930 function doesResourceMatchClauses(resource, clauses) {
3931 for (var i = 0; i < clauses.length; i++) {
3932 var map;
3933 switch (clauses[i].attr) {
3934 case 'type':
3935 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3936 break;
3937 case 'lang':
3938 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3939 break;
3940 case 'tag':
3941 map = IS_RESOURCE_TAGGED[clauses[i].value];
3942 break;
3943 }
3944
3945 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3946 return clauses[i].negative;
3947 }
3948 }
3949 return true;
3950 }
smain@google.com95948b82014-06-16 19:24:25 -07003951
Robert Lye7eeb402014-06-03 19:35:24 -07003952 function cleanUrl(url)
3953 {
3954 if (url && url.indexOf('//') === -1) {
3955 url = toRoot + url;
3956 }
smain@google.com95948b82014-06-16 19:24:25 -07003957
Robert Lye7eeb402014-06-03 19:35:24 -07003958 return url;
3959 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003960
3961
3962 function parseResourceQuery(query) {
3963 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3964 var expressions = [];
3965 var expressionStrs = query.split(',') || [];
3966 for (var i = 0; i < expressionStrs.length; i++) {
3967 var expr = expressionStrs[i] || '';
3968
3969 // Break expression into clauses (clause e.g. 'tag:foo')
3970 var clauses = [];
3971 var clauseStrs = expr.split(/(?=[\+\-])/);
3972 for (var j = 0; j < clauseStrs.length; j++) {
3973 var clauseStr = clauseStrs[j] || '';
3974
3975 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3976 var parts = clauseStr.split(':');
3977 var clause = {};
3978
3979 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3980 if (clause.attr) {
3981 if (clause.attr.charAt(0) == '+') {
3982 clause.attr = clause.attr.substring(1);
3983 } else if (clause.attr.charAt(0) == '-') {
3984 clause.negative = true;
3985 clause.attr = clause.attr.substring(1);
3986 }
3987 }
3988
3989 if (parts.length > 1) {
3990 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3991 }
3992
3993 clauses.push(clause);
3994 }
3995
3996 if (!clauses.length) {
3997 continue;
3998 }
3999
4000 expressions.push(clauses);
4001 }
4002
4003 return expressions;
4004 }
4005})();
4006
4007(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07004008
smain@google.com95948b82014-06-16 19:24:25 -07004009 /*
Robert Lye7eeb402014-06-03 19:35:24 -07004010 Utility method for creating dom for the description area of a card.
4011 Used in decorateResourceCard and decorateResource.
4012 */
4013 function buildResourceCardDescription(resource, plusone) {
4014 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07004015
Robert Lye7eeb402014-06-03 19:35:24 -07004016 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07004017
Robert Lye7eeb402014-06-03 19:35:24 -07004018 if (resource.cta) {
4019 $description.append($('<a>').addClass('cta').html(resource.cta));
4020 }
smain@google.com95948b82014-06-16 19:24:25 -07004021
Robert Lye7eeb402014-06-03 19:35:24 -07004022 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07004023 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07004024 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004025
Robert Lye7eeb402014-06-03 19:35:24 -07004026 $description.append($('<div>').addClass('util')
4027 .append($('<div>').addClass('g-plusone')
4028 .attr('data-size', 'small')
4029 .attr('data-align', 'right')
4030 .attr('data-href', plusurl)));
4031 }
smain@google.com95948b82014-06-16 19:24:25 -07004032
Robert Lye7eeb402014-06-03 19:35:24 -07004033 return $description;
4034 }
smain@google.com95948b82014-06-16 19:24:25 -07004035
4036
Dirk Doughertyc3921652014-05-13 16:55:26 -07004037 /* Simple jquery function to create dom for a standard resource card */
4038 $.fn.decorateResourceCard = function(resource,plusone) {
4039 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07004040 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004041 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07004042
Robert Lye7eeb402014-06-03 19:35:24 -07004043 if (imgUrl.indexOf('//') === -1) {
4044 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07004045 }
Robert Lye7eeb402014-06-03 19:35:24 -07004046
4047 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07004048 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07004049 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07004050 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07004051
Robert Lye7eeb402014-06-03 19:35:24 -07004052 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
4053 .append($('<div>').addClass('section').text(section))
4054 .append($('<div>').addClass('title').html(resource.title))
4055 .append(buildResourceCardDescription(resource, plusone))
4056 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07004057
4058 return this;
4059 };
4060
4061 /* Simple jquery function to create dom for a resource section card (menu) */
4062 $.fn.decorateResourceSection = function(section,plusone) {
4063 var resource = section.resource;
4064 //keep url clean for matching and offline mode handling
4065 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
4066 var $base = $('<a>')
4067 .addClass('card-bg')
4068 .attr('href', resource.url)
4069 .append($('<div>').addClass('card-section-icon')
4070 .append($('<div>').addClass('icon'))
4071 .append($('<div>').addClass('section').html(resource.title)))
4072 .appendTo(this);
4073
4074 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
4075
4076 if (section.sections && section.sections.length) {
4077 // Recurse the section sub-tree to find a resource image.
4078 var stack = [section];
4079
4080 while (stack.length) {
4081 if (stack[0].resource.image) {
4082 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4083 break;
4084 }
4085
4086 if (stack[0].sections) {
4087 stack = stack.concat(stack[0].sections);
4088 }
4089
4090 stack.shift();
4091 }
4092
4093 var $ul = $('<ul>')
4094 .appendTo($cardInfo);
4095
4096 var max = section.sections.length > 3 ? 3 : section.sections.length;
4097
4098 for (var i = 0; i < max; ++i) {
4099
4100 var subResource = section.sections[i];
4101 if (!plusone) {
4102 $('<li>')
4103 .append($('<a>').attr('href', subResource.url)
4104 .append($('<div>').addClass('title').html(subResource.title))
4105 .append($('<div>').addClass('description ellipsis')
4106 .append($('<div>').addClass('text').html(subResource.summary))
4107 .append($('<div>').addClass('util'))))
4108 .appendTo($ul);
4109 } else {
4110 $('<li>')
4111 .append($('<a>').attr('href', subResource.url)
4112 .append($('<div>').addClass('title').html(subResource.title))
4113 .append($('<div>').addClass('description ellipsis')
4114 .append($('<div>').addClass('text').html(subResource.summary))
4115 .append($('<div>').addClass('util')
4116 .append($('<div>').addClass('g-plusone')
4117 .attr('data-size', 'small')
4118 .attr('data-align', 'right')
4119 .attr('data-href', resource.url)))))
4120 .appendTo($ul);
4121 }
4122 }
4123
4124 // Add a more row
4125 if (max < section.sections.length) {
4126 $('<li>')
4127 .append($('<a>').attr('href', resource.url)
4128 .append($('<div>')
4129 .addClass('title')
4130 .text('More')))
4131 .appendTo($ul);
4132 }
4133 } else {
4134 // No sub-resources, just render description?
4135 }
4136
4137 return this;
4138 };
smain@google.com95948b82014-06-16 19:24:25 -07004139
4140
4141
4142
Robert Lye7eeb402014-06-03 19:35:24 -07004143 /* Render other types of resource styles that are not cards. */
4144 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07004145 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004146 'assets/images/resource-card-default-android.jpg';
4147 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004148
Robert Lye7eeb402014-06-03 19:35:24 -07004149 if (imgUrl.indexOf('//') === -1) {
4150 imgUrl = toRoot + imgUrl;
4151 }
smain@google.com95948b82014-06-16 19:24:25 -07004152
Robert Lye7eeb402014-06-03 19:35:24 -07004153 if (linkUrl && linkUrl.indexOf('//') === -1) {
4154 linkUrl = toRoot + linkUrl;
4155 }
4156
4157 $(this).append(
4158 $('<div>').addClass('image')
4159 .css('background-image', 'url(' + imgUrl + ')'),
4160 $('<div>').addClass('info').append(
4161 $('<h4>').addClass('title').html(resource.title),
4162 $('<p>').addClass('summary').html(resource.summary),
4163 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4164 )
4165 );
4166
4167 return this;
4168 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004169})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004170
4171
Dirk Doughertyc3921652014-05-13 16:55:26 -07004172/* Calculate the vertical area remaining */
4173(function($) {
4174 $.fn.ellipsisfade= function(lineHeight) {
4175 this.each(function() {
4176 // get element text
4177 var $this = $(this);
4178 var remainingHeight = $this.parent().parent().height();
4179 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004180 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004181 if ($(this).is(":visible")) {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004182 var h = $(this).outerHeight(true);
smain@google.comcda1a9a2014-06-19 17:07:46 -07004183 remainingHeight = remainingHeight - h;
4184 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004185 });
4186
4187 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4188 $this.parent().css({'height': adjustedRemainingHeight});
4189 $this.css({'height': "auto"});
4190 });
4191
4192 return this;
4193 };
4194}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004195
4196/*
4197 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004198
Robert Lye7eeb402014-06-03 19:35:24 -07004199 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004200 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004201 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004202
Robert Lye7eeb402014-06-03 19:35:24 -07004203 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004204
Robert Lye7eeb402014-06-03 19:35:24 -07004205 <div class="fullscreen-carousel">
4206 <div class="fullscreen-carousel-content">
4207 <!-- content here -->
4208 </div>
4209 <div class="fullscreen-carousel-content">
4210 <!-- content here -->
4211 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004212
Robert Lye7eeb402014-06-03 19:35:24 -07004213 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004214
Robert Lye7eeb402014-06-03 19:35:24 -07004215 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004216
Robert Lye7eeb402014-06-03 19:35:24 -07004217 Control over how the carousel takes over the screen can mostly be defined in
4218 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004219 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004220 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004221 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004222 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004223
Robert Lye7eeb402014-06-03 19:35:24 -07004224 There is limited functionality for having multiple sections since that request
4225 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4226 scroll between multiple content areas.
4227*/
4228
4229(function() {
4230 $(document).ready(function() {
4231 $('.fullscreen-carousel').each(function() {
4232 initWidget(this);
4233 });
4234 });
4235
4236 function initWidget(widget) {
4237 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004238
Robert Lye7eeb402014-06-03 19:35:24 -07004239 var topOffset = $widget.offset().top;
4240 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4241 var maxHeight = 0;
4242 var minHeight = 0;
4243 var $content = $widget.find('.fullscreen-carousel-content');
4244 var $nextArrow = $widget.find('.next-arrow');
4245 var $prevArrow = $widget.find('.prev-arrow');
4246 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004247
Robert Lye7eeb402014-06-03 19:35:24 -07004248 if ($content.length <= 1) {
4249 $nextArrow.hide();
4250 $prevArrow.hide();
4251 } else {
4252 $nextArrow.click(function() {
4253 var index = ($content.index($curSection) + 1);
4254 $curSection.hide();
4255 $curSection = $($content[index >= $content.length ? 0 : index]);
4256 $curSection.show();
4257 });
smain@google.com95948b82014-06-16 19:24:25 -07004258
Robert Lye7eeb402014-06-03 19:35:24 -07004259 $prevArrow.click(function() {
4260 var index = ($content.index($curSection) - 1);
4261 $curSection.hide();
4262 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4263 $curSection.show();
4264 });
4265 }
4266
4267 // Just hide all content sections except first.
4268 $content.each(function(index) {
4269 if ($(this).height() > minHeight) minHeight = $(this).height();
4270 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4271 });
4272
4273 // Register for changes to window size, and trigger.
4274 $(window).resize(resizeWidget);
4275 resizeWidget();
4276
4277 function resizeWidget() {
4278 var height = $(window).height() - topOffset - padBottom;
4279 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004280 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004281 (maxHeight && height > maxHeight ? maxHeight : height));
4282 }
smain@google.com95948b82014-06-16 19:24:25 -07004283 }
Robert Lye7eeb402014-06-03 19:35:24 -07004284})();
4285
4286
4287
4288
4289
4290/*
4291 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004292
Robert Lye7eeb402014-06-03 19:35:24 -07004293 The following allows tab widgets to be installed via the html below. Each
4294 tab content section should have a data-tab attribute matching one of the
4295 nav items'. Also each tab content section should have a width matching the
4296 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004297
Robert Lye7eeb402014-06-03 19:35:24 -07004298 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004299
Robert Lye7eeb402014-06-03 19:35:24 -07004300 <div class="tab-carousel">
4301 <ul class="tab-nav">
4302 <li><a href="#" data-tab="handsets">Handsets</a>
4303 <li><a href="#" data-tab="wearable">Wearable</a>
4304 <li><a href="#" data-tab="tv">TV</a>
4305 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004306
Robert Lye7eeb402014-06-03 19:35:24 -07004307 <div class="tab-carousel-content">
4308 <div data-tab="handsets">
4309 <!--Full width content here-->
4310 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004311
Robert Lye7eeb402014-06-03 19:35:24 -07004312 <div data-tab="wearable">
4313 <!--Full width content here-->
4314 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004315
Robert Lye7eeb402014-06-03 19:35:24 -07004316 <div data-tab="tv">
4317 <!--Full width content here-->
4318 </div>
4319 </div>
4320 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004321
Robert Lye7eeb402014-06-03 19:35:24 -07004322*/
4323(function() {
4324 $(document).ready(function() {
4325 $('.tab-carousel').each(function() {
4326 initWidget(this);
4327 });
4328 });
4329
4330 function initWidget(widget) {
4331 var $widget = $(widget);
4332 var $nav = $widget.find('.tab-nav');
4333 var $anchors = $nav.find('[data-tab]');
4334 var $li = $nav.find('li');
4335 var $contentContainer = $widget.find('.tab-carousel-content');
4336 var $tabs = $contentContainer.find('[data-tab]');
4337 var $curTab = $($tabs[0]); // Current tab is first tab.
4338 var width = $widget.width();
4339
4340 // Setup nav interactivity.
4341 $anchors.click(function(evt) {
4342 evt.preventDefault();
4343 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004344 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004345 });
smain@google.com95948b82014-06-16 19:24:25 -07004346
Robert Lye7eeb402014-06-03 19:35:24 -07004347 // Add highlight for navigation on first item.
4348 var $highlight = $('<div>').addClass('highlight')
4349 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4350 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004351
Robert Lye7eeb402014-06-03 19:35:24 -07004352 // Store height since we will change contents to absolute.
4353 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004354
Robert Lye7eeb402014-06-03 19:35:24 -07004355 // Absolutely position tabs so they're ready for transition.
4356 $tabs.each(function(index) {
4357 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4358 });
smain@google.com95948b82014-06-16 19:24:25 -07004359
Robert Lye7eeb402014-06-03 19:35:24 -07004360 function transitionWidget($toTab) {
4361 if (!$curTab.is($toTab)) {
4362 var curIndex = $tabs.index($curTab[0]);
4363 var toIndex = $tabs.index($toTab[0]);
4364 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004365
Robert Lye7eeb402014-06-03 19:35:24 -07004366 // Animate content sections.
4367 $toTab.css({left:(width * dir) + 'px'});
4368 $curTab.animate({left:(width * -dir) + 'px'});
4369 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004370
Robert Lye7eeb402014-06-03 19:35:24 -07004371 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004372 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004373 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004374
Robert Lye7eeb402014-06-03 19:35:24 -07004375 // Store new current section.
4376 $curTab = $toTab;
4377 }
4378 }
smain@google.com95948b82014-06-16 19:24:25 -07004379 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08004380})();
Dirk Dougherty29e93432015-05-05 18:17:13 -07004381
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004382/**
4383 * Auto TOC
4384 *
4385 * Upgrades h2s on the page to have a rule and be toggle-able on mobile.
4386 */
4387(function($) {
4388 var upgraded = false;
4389 var h2Titles;
4390
4391 function initWidget() {
4392 // add HRs below all H2s (except for a few other h2 variants)
4393 // Consider doing this with css instead.
4394 h2Titles = $('h2').not('#qv h2, #tb h2, .sidebox h2, #devdoc-nav h2, h2.norule');
4395 h2Titles.css({marginBottom:0}).after('<hr/>');
4396
4397 // Exit early if on older browser.
4398 if (!window.matchMedia) {
4399 return;
4400 }
4401
4402 // Only run logic in mobile layout.
4403 var query = window.matchMedia('(max-width: 719px)');
4404 if (query.matches) {
4405 makeTogglable();
4406 } else {
4407 query.addListener(makeTogglable);
4408 }
4409 }
4410
4411 function makeTogglable() {
4412 // Only run this logic once.
4413 if (upgraded) { return; }
4414 upgraded = true;
4415
4416 // Only make content h2s togglable.
4417 var contentTitles = h2Titles.filter('#jd-content *');
4418
4419 // If there are more than 1
4420 if (contentTitles.size() < 2) {
4421 return;
4422 }
4423
4424 contentTitles.each(function() {
4425 // Find all the relevant nodes.
4426 var $title = $(this);
4427 var $hr = $title.next();
4428 var $contents = $hr.nextUntil('h2, .next-docs');
4429 var $section = $($title)
4430 .add($hr)
4431 .add($title.prev('a[name]'))
4432 .add($contents);
4433 var $anchor = $section.first().prev();
4434 var anchorMethod = 'after';
4435 if ($anchor.length === 0) {
4436 $anchor = $title.parent();
4437 anchorMethod = 'prepend';
4438 }
4439
4440 // Remove from DOM before messing with it. DOM is slow!
4441 $section.detach();
4442
4443 // Add mobile-only expand arrows.
4444 $title.prepend('<span class="dac-visible-mobile-inline-block">' +
4445 '<i class="dac-toggle-expand dac-sprite dac-expand-more-black"></i>' +
4446 '<i class="dac-toggle-collapse dac-sprite dac-expand-less-black"></i>' +
4447 '</span>')
4448 .attr('data-toggle', 'section');
4449
4450 // Wrap in magic markup.
4451 $section = $section.wrapAll('<div class="dac-toggle dac-mobile">').parent();
4452 $contents.wrapAll('<div class="dac-toggle-content"><div>'); // extra div used for max-height calculation.
4453
4454 // Add it back to the dom.
4455 $anchor[anchorMethod].call($anchor, $section);
4456 });
4457 }
4458
4459 $(function() {
4460 initWidget();
4461 });
4462})(jQuery);
4463
Dirk Dougherty29e93432015-05-05 18:17:13 -07004464(function($) {
4465 'use strict';
4466
4467 /**
4468 * Toggle Floating Label state.
4469 * @param {HTMLElement} el - The DOM element.
4470 * @param options
4471 * @constructor
4472 */
4473 function FloatingLabel(el, options) {
4474 this.el = $(el);
4475 this.options = $.extend({}, FloatingLabel.DEFAULTS_, options);
4476 this.group = this.el.closest('.dac-form-input-group');
4477 this.input = this.group.find('.dac-form-input');
4478
4479 this.checkValue_ = this.checkValue_.bind(this);
4480 this.checkValue_();
4481
4482 this.input.on('focus', function() {
4483 this.group.addClass('dac-focused');
4484 }.bind(this));
4485 this.input.on('blur', function() {
4486 this.group.removeClass('dac-focused');
4487 this.checkValue_();
4488 }.bind(this));
4489 this.input.on('keyup', this.checkValue_);
4490 }
4491
4492 /**
4493 * The label is moved out of the textbox when it has a value.
4494 */
4495 FloatingLabel.prototype.checkValue_ = function() {
4496 if (this.input.val().length) {
4497 this.group.addClass('dac-has-value');
4498 } else {
4499 this.group.removeClass('dac-has-value');
4500 }
4501 };
4502
4503 /**
4504 * jQuery plugin
4505 * @param {object} options - Override default options.
4506 */
4507 $.fn.dacFloatingLabel = function(options) {
4508 return this.each(function() {
4509 new FloatingLabel(this, options);
4510 });
4511 };
4512
4513 $(document).on('ready.aranja', function() {
4514 $('.dac-form-floatlabel').each(function() {
4515 $(this).dacFloatingLabel($(this).data());
4516 });
4517 });
4518})(jQuery);
4519
4520/* global toRoot, CAROUSEL_OVERRIDE */
4521(function($) {
4522 // Ordering matters
4523 var TAG_MAP = [
4524 {from: 'developerstory', to: 'Android Developer Story'},
4525 {from: 'googleplay', to: 'Google Play'}
4526 ];
4527
4528 function DacCarouselQuery(el) {
4529 this.el = $(el);
4530
4531 var opts = this.el.data();
4532 opts.maxResults = parseInt(opts.maxResults || '100', 10);
4533 opts.query = opts.carouselQuery;
4534 var resources = $.queryResources(opts);
4535
4536 this.el.empty();
4537 $(resources).map(function() {
4538 var resource = $.extend({}, this, CAROUSEL_OVERRIDE[this.url]);
4539 var slide = $('<article class="dac-expand dac-hero">');
4540 var image = cleanUrl(resource.heroImage || resource.image);
4541 var fullBleed = image && !resource.heroColor;
4542
4543 // Configure background
4544 slide.css({
4545 backgroundImage: fullBleed ? 'url(' + image + ')' : '',
4546 backgroundColor: resource.heroColor || ''
4547 });
4548
4549 // Should copy be inverted
4550 slide.toggleClass('dac-invert', resource.heroInvert || fullBleed);
4551 slide.toggleClass('dac-darken', fullBleed);
4552
4553 var cols = $('<div class="cols dac-hero-content">');
4554
4555 // inline image column
4556 var rightCol = $('<div class="col-1of2 col-push-1of2 dac-hero-figure">')
4557 .appendTo(cols);
4558
4559 if (!fullBleed && image) {
4560 rightCol.append($('<img>').attr('src', image));
4561 }
4562
4563 // info column
4564 $('<div class="col-1of2 col-pull-1of2">')
4565 .append($('<div class="dac-hero-tag">').text(formatTag(resource)))
4566 .append($('<h1 class="dac-hero-title">').text(formatTitle(resource)))
4567 .append($('<p class="dac-hero-description">').text(resource.summary))
4568 .append($('<a class="dac-hero-cta">')
4569 .text(formatCTA(resource))
4570 .attr('href', cleanUrl(resource.url))
4571 .prepend($('<span class="dac-sprite dac-auto-chevron">'))
4572 )
4573 .appendTo(cols);
4574
4575 slide.append(cols.wrap('<div class="wrap">').parent());
4576 return slide[0];
4577 }).prependTo(this.el);
4578
4579 // Pagination element.
4580 this.el.append('<div class="dac-hero-carousel-pagination"><div class="wrap" data-carousel-pagination>');
4581
4582 this.el.dacCarousel();
4583 }
4584
4585 function cleanUrl(url) {
4586 if (url && url.indexOf('//') === -1) {
4587 url = toRoot + url;
4588 }
4589 return url;
4590 }
4591
4592 function formatTag(resource) {
4593 // Hmm, need a better more scalable solution for this.
4594 for (var i = 0, mapping; mapping = TAG_MAP[i]; i++) {
4595 if (resource.tags.indexOf(mapping.from) > -1) {
4596 return mapping.to;
4597 }
4598 }
4599 return resource.type;
4600 }
4601
4602 function formatTitle(resource) {
4603 return resource.title.replace(/android developer story: /i, '');
4604 }
4605
4606 function formatCTA(resource) {
4607 return resource.type === 'youtube' ? 'Watch the video' : 'Learn more';
4608 }
4609
4610 // jQuery plugin
4611 $.fn.dacCarouselQuery = function() {
4612 return this.each(function() {
4613 var el = $(this);
4614 var data = el.data('dac.carouselQuery');
4615
4616 if (!data) { el.data('dac.carouselQuery', (data = new DacCarouselQuery(el))); }
4617 });
4618 };
4619
4620 // Data API
4621 $(function() {
4622 $('[data-carousel-query]').dacCarouselQuery();
4623 });
4624})(jQuery);
4625
4626(function($) {
4627 /**
4628 * A CSS based carousel, inspired by SequenceJS.
4629 * @param {jQuery} el
4630 * @param {object} options
4631 * @constructor
4632 */
4633 function DacCarousel(el, options) {
4634 this.el = $(el);
4635 this.options = options = $.extend({}, DacCarousel.OPTIONS, this.el.data(), options || {});
4636 this.frames = this.el.find(options.frameSelector);
4637 this.count = this.frames.size();
4638 this.current = options.start;
4639
4640 this.initPagination();
4641 this.initEvents();
4642 this.initFrame();
4643 }
4644
4645 DacCarousel.OPTIONS = {
4646 auto: true,
4647 autoTime: 10000,
4648 autoMinTime: 5000,
4649 btnPrev: '[data-carousel-prev]',
4650 btnNext: '[data-carousel-next]',
4651 frameSelector: 'article',
4652 loop: true,
4653 start: 0,
4654 pagination: '[data-carousel-pagination]'
4655 };
4656
4657 DacCarousel.prototype.initPagination = function() {
4658 this.pagination = $([]);
4659 if (!this.options.pagination) { return; }
4660
4661 var pagination = $('<ul class="dac-pagination">');
4662 var parent = this.el;
4663 if (typeof this.options.pagination === 'string') { parent = this.el.find(this.options.pagination); }
4664
4665 if (this.count > 1) {
4666 for (var i = 0; i < this.count; i++) {
4667 var li = $('<li class="dac-pagination-item">').text(i);
4668 if (i === this.options.start) { li.addClass('active'); }
4669 li.click(this.go.bind(this, i));
4670
4671 pagination.append(li);
4672 }
4673 this.pagination = pagination.children();
4674 parent.append(pagination);
4675 }
4676 };
4677
4678 DacCarousel.prototype.initEvents = function() {
4679 var that = this;
4680
4681 this.el.hover(function() {
4682 that.pauseRotateTimer();
4683 }, function() {
4684 that.startRotateTimer();
4685 });
4686
4687 $(this.options.btnPrev).click(function(e) {
4688 e.preventDefault();
4689 that.prev();
4690 });
4691
4692 $(this.options.btnNext).click(function(e) {
4693 e.preventDefault();
4694 that.next();
4695 });
4696 };
4697
4698 DacCarousel.prototype.initFrame = function() {
4699 this.frames.removeClass('active').eq(this.options.start).addClass('active');
4700 };
4701
4702 DacCarousel.prototype.startRotateTimer = function() {
4703 if (!this.options.auto || this.rotateTimer) { return; }
4704 this.rotateTimer = setTimeout(this.next.bind(this), this.options.autoTime);
4705 };
4706
4707 DacCarousel.prototype.pauseRotateTimer = function() {
4708 clearTimeout(this.rotateTimer);
4709 this.rotateTimer = null;
4710 };
4711
4712 DacCarousel.prototype.prev = function() {
4713 this.go(this.current - 1);
4714 };
4715
4716 DacCarousel.prototype.next = function() {
4717 this.go(this.current + 1);
4718 };
4719
4720 DacCarousel.prototype.go = function(next) {
4721 // Figure out what the next slide is.
4722 while (this.count > 0 && next >= this.count) { next -= this.count; }
4723 while (next < 0) { next += this.count; }
4724
4725 // Cancel if we're already on that slide.
4726 if (next === this.current) { return; }
4727
4728 // Prepare next slide.
4729 this.frames.eq(next).removeClass('out');
4730
4731 // Recalculate styles before starting slide transition.
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004732 this.el.resolveStyles();
4733 // Update pagination
4734 this.pagination.removeClass('active').eq(next).addClass('active');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004735
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004736 // Transition out current frame
4737 this.frames.eq(this.current).toggleClass('active out');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004738
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004739 // Transition in a new frame
4740 this.frames.eq(next).toggleClass('active');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004741
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004742 this.current = next;
Dirk Dougherty29e93432015-05-05 18:17:13 -07004743 };
4744
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004745 // Helper which resolves new styles for an element, so it can start transitioning
4746 // from the new values.
4747 $.fn.resolveStyles = function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004748 /*jshint expr:true*/
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004749 this[0] && this[0].offsetTop;
4750 return this;
4751 };
Dirk Dougherty29e93432015-05-05 18:17:13 -07004752
4753 // jQuery plugin
4754 $.fn.dacCarousel = function() {
4755 this.each(function() {
4756 var $el = $(this);
4757 $el.data('dac-carousel', new DacCarousel(this));
4758 });
4759 return this;
4760 };
4761
4762 // Data API
4763 $(function() {
4764 $('[data-carousel]').dacCarousel();
4765 });
4766})(jQuery);
4767
4768(function($) {
4769 'use strict';
4770
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004771 function Modal(el, options) {
4772 this.el = $(el);
4773 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
4774 this.isOpen = false;
4775
4776 this.el.on('click', function(event) {
4777 if (!$.contains($('.dac-modal-window')[0], event.target)) {
4778 return this.close_();
4779 }
4780 }.bind(this));
4781
4782 this.el.on('open', this.open_.bind(this));
4783 this.el.on('close', this.close_.bind(this));
4784 this.el.on('toggle', this.toggle_.bind(this));
4785 }
4786
4787 Modal.prototype.toggle_ = function() {
4788 if (this.isOpen) {
4789 this.close_();
4790 } else {
4791 this.open_();
4792 }
4793 };
4794
4795 Modal.prototype.close_ = function() {
4796 this.el.removeClass('dac-active');
4797 $('body').removeClass('dac-modal-open');
4798 this.isOpen = false;
4799 };
4800
4801 Modal.prototype.open_ = function() {
4802 this.el.addClass('dac-active');
4803 $('body').addClass('dac-modal-open');
4804 this.isOpen = true;
4805 };
4806
Dirk Dougherty29e93432015-05-05 18:17:13 -07004807 function ToggleModal(el, options) {
4808 this.el = $(el);
4809 this.options = $.extend({}, ToggleModal.DEFAULTS_, options);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004810 this.modal = this.options.modalToggle ? $('[data-modal="' + this.options.modalToggle + '"]') :
4811 this.el.closest('[data-modal]');
4812
Dirk Dougherty29e93432015-05-05 18:17:13 -07004813 this.el.on('click', this.clickHandler_.bind(this));
4814 }
4815
Dirk Dougherty29e93432015-05-05 18:17:13 -07004816 ToggleModal.prototype.clickHandler_ = function(event) {
4817 event.preventDefault();
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004818 this.modal.trigger('toggle');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004819 };
4820
4821 /**
4822 * jQuery plugin
4823 * @param {object} options - Override default options.
4824 */
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004825 $.fn.dacModal = function(options) {
4826 return this.each(function() {
4827 new Modal(this, options);
4828 });
4829 };
4830
Dirk Dougherty29e93432015-05-05 18:17:13 -07004831 $.fn.dacToggleModal = function(options) {
4832 return this.each(function() {
4833 new ToggleModal(this, options);
4834 });
4835 };
4836
4837 /**
4838 * Data Attribute API
4839 */
4840 $(document).on('ready.aranja', function() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004841 $('[data-modal]').each(function() {
4842 $(this).dacModal($(this).data());
4843 });
4844
4845 $('[data-modal-toggle]').each(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004846 $(this).dacToggleModal($(this).data());
4847 });
4848 });
4849})(jQuery);
4850
4851(function($) {
4852 'use strict';
4853
4854 /**
4855 * Toggle the visabilty of the mobile navigation.
4856 * @param {HTMLElement} el - The DOM element.
4857 * @param options
4858 * @constructor
4859 */
4860 function ToggleNav(el, options) {
4861 this.el = $(el);
4862 this.options = $.extend({}, ToggleNav.DEFAULTS_, options);
4863 this.options.target = [this.options.navigation];
4864
4865 if (this.options.body) {this.options.target.push('body')}
4866 if (this.options.dimmer) {this.options.target.push(this.options.dimmer)}
4867
4868 this.el.on('click', this.clickHandler_.bind(this));
4869 }
4870
4871 /**
4872 * ToggleNav Default Settings
4873 * @type {{body: boolean, dimmer: string, navigation: string, toggleClass: string}}
4874 * @private
4875 */
4876 ToggleNav.DEFAULTS_ = {
4877 body: true,
4878 dimmer: '.dac-nav-dimmer',
4879 navigation: '[data-dac-nav]',
4880 toggleClass: 'dac-nav-open'
4881 };
4882
4883 /**
4884 * The actual toggle logic.
4885 * @param event
4886 * @private
4887 */
4888 ToggleNav.prototype.clickHandler_ = function(event) {
4889 event.preventDefault();
4890 $(this.options.target.join(', ')).toggleClass(this.options.toggleClass);
4891 };
4892
4893 /**
4894 * jQuery plugin
4895 * @param {object} options - Override default options.
4896 */
4897 $.fn.dacToggleMobileNav = function(options) {
4898 return this.each(function() {
4899 new ToggleNav(this, options);
4900 });
4901 };
4902
4903 /**
4904 * Data Attribute API
4905 */
4906 $(window).on('load.aranja', function() {
4907 $('[data-dac-toggle-nav]').each(function() {
4908 $(this).dacToggleMobileNav($(this).data());
4909 });
4910 });
4911})(jQuery);
4912
4913(function($) {
4914 'use strict';
4915
4916 /**
4917 * Submit the newsletter form to a Google Form.
4918 * @param {HTMLElement} el - The Form DOM element.
4919 * @constructor
4920 */
4921 function NewsletterForm(el) {
4922 this.el = $(el);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004923 this.form = this.el.find('form');
4924 $('<iframe/>').hide()
4925 .attr('name', 'dac-newsletter-iframe')
4926 .attr('src', '')
4927 .insertBefore(this.form);
4928 this.form.on('submit', this.submitHandler_.bind(this));
Dirk Dougherty29e93432015-05-05 18:17:13 -07004929 }
4930
4931 /**
4932 * Close the modal when the form is sent.
4933 * @private
4934 */
4935 NewsletterForm.prototype.submitHandler_ = function() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004936 this.form.trigger('reset');
4937 this.el.trigger('close');
Dirk Dougherty29e93432015-05-05 18:17:13 -07004938 };
4939
4940 /**
4941 * jQuery plugin
4942 * @param {object} options - Override default options.
4943 */
4944 $.fn.dacNewsletterForm = function(options) {
4945 return this.each(function() {
4946 new NewsletterForm(this, options);
4947 });
4948 };
4949
4950 /**
4951 * Data Attribute API
4952 */
4953 $(document).on('ready.aranja', function() {
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004954 $('[data-newsletter]').each(function() {
Dirk Dougherty29e93432015-05-05 18:17:13 -07004955 $(this).dacNewsletterForm();
4956 });
4957 });
4958})(jQuery);
Dirk Doughertyf97b2ef2015-05-12 21:23:05 -07004959
4960(function($) {
4961 'use strict';
4962
4963 /**
4964 * Smoothly scroll to location on current page.
4965 * @param el
4966 * @param options
4967 * @constructor
4968 */
4969 function ScrollButton(el, options) {
4970 this.el = $(el);
4971 this.target = $(this.el.attr('href'));
4972 this.options = $.extend({}, ScrollButton.DEFAULTS_, options);
4973
4974 if (typeof this.options.offset === 'string') {
4975 this.options.offset = $(this.options.offset).height();
4976 }
4977
4978 this.el.on('click', this.clickHandler_.bind(this));
4979 }
4980
4981 /**
4982 * Default options
4983 * @type {{duration: number, easing: string, offset: number, scrollContainer: string}}
4984 * @private
4985 */
4986 ScrollButton.DEFAULTS_ = {
4987 duration: 300,
4988 easing: 'swing',
4989 offset: 0,
4990 scrollContainer: 'html, body'
4991 };
4992
4993 /**
4994 * Scroll logic
4995 * @param event
4996 * @private
4997 */
4998 ScrollButton.prototype.clickHandler_ = function(event) {
4999 if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
5000 return;
5001 }
5002
5003 event.preventDefault();
5004
5005 $(this.options.scrollContainer).animate({
5006 scrollTop: this.target.offset().top - this.options.offset
5007 }, this.options);
5008 };
5009
5010 /**
5011 * jQuery plugin
5012 * @param {object} options - Override default options.
5013 */
5014 $.fn.dacScrollButton = function(options) {
5015 return this.each(function() {
5016 new ScrollButton(this, options);
5017 });
5018 };
5019
5020 /**
5021 * Data Attribute API
5022 */
5023 $(document).on('ready.aranja', function() {
5024 $('[data-scroll-button]').each(function() {
5025 $(this).dacScrollButton($(this).data());
5026 });
5027 });
5028})(jQuery);
5029
5030(function($) {
5031 function Toggle(el) {
5032 $(el).on('click.dac.togglesection', this.toggle);
5033 }
5034
5035 Toggle.prototype.toggle = function() {
5036 var $this = $(this);
5037
5038 var $parent = getParent($this);
5039 var isExpanded = $parent.hasClass('is-expanded');
5040
5041 transitionMaxHeight($parent.find('.dac-toggle-content'), !isExpanded);
5042 $parent.toggleClass('is-expanded');
5043
5044 return false;
5045 };
5046
5047 function getParent($this) {
5048 var selector = $this.attr('data-target');
5049
5050 if (!selector) {
5051 selector = $this.attr('href');
5052 selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '');
5053 }
5054
5055 var $parent = selector && $(selector);
5056
5057 return $parent && $parent.length ? $parent : $this.parent();
5058 }
5059
5060 /**
5061 * Runs a transition of max-height along with responsive styles which hide or expand the element.
5062 * @param $el
5063 * @param visible
5064 */
5065 function transitionMaxHeight($el, visible) {
5066 // Only supports 1 child
5067 var contentHeight = $el.children().outerHeight();
5068 var targetHeight = visible ? contentHeight : 0;
5069 var duration = $el.transitionDuration();
5070
5071 // If we're hiding, first set the maxHeight we're transitioning from.
5072 if (!visible) {
5073 $el.css('maxHeight', contentHeight + 'px')
5074 .resolveStyles();
5075 }
5076
5077 // Transition to new state
5078 $el.css('maxHeight', targetHeight);
5079
5080 // Reset maxHeight to css value after transition.
5081 setTimeout(function() {
5082 $el.css('maxHeight', '');
5083 }, duration);
5084 }
5085
5086 // Utility to get the transition duration for the element.
5087 $.fn.transitionDuration = function() {
5088 var d = $(this).css('transitionDuration') || '0s';
5089
5090 return +(parseFloat(d) * (/ms/.test(d) ? 1 : 1000)).toFixed(0);
5091 };
5092
5093 // jQuery plugin
5094 $.fn.toggleSection = function(option) {
5095 return this.each(function() {
5096 var $this = $(this);
5097 var data = $this.data('dac.togglesection');
5098 if (!data) {$this.data('dac.togglesection', (data = new Toggle(this)));}
5099 if (typeof option === 'string') {data[option].call($this);}
5100 });
5101 };
5102
5103 // Data api
5104 $(document)
5105 .on('click.toggle', '[data-toggle="section"]', Toggle.prototype.toggle);
5106})(jQuery);