blob: e3b717503da1bfa99fb081d47b2ff410f7212544 [file] [log] [blame]
Scott Maine4d8f1b2012-06-21 18:03:05 -07001var classesNav;
2var devdocNav;
3var sidenav;
4var cookie_namespace = 'android_developer';
5var NAV_PREF_TREE = "tree";
6var NAV_PREF_PANELS = "panels";
7var nav_pref;
Scott Maine4d8f1b2012-06-21 18:03:05 -07008var isMobile = false; // true if mobile, so we can adjust some layout
Scott Mainf6145542013-04-01 16:38:11 -07009var mPagePath; // initialized in ready() function
Scott Maine4d8f1b2012-06-21 18:03:05 -070010
Scott Main1b3db112012-07-03 14:06:22 -070011var basePath = getBaseUri(location.pathname);
12var SITE_ROOT = toRoot + basePath.substring(1,basePath.indexOf("/",1));
Scott Main7e447ed2013-02-19 17:22:37 -080013var GOOGLE_DATA; // combined data for google service apis, used for search suggest
Scott Main3b90aff2013-08-01 18:09:35 -070014
Scott Main25e73002013-03-27 15:24:06 -070015// Ensure that all ajax getScript() requests allow caching
16$.ajaxSetup({
17 cache: true
18});
Scott Maine4d8f1b2012-06-21 18:03:05 -070019
20/****** ON LOAD SET UP STUFF *********/
21
Scott Maine4d8f1b2012-06-21 18:03:05 -070022$(document).ready(function() {
smain@google.com698fff02014-11-20 20:39:33 -080023
Dirk Doughertyb87e3002014-11-18 19:34:34 -080024 // show lang dialog if the URL includes /intl/
25 //if (location.pathname.substring(0,6) == "/intl/") {
26 // var lang = location.pathname.split('/')[2];
27 // if (lang != getLangPref()) {
28 // $("#langMessage a.yes").attr("onclick","changeLangPref('" + lang
29 // + "', true); $('#langMessage').hide(); return false;");
30 // $("#langMessage .lang." + lang).show();
31 // $("#langMessage").show();
32 // }
33 //}
Scott Main7e447ed2013-02-19 17:22:37 -080034
Scott Main0e76e7e2013-03-12 10:24:07 -070035 // load json file for JD doc search suggestions
Scott Main719acb42013-12-05 16:05:09 -080036 $.getScript(toRoot + 'jd_lists_unified.js');
Scott Main7e447ed2013-02-19 17:22:37 -080037 // load json file for Android API search suggestions
38 $.getScript(toRoot + 'reference/lists.js');
39 // load json files for Google services API suggestions
Scott Main9f2971d2013-02-26 13:07:41 -080040 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080041 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
42 if(jqxhr.status === 200) {
Scott Main9f2971d2013-02-26 13:07:41 -080043 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080044 if(jqxhr.status === 200) {
45 // combine GCM and GMS data
46 GOOGLE_DATA = GMS_DATA;
47 var start = GOOGLE_DATA.length;
48 for (var i=0; i<GCM_DATA.length; i++) {
49 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
50 link:GCM_DATA[i].link, type:GCM_DATA[i].type});
51 }
52 }
53 });
54 }
55 });
56
Scott Main0e76e7e2013-03-12 10:24:07 -070057 // setup keyboard listener for search shortcut
58 $('body').keyup(function(event) {
59 if (event.which == 191) {
60 $('#search_autocomplete').focus();
61 }
62 });
Scott Main015d6162013-01-29 09:01:52 -080063
Scott Maine4d8f1b2012-06-21 18:03:05 -070064 // init the fullscreen toggle click event
65 $('#nav-swap .fullscreen').click(function(){
66 if ($(this).hasClass('disabled')) {
67 toggleFullscreen(true);
68 } else {
69 toggleFullscreen(false);
70 }
71 });
Scott Main3b90aff2013-08-01 18:09:35 -070072
Scott Maine4d8f1b2012-06-21 18:03:05 -070073 // initialize the divs with custom scrollbars
74 $('.scroll-pane').jScrollPane( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -070075
Scott Maine4d8f1b2012-06-21 18:03:05 -070076 // add HRs below all H2s (except for a few other h2 variants)
Scott Mainc29b3f52014-05-30 21:18:30 -070077 $('h2').not('#qv h2')
78 .not('#tb h2')
79 .not('.sidebox h2')
80 .not('#devdoc-nav h2')
81 .not('h2.norule').css({marginBottom:0})
82 .after('<hr/>');
Scott Maine4d8f1b2012-06-21 18:03:05 -070083
84 // set up the search close button
85 $('.search .close').click(function() {
86 $searchInput = $('#search_autocomplete');
87 $searchInput.attr('value', '');
88 $(this).addClass("hide");
89 $("#search-container").removeClass('active');
90 $("#search_autocomplete").blur();
Scott Main0e76e7e2013-03-12 10:24:07 -070091 search_focus_changed($searchInput.get(), false);
92 hideResults();
Scott Maine4d8f1b2012-06-21 18:03:05 -070093 });
94
95 // Set up quicknav
Scott Main3b90aff2013-08-01 18:09:35 -070096 var quicknav_open = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -070097 $("#btn-quicknav").click(function() {
98 if (quicknav_open) {
99 $(this).removeClass('active');
100 quicknav_open = false;
101 collapse();
102 } else {
103 $(this).addClass('active');
104 quicknav_open = true;
105 expand();
106 }
107 })
Scott Main3b90aff2013-08-01 18:09:35 -0700108
Scott Maine4d8f1b2012-06-21 18:03:05 -0700109 var expand = function() {
110 $('#header-wrap').addClass('quicknav');
111 $('#quicknav').stop().show().animate({opacity:'1'});
112 }
Scott Main3b90aff2013-08-01 18:09:35 -0700113
Scott Maine4d8f1b2012-06-21 18:03:05 -0700114 var collapse = function() {
115 $('#quicknav').stop().animate({opacity:'0'}, 100, function() {
116 $(this).hide();
117 $('#header-wrap').removeClass('quicknav');
118 });
119 }
Scott Main3b90aff2013-08-01 18:09:35 -0700120
121
Scott Maine4d8f1b2012-06-21 18:03:05 -0700122 //Set up search
123 $("#search_autocomplete").focus(function() {
124 $("#search-container").addClass('active');
125 })
126 $("#search-container").mouseover(function() {
127 $("#search-container").addClass('active');
128 $("#search_autocomplete").focus();
129 })
130 $("#search-container").mouseout(function() {
131 if ($("#search_autocomplete").is(":focus")) return;
132 if ($("#search_autocomplete").val() == '') {
133 setTimeout(function(){
134 $("#search-container").removeClass('active');
135 $("#search_autocomplete").blur();
136 },250);
137 }
138 })
139 $("#search_autocomplete").blur(function() {
140 if ($("#search_autocomplete").val() == '') {
141 $("#search-container").removeClass('active');
142 }
143 })
144
Scott Main3b90aff2013-08-01 18:09:35 -0700145
Scott Maine4d8f1b2012-06-21 18:03:05 -0700146 // prep nav expandos
147 var pagePath = document.location.pathname;
148 // account for intl docs by removing the intl/*/ path
149 if (pagePath.indexOf("/intl/") == 0) {
150 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
151 }
Scott Mainac2aef52013-02-12 14:15:23 -0800152
Scott Maine4d8f1b2012-06-21 18:03:05 -0700153 if (pagePath.indexOf(SITE_ROOT) == 0) {
154 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
155 pagePath += 'index.html';
156 }
157 }
158
Scott Main01a25452013-02-12 17:32:27 -0800159 // Need a copy of the pagePath before it gets changed in the next block;
160 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
161 var pagePathOriginal = pagePath;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700162 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
163 // If running locally, SITE_ROOT will be a relative path, so account for that by
164 // finding the relative URL to this page. This will allow us to find links on the page
165 // leading back to this page.
166 var pathParts = pagePath.split('/');
167 var relativePagePathParts = [];
168 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
169 for (var i = 0; i < upDirs; i++) {
170 relativePagePathParts.push('..');
171 }
172 for (var i = 0; i < upDirs; i++) {
173 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
174 }
175 relativePagePathParts.push(pathParts[pathParts.length - 1]);
176 pagePath = relativePagePathParts.join('/');
177 } else {
178 // Otherwise the page path is already an absolute URL
179 }
180
Scott Mainac2aef52013-02-12 14:15:23 -0800181 // Highlight the header tabs...
182 // highlight Design tab
183 if ($("body").hasClass("design")) {
184 $("#header li.design a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700185 $("#sticky-header").addClass("design");
Scott Mainac2aef52013-02-12 14:15:23 -0800186
smain@google.com6040ffa2014-06-13 15:06:23 -0700187 // highlight About tabs
188 } else if ($("body").hasClass("about")) {
189 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
190 if (rootDir == "about") {
191 $("#nav-x li.about a").addClass("selected");
192 } else if (rootDir == "wear") {
193 $("#nav-x li.wear a").addClass("selected");
194 } else if (rootDir == "tv") {
195 $("#nav-x li.tv a").addClass("selected");
196 } else if (rootDir == "auto") {
197 $("#nav-x li.auto a").addClass("selected");
198 }
Scott Mainac2aef52013-02-12 14:15:23 -0800199 // highlight Develop tab
200 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
201 $("#header li.develop a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700202 $("#sticky-header").addClass("develop");
Scott Mainac2aef52013-02-12 14:15:23 -0800203 // In Develop docs, also highlight appropriate sub-tab
Scott Main01a25452013-02-12 17:32:27 -0800204 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
Scott Mainac2aef52013-02-12 14:15:23 -0800205 if (rootDir == "training") {
206 $("#nav-x li.training a").addClass("selected");
207 } else if (rootDir == "guide") {
208 $("#nav-x li.guide a").addClass("selected");
209 } else if (rootDir == "reference") {
210 // If the root is reference, but page is also part of Google Services, select Google
211 if ($("body").hasClass("google")) {
212 $("#nav-x li.google a").addClass("selected");
213 } else {
214 $("#nav-x li.reference a").addClass("selected");
215 }
216 } else if ((rootDir == "tools") || (rootDir == "sdk")) {
217 $("#nav-x li.tools a").addClass("selected");
218 } else if ($("body").hasClass("google")) {
219 $("#nav-x li.google a").addClass("selected");
Dirk Dougherty4f7e5152010-09-16 10:43:40 -0700220 } else if ($("body").hasClass("samples")) {
221 $("#nav-x li.samples a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800222 }
223
224 // highlight Distribute tab
225 } else if ($("body").hasClass("distribute")) {
226 $("#header li.distribute a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700227 $("#sticky-header").addClass("distribute");
228
229 var baseFrag = pagePathOriginal.indexOf('/', 1) + 1;
230 var secondFrag = pagePathOriginal.substring(baseFrag, pagePathOriginal.indexOf('/', baseFrag));
231 if (secondFrag == "users") {
232 $("#nav-x li.users a").addClass("selected");
233 } else if (secondFrag == "engage") {
234 $("#nav-x li.engage a").addClass("selected");
235 } else if (secondFrag == "monetize") {
236 $("#nav-x li.monetize a").addClass("selected");
237 } else if (secondFrag == "tools") {
238 $("#nav-x li.disttools a").addClass("selected");
239 } else if (secondFrag == "stories") {
240 $("#nav-x li.stories a").addClass("selected");
241 } else if (secondFrag == "essentials") {
242 $("#nav-x li.essentials a").addClass("selected");
243 } else if (secondFrag == "googleplay") {
244 $("#nav-x li.googleplay a").addClass("selected");
245 }
246 } else if ($("body").hasClass("about")) {
247 $("#sticky-header").addClass("about");
Scott Mainb16376f2014-05-21 20:35:47 -0700248 }
Scott Mainac2aef52013-02-12 14:15:23 -0800249
Scott Mainf6145542013-04-01 16:38:11 -0700250 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
251 // and highlight the sidenav
252 mPagePath = pagePath;
253 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700254 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800255
Scott Mainf6145542013-04-01 16:38:11 -0700256 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700257 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700258 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700259 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800260 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700261
262 // set up prev links
263 var $prevLink = [];
264 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700265
Scott Maine4d8f1b2012-06-21 18:03:05 -0700266 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
267false; // navigate across topic boundaries only in design docs
268 if ($prevListItem.length) {
smain@google.comc91ecb72014-06-23 10:22:23 -0700269 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Scott Main5a1123e2012-09-26 12:51:28 -0700270 // jump to last topic of previous section
271 $prevLink = $prevListItem.find('a:last');
272 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700273 // jump to previous topic in this section
274 $prevLink = $prevListItem.find('a:eq(0)');
275 }
276 } else {
277 // jump to this section's index page (if it exists)
278 var $parentListItem = $selListItem.parents('li');
279 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700280
Scott Maine4d8f1b2012-06-21 18:03:05 -0700281 // except if cross boundaries aren't allowed, and we're at the top of a section already
282 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700283 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700284 && $selListItem.hasClass('nav-section')) {
285 $prevLink = [];
286 }
287 }
288
Scott Maine4d8f1b2012-06-21 18:03:05 -0700289 // set up next links
290 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700291 var startClass = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700292 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700293
Scott Main1a00f7f2013-10-29 11:11:19 -0700294 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700295 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700296 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700297
298 // if there aren't any children, go to the next section (required for About pages)
299 if($nextLink.length == 0) {
300 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700301 } else if ($('.topic-start-link').length) {
302 // as long as there's a child link and there is a "topic start link" (we're on a landing)
303 // then set the landing page "start link" text to be the first doc title
304 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700305 }
Scott Main3b90aff2013-08-01 18:09:35 -0700306
Scott Main5a1123e2012-09-26 12:51:28 -0700307 // If the selected page has a description, then it's a class or article homepage
308 if ($selListItem.find('a[description]').length) {
309 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700310 startClass = true;
311 }
312 } else {
313 // jump to the next topic in this section (if it exists)
314 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700315 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700316 isCrossingBoundary = true;
317 // no more topics in this section, jump to the first topic in the next section
smain@google.comabf34112014-06-23 11:39:02 -0700318 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Scott Main5a1123e2012-09-26 12:51:28 -0700319 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
320 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700321 if ($nextLink.length == 0) {
322 // if that doesn't work, we're at the end of the list, so disable NEXT link
323 $('.next-page-link').attr('href','').addClass("disabled")
324 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700325 // and completely hide the one in the footer
326 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700327 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700328 }
329 }
330 }
Scott Main5a1123e2012-09-26 12:51:28 -0700331
332 if (startClass) {
333 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
334
Scott Main3b90aff2013-08-01 18:09:35 -0700335 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700336 // then we need to add a bottom border to button
337 if (!$("#tb").length) {
338 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700339 }
Scott Main5a1123e2012-09-26 12:51:28 -0700340 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
341 $('.content-footer.next-class').show();
342 $('.next-page-link').attr('href','')
343 .removeClass("hide").addClass("disabled")
344 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700345 // and completely hide the one in the footer
346 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700347 if ($nextLink.length) {
348 $('.next-class-link').attr('href',$nextLink.attr('href'))
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700349 .removeClass("hide")
350 .append(": " + $nextLink.html());
Scott Main1a00f7f2013-10-29 11:11:19 -0700351 $('.next-class-link').find('.new').empty();
352 }
Scott Main5a1123e2012-09-26 12:51:28 -0700353 } else {
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700354 $('.next-page-link').attr('href', $nextLink.attr('href'))
355 .removeClass("hide");
356 // for the footer link, also add the next page title
357 $('.content-footer .next-page-link').append(": " + $nextLink.html());
Scott Main5a1123e2012-09-26 12:51:28 -0700358 }
359
360 if (!startClass && $prevLink.length) {
361 var prevHref = $prevLink.attr('href');
362 if (prevHref == SITE_ROOT + 'index.html') {
363 // Don't show Previous when it leads to the homepage
364 } else {
365 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
366 }
Scott Main3b90aff2013-08-01 18:09:35 -0700367 }
Scott Main5a1123e2012-09-26 12:51:28 -0700368
Scott Maine4d8f1b2012-06-21 18:03:05 -0700369 }
Scott Main3b90aff2013-08-01 18:09:35 -0700370
371
372
Scott Main5a1123e2012-09-26 12:51:28 -0700373 // Set up the course landing pages for Training with class names and descriptions
374 if ($('body.trainingcourse').length) {
375 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700376
377 // create an array for all the class descriptions
378 var $classDescriptions = new Array($classLinks.length);
379 var lang = getLangPref();
380 $classLinks.each(function(index) {
381 var langDescr = $(this).attr(lang + "-description");
382 if (typeof langDescr !== 'undefined' && langDescr !== false) {
383 // if there's a class description in the selected language, use that
384 $classDescriptions[index] = langDescr;
385 } else {
386 // otherwise, use the default english description
387 $classDescriptions[index] = $(this).attr("description");
388 }
389 });
Scott Main3b90aff2013-08-01 18:09:35 -0700390
Scott Main5a1123e2012-09-26 12:51:28 -0700391 var $olClasses = $('<ol class="class-list"></ol>');
392 var $liClass;
393 var $imgIcon;
394 var $h2Title;
395 var $pSummary;
396 var $olLessons;
397 var $liLesson;
398 $classLinks.each(function(index) {
399 $liClass = $('<li></li>');
400 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700401 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700402
Scott Main5a1123e2012-09-26 12:51:28 -0700403 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700404
Scott Main5a1123e2012-09-26 12:51:28 -0700405 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700406
Scott Main5a1123e2012-09-26 12:51:28 -0700407 if ($lessons.length) {
Scott Main3b90aff2013-08-01 18:09:35 -0700408 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-tutorial.png" '
409 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700410 $lessons.each(function(index) {
411 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
412 });
413 } else {
Scott Main3b90aff2013-08-01 18:09:35 -0700414 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-article.png" '
415 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700416 $pSummary.addClass('article');
417 }
418
419 $liClass.append($h2Title).append($imgIcon).append($pSummary).append($olLessons);
420 $olClasses.append($liClass);
421 });
422 $('.jd-descr').append($olClasses);
423 }
424
Scott Maine4d8f1b2012-06-21 18:03:05 -0700425 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700426 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700427
Scott Main3b90aff2013-08-01 18:09:35 -0700428
Scott Maine4d8f1b2012-06-21 18:03:05 -0700429 $(".scroll-pane").scroll(function(event) {
430 event.preventDefault();
431 return false;
432 });
433
434 /* Resize nav height when window height changes */
435 $(window).resize(function() {
436 if ($('#side-nav').length == 0) return;
437 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
438 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
439 // make sidenav behave when resizing the window and side-scolling is a concern
Scott Mainf5257812014-05-22 17:26:38 -0700440 if (sticky) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700441 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
442 updateSideNavPosition();
443 } else {
444 updateSidenavFullscreenWidth();
445 }
446 }
447 resizeNav();
448 });
449
450
Scott Maine4d8f1b2012-06-21 18:03:05 -0700451 var navBarLeftPos;
452 if ($('#devdoc-nav').length) {
453 setNavBarLeftPos();
454 }
455
456
Scott Maine4d8f1b2012-06-21 18:03:05 -0700457 // Set up play-on-hover <video> tags.
458 $('video.play-on-hover').bind('click', function(){
459 $(this).get(0).load(); // in case the video isn't seekable
460 $(this).get(0).play();
461 });
462
463 // Set up tooltips
464 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700465 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700466 var $target = $(this);
467 var $tooltip = $('<div>')
468 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700469 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700470 .hide()
471 .appendTo('body');
472 $target.removeAttr('title');
473
474 $target.hover(function() {
475 // in
476 var targetRect = $target.offset();
477 targetRect.width = $target.width();
478 targetRect.height = $target.height();
479
480 $tooltip.css({
481 left: targetRect.left,
482 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
483 });
484 $tooltip.addClass('below');
485 $tooltip.show();
486 }, function() {
487 // out
488 $tooltip.hide();
489 });
490 });
491
492 // Set up <h2> deeplinks
493 $('h2').click(function() {
494 var id = $(this).attr('id');
495 if (id) {
496 document.location.hash = id;
497 }
498 });
499
500 //Loads the +1 button
501 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
502 po.src = 'https://apis.google.com/js/plusone.js';
503 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
504
505
Scott Main3b90aff2013-08-01 18:09:35 -0700506 // Revise the sidenav widths to make room for the scrollbar
Scott Maine4d8f1b2012-06-21 18:03:05 -0700507 // which avoids the visible width from changing each time the bar appears
508 var $sidenav = $("#side-nav");
509 var sidenav_width = parseInt($sidenav.innerWidth());
Scott Main3b90aff2013-08-01 18:09:35 -0700510
Scott Maine4d8f1b2012-06-21 18:03:05 -0700511 $("#devdoc-nav #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width
512
513
514 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700515
Scott Maine4d8f1b2012-06-21 18:03:05 -0700516 if ($(".scroll-pane").length > 1) {
517 // Check if there's a user preference for the panel heights
518 var cookieHeight = readCookie("reference_height");
519 if (cookieHeight) {
520 restoreHeight(cookieHeight);
521 }
522 }
Scott Main3b90aff2013-08-01 18:09:35 -0700523
Scott Main06f3f2c2014-05-30 11:23:00 -0700524 // Resize once loading is finished
Scott Maine4d8f1b2012-06-21 18:03:05 -0700525 resizeNav();
Scott Main06f3f2c2014-05-30 11:23:00 -0700526 // Check if there's an anchor that we need to scroll into view.
527 // A delay is needed, because some browsers do not immediately scroll down to the anchor
528 window.setTimeout(offsetScrollForSticky, 100);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700529
Scott Main015d6162013-01-29 09:01:52 -0800530 /* init the language selector based on user cookie for lang */
531 loadLangPref();
532 changeNavLang(getLangPref());
533
534 /* setup event handlers to ensure the overflow menu is visible while picking lang */
535 $("#language select")
536 .mousedown(function() {
537 $("div.morehover").addClass("hover"); })
538 .blur(function() {
539 $("div.morehover").removeClass("hover"); });
540
541 /* some global variable setup */
542 resizePackagesNav = $("#resize-packages-nav");
543 classesNav = $("#classes-nav");
544 devdocNav = $("#devdoc-nav");
545
546 var cookiePath = "";
547 if (location.href.indexOf("/reference/") != -1) {
548 cookiePath = "reference_";
549 } else if (location.href.indexOf("/guide/") != -1) {
550 cookiePath = "guide_";
551 } else if (location.href.indexOf("/tools/") != -1) {
552 cookiePath = "tools_";
553 } else if (location.href.indexOf("/training/") != -1) {
554 cookiePath = "training_";
555 } else if (location.href.indexOf("/design/") != -1) {
556 cookiePath = "design_";
557 } else if (location.href.indexOf("/distribute/") != -1) {
558 cookiePath = "distribute_";
559 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700560
smain@google.com698fff02014-11-20 20:39:33 -0800561
562 /* setup shadowbox for any videos that want it */
smain@google.comf75ee212014-11-24 09:42:59 -0800563 var $videoLinks = $("a.video-shadowbox-button, a.notice-developers-video");
smain@google.com698fff02014-11-20 20:39:33 -0800564 if ($videoLinks.length) {
565 // if there's at least one, add the shadowbox HTML to the body
566 $('body').prepend(
567'<div id="video-container">'+
568 '<div id="video-frame">'+
569 '<div class="video-close">'+
570 '<span id="icon-video-close" onclick="closeVideo()">&nbsp;</span>'+
571 '</div>'+
572 '<div id="youTubePlayer"></div>'+
573 '</div>'+
574'</div>');
575
576 // loads the IFrame Player API code asynchronously.
577 $.getScript("https://www.youtube.com/iframe_api");
578
579 $videoLinks.each(function() {
580 var videoId = $(this).attr('href').split('?v=')[1];
581 $(this).click(function(event) {
582 event.preventDefault();
583 startYouTubePlayer(videoId);
584 });
585 });
smain@google.com698fff02014-11-20 20:39:33 -0800586 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700587});
Scott Main7e447ed2013-02-19 17:22:37 -0800588// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700589
590
smain@google.com698fff02014-11-20 20:39:33 -0800591var youTubePlayer;
592function onYouTubeIframeAPIReady() {
593}
594
595function startYouTubePlayer(videoId) {
smain@google.com570c2212014-11-25 19:01:40 -0800596 var idAndHash = videoId.split("#");
597 var startTime = 0;
598 if (idAndHash.length > 1) {
599 startTime = idAndHash[1].split("t=")[1] != undefined ? idAndHash[1].split("t=")[1] : 0;
600 }
smain@google.com698fff02014-11-20 20:39:33 -0800601 if (youTubePlayer == null) {
602 youTubePlayer = new YT.Player('youTubePlayer', {
603 height: '529',
604 width: '940',
smain@google.com570c2212014-11-25 19:01:40 -0800605 videoId: idAndHash[0],
606 playerVars: {start: startTime},
smain@google.com698fff02014-11-20 20:39:33 -0800607 events: {
smain@google.comf75ee212014-11-24 09:42:59 -0800608 'onReady': onPlayerReady,
609 'onStateChange': onPlayerStateChange
smain@google.com698fff02014-11-20 20:39:33 -0800610 }
611 });
612 } else {
613 youTubePlayer.playVideo();
614 }
615 $("#video-container").fadeIn(200, function(){$("#video-frame").show()});
616}
617
618function onPlayerReady(event) {
619 event.target.playVideo();
smain@google.comd24088c2014-12-12 11:31:13 -0800620 // track the start playing event so we know from which page the video was selected
621 ga('send', 'event', 'Videos', 'Start: ' +
622 youTubePlayer.getVideoUrl().split('?v=')[1], 'on: ' + document.location.href);
smain@google.com698fff02014-11-20 20:39:33 -0800623}
624
625function closeVideo() {
626 try {
smain@google.comf75ee212014-11-24 09:42:59 -0800627 youTubePlayer.pauseVideo();
smain@google.com698fff02014-11-20 20:39:33 -0800628 $("#video-container").fadeOut(200);
629 } catch(e) {
630 console.log('Video not available');
631 $("#video-container").fadeOut(200);
632 }
633}
634
smain@google.comf75ee212014-11-24 09:42:59 -0800635/* Track youtube playback for analytics */
636function onPlayerStateChange(event) {
637 // Video starts, send the video ID
638 if (event.data == YT.PlayerState.PLAYING) {
smain@google.comd24088c2014-12-12 11:31:13 -0800639 ga('send', 'event', 'Videos', 'Play',
640 youTubePlayer.getVideoUrl().split('?v=')[1]);
smain@google.comf75ee212014-11-24 09:42:59 -0800641 }
642 // Video paused, send video ID and video elapsed time
643 if (event.data == YT.PlayerState.PAUSED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800644 ga('send', 'event', 'Videos', 'Paused',
645 youTubePlayer.getVideoUrl().split('?v=')[1], youTubePlayer.getCurrentTime());
smain@google.comf75ee212014-11-24 09:42:59 -0800646 }
647 // Video finished, send video ID and video elapsed time
648 if (event.data == YT.PlayerState.ENDED) {
smain@google.comd24088c2014-12-12 11:31:13 -0800649 ga('send', 'event', 'Videos', 'Finished',
650 youTubePlayer.getVideoUrl().split('?v=')[1], youTubePlayer.getCurrentTime());
smain@google.comf75ee212014-11-24 09:42:59 -0800651 }
652}
653
smain@google.com698fff02014-11-20 20:39:33 -0800654
655
Scott Mainad08f072013-08-20 16:49:57 -0700656function initExpandableNavItems(rootTag) {
657 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
658 var section = $(this).closest('li.nav-section');
659 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700660 /* hide me and descendants */
661 section.find('ul').slideUp(250, function() {
662 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700663 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700664 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700665 resizeNav();
666 });
667 } else {
668 /* show me */
669 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700670 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700671 $others.removeClass('expanded').children('ul').slideUp(250);
672
673 // now expand me
674 section.closest('li').addClass('expanded');
675 section.children('ul').slideDown(250, function() {
676 resizeNav();
677 });
678 }
679 });
Scott Mainf0093852013-08-22 11:37:11 -0700680
681 // Stop expand/collapse behavior when clicking on nav section links
682 // (since we're navigating away from the page)
683 // This selector captures the first instance of <a>, but not those with "#" as the href.
684 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
685 window.location.href = $(this).attr('href');
686 return false;
687 });
Scott Mainad08f072013-08-20 16:49:57 -0700688}
689
Dirk Doughertyc3921652014-05-13 16:55:26 -0700690
691/** Create the list of breadcrumb links in the sticky header */
692function buildBreadcrumbs() {
693 var $breadcrumbUl = $("#sticky-header ul.breadcrumb");
694 // Add the secondary horizontal nav item, if provided
695 var $selectedSecondNav = $("div#nav-x ul.nav-x a.selected").clone().removeClass("selected");
696 if ($selectedSecondNav.length) {
697 $breadcrumbUl.prepend($("<li>").append($selectedSecondNav))
698 }
699 // Add the primary horizontal nav
700 var $selectedFirstNav = $("div#header-wrap ul.nav-x a.selected").clone().removeClass("selected");
701 // If there's no header nav item, use the logo link and title from alt text
702 if ($selectedFirstNav.length < 1) {
703 $selectedFirstNav = $("<a>")
704 .attr('href', $("div#header .logo a").attr('href'))
705 .text($("div#header .logo img").attr('alt'));
706 }
707 $breadcrumbUl.prepend($("<li>").append($selectedFirstNav));
708}
709
710
711
Scott Maine624b3f2013-09-12 12:56:41 -0700712/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700713function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700714 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
715 if ($("ul#nav li.selected").length) {
716 unHighlightSidenav();
717 }
718 // look for URL in sidenav, including the hash
719 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
720
721 // If the selNavLink is still empty, look for it without the hash
722 if ($selNavLink.length == 0) {
723 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
724 }
725
Scott Mainf6145542013-04-01 16:38:11 -0700726 var $selListItem;
727 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700728 // Find this page's <li> in sidenav and set selected
729 $selListItem = $selNavLink.closest('li');
730 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700731
Scott Mainf6145542013-04-01 16:38:11 -0700732 // Traverse up the tree and expand all parent nav-sections
733 $selNavLink.parents('li.nav-section').each(function() {
734 $(this).addClass('expanded');
735 $(this).children('ul').show();
736 });
737 }
738}
739
Scott Maine624b3f2013-09-12 12:56:41 -0700740function unHighlightSidenav() {
741 $("ul#nav li.selected").removeClass("selected");
742 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
743}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700744
745function toggleFullscreen(enable) {
746 var delay = 20;
747 var enabled = true;
748 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
749 if (enable) {
750 // Currently NOT USING fullscreen; enable fullscreen
751 stylesheet.removeAttr('disabled');
752 $('#nav-swap .fullscreen').removeClass('disabled');
753 $('#devdoc-nav').css({left:''});
754 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
755 enabled = true;
756 } else {
757 // Currently USING fullscreen; disable fullscreen
758 stylesheet.attr('disabled', 'disabled');
759 $('#nav-swap .fullscreen').addClass('disabled');
760 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
761 enabled = false;
762 }
smain@google.com6bdcb982014-11-14 11:53:07 -0800763 writeCookie("fullscreen", enabled, null);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700764 setNavBarLeftPos();
765 resizeNav(delay);
766 updateSideNavPosition();
767 setTimeout(initSidenavHeightResize,delay);
768}
769
770
771function setNavBarLeftPos() {
772 navBarLeftPos = $('#body-content').offset().left;
773}
774
775
776function updateSideNavPosition() {
777 var newLeft = $(window).scrollLeft() - navBarLeftPos;
778 $('#devdoc-nav').css({left: -newLeft});
779 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
780}
Scott Main3b90aff2013-08-01 18:09:35 -0700781
Scott Maine4d8f1b2012-06-21 18:03:05 -0700782// TODO: use $(document).ready instead
783function addLoadEvent(newfun) {
784 var current = window.onload;
785 if (typeof window.onload != 'function') {
786 window.onload = newfun;
787 } else {
788 window.onload = function() {
789 current();
790 newfun();
791 }
792 }
793}
794
795var agent = navigator['userAgent'].toLowerCase();
796// If a mobile phone, set flag and do mobile setup
797if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
798 (agent.indexOf("blackberry") != -1) ||
799 (agent.indexOf("webos") != -1) ||
800 (agent.indexOf("mini") != -1)) { // opera mini browsers
801 isMobile = true;
802}
803
804
Scott Main498d7102013-08-21 15:47:38 -0700805$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700806 $("pre:not(.no-pretty-print)").addClass("prettyprint");
807 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700808});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700809
Scott Maine4d8f1b2012-06-21 18:03:05 -0700810
811
812
813/* ######### RESIZE THE SIDENAV HEIGHT ########## */
814
815function resizeNav(delay) {
816 var $nav = $("#devdoc-nav");
817 var $window = $(window);
818 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700819
Scott Maine4d8f1b2012-06-21 18:03:05 -0700820 // Get the height of entire window and the total header height.
821 // Then figure out based on scroll position whether the header is visible
822 var windowHeight = $window.height();
823 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700824 var headerHeight = $('#header-wrapper').outerHeight();
825 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700826
827 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700828 // Could be either margin or top position, depending on whether the nav is fixed.
Scott Main3b90aff2013-08-01 18:09:35 -0700829 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700830 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700831
Scott Maine4d8f1b2012-06-21 18:03:05 -0700832 // Depending on whether the header is visible, set the side nav's height.
833 if (headerVisible) {
834 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700835 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700836 } else {
837 // Once header is off screen, the nav height is almost full window height
838 navHeight = windowHeight - topMargin;
839 }
Scott Main3b90aff2013-08-01 18:09:35 -0700840
841
842
Scott Maine4d8f1b2012-06-21 18:03:05 -0700843 $scrollPanes = $(".scroll-pane");
844 if ($scrollPanes.length > 1) {
845 // subtract the height of the api level widget and nav swapper from the available nav height
846 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700847
Scott Maine4d8f1b2012-06-21 18:03:05 -0700848 $("#swapper").css({height:navHeight + "px"});
849 if ($("#nav-tree").is(":visible")) {
850 $("#nav-tree").css({height:navHeight});
851 }
Scott Main3b90aff2013-08-01 18:09:35 -0700852
853 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700854 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700855
856 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700857 // then the package panel should begin to shrink
858 if (parseInt(classesHeight) <= 0) {
859 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
860 $("#packages-nav").css({height:navHeight - 10});
861 }
Scott Main3b90aff2013-08-01 18:09:35 -0700862
Scott Maine4d8f1b2012-06-21 18:03:05 -0700863 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
864 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700865
866
Scott Maine4d8f1b2012-06-21 18:03:05 -0700867 } else {
868 $nav.height(navHeight);
869 }
Scott Main3b90aff2013-08-01 18:09:35 -0700870
Scott Maine4d8f1b2012-06-21 18:03:05 -0700871 if (delay) {
872 updateFromResize = true;
873 delayedReInitScrollbars(delay);
874 } else {
875 reInitScrollbars();
876 }
Scott Main3b90aff2013-08-01 18:09:35 -0700877
Scott Maine4d8f1b2012-06-21 18:03:05 -0700878}
879
880var updateScrollbars = false;
881var updateFromResize = false;
882
883/* Re-initialize the scrollbars to account for changed nav size.
884 * This method postpones the actual update by a 1/4 second in order to optimize the
885 * scroll performance while the header is still visible, because re-initializing the
886 * scroll panes is an intensive process.
887 */
888function delayedReInitScrollbars(delay) {
889 // If we're scheduled for an update, but have received another resize request
890 // before the scheduled resize has occured, just ignore the new request
891 // (and wait for the scheduled one).
892 if (updateScrollbars && updateFromResize) {
893 updateFromResize = false;
894 return;
895 }
Scott Main3b90aff2013-08-01 18:09:35 -0700896
Scott Maine4d8f1b2012-06-21 18:03:05 -0700897 // We're scheduled for an update and the update request came from this method's setTimeout
898 if (updateScrollbars && !updateFromResize) {
899 reInitScrollbars();
900 updateScrollbars = false;
901 } else {
902 updateScrollbars = true;
903 updateFromResize = false;
904 setTimeout('delayedReInitScrollbars()',delay);
905 }
906}
907
908/* Re-initialize the scrollbars to account for changed nav size. */
909function reInitScrollbars() {
910 var pane = $(".scroll-pane").each(function(){
911 var api = $(this).data('jsp');
912 if (!api) { setTimeout(reInitScrollbars,300); return;}
913 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700914 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700915 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
916}
917
918
919/* Resize the height of the nav panels in the reference,
920 * and save the new size to a cookie */
921function saveNavPanels() {
922 var basePath = getBaseUri(location.pathname);
923 var section = basePath.substring(1,basePath.indexOf("/",1));
smain@google.com6bdcb982014-11-14 11:53:07 -0800924 writeCookie("height", resizePackagesNav.css("height"), section);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700925}
926
927
928
929function restoreHeight(packageHeight) {
930 $("#resize-packages-nav").height(packageHeight);
931 $("#packages-nav").height(packageHeight);
932 // var classesHeight = navHeight - packageHeight;
933 // $("#classes-nav").css({height:classesHeight});
934 // $("#classes-nav .jspContainer").css({height:classesHeight});
935}
936
937
938
939/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
940
941
942
943
944
Scott Main3b90aff2013-08-01 18:09:35 -0700945/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700946 This is called when the page finished loading. */
947function scrollIntoView(nav) {
948 var $nav = $("#"+nav);
949 var element = $nav.jScrollPane({/* ...settings... */});
950 var api = element.data('jsp');
951
952 if ($nav.is(':visible')) {
953 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700954 if ($selected.length == 0) {
955 // If no selected item found, exit
956 return;
957 }
Scott Main52dd2062013-08-15 12:22:28 -0700958 // get the selected item's offset from its container nav by measuring the item's offset
959 // relative to the document then subtract the container nav's offset relative to the document
960 var selectedOffset = $selected.offset().top - $nav.offset().top;
961 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
962 // if it's more than 80% down the nav
963 // scroll the item up by an amount equal to 80% the container nav's height
964 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700965 }
966 }
967}
968
969
970
971
972
973
974/* Show popup dialogs */
975function showDialog(id) {
976 $dialog = $("#"+id);
977 $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>');
978 $dialog.wrapInner('<div/>');
979 $dialog.removeClass("hide");
980}
981
982
983
984
985
986/* ######### COOKIES! ########## */
987
988function readCookie(cookie) {
989 var myCookie = cookie_namespace+"_"+cookie+"=";
990 if (document.cookie) {
991 var index = document.cookie.indexOf(myCookie);
992 if (index != -1) {
993 var valStart = index + myCookie.length;
994 var valEnd = document.cookie.indexOf(";", valStart);
995 if (valEnd == -1) {
996 valEnd = document.cookie.length;
997 }
998 var val = document.cookie.substring(valStart, valEnd);
999 return val;
1000 }
1001 }
1002 return 0;
1003}
1004
smain@google.com6bdcb982014-11-14 11:53:07 -08001005function writeCookie(cookie, val, section) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001006 if (val==undefined) return;
1007 section = section == null ? "_" : "_"+section+"_";
smain@google.com6bdcb982014-11-14 11:53:07 -08001008 var age = 2*365*24*60*60; // set max-age to 2 years
Scott Main3b90aff2013-08-01 18:09:35 -07001009 var cookieValue = cookie_namespace + section + cookie + "=" + val
smain@google.com80e38f42014-11-03 10:47:12 -08001010 + "; max-age=" + age +"; path=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -07001011 document.cookie = cookieValue;
1012}
1013
1014/* ######### END COOKIES! ########## */
1015
1016
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001017var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -07001018var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001019var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -07001020/* Sets the vertical scoll position at which the sticky bar should appear.
1021 This method is called to reset the position when search results appear or hide */
1022function setStickyTop() {
1023 stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight();
1024}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001025
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001026/*
Scott Mainb16376f2014-05-21 20:35:47 -07001027 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -07001028 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001029$(window).scroll(function(event) {
1030
1031 setStickyTop();
1032 var hiding = false;
1033 var $stickyEl = $('#sticky-header');
1034 var $menuEl = $('.menu-container');
1035 // Exit if there's no sidenav
1036 if ($('#side-nav').length == 0) return;
1037 // Exit if the mouse target is a DIV, because that means the event is coming
1038 // from a scrollable div and so there's no need to make adjustments to our layout
1039 if ($(event.target).nodeName == "DIV") {
1040 return;
1041 }
1042
1043 var top = $(window).scrollTop();
1044 // we set the navbar fixed when the scroll position is beyond the height of the site header...
1045 var shouldBeSticky = top >= stickyTop;
1046 // ... except if the document content is shorter than the sidenav height.
1047 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1048 if ($("#doc-col").height() < $("#side-nav").height()) {
1049 shouldBeSticky = false;
1050 }
Scott Mainf5257812014-05-22 17:26:38 -07001051 // Account for horizontal scroll
1052 var scrollLeft = $(window).scrollLeft();
1053 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1054 if (sticky && (scrollLeft != prevScrollLeft)) {
1055 updateSideNavPosition();
1056 prevScrollLeft = scrollLeft;
1057 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001058
1059 // Don't continue if the header is sufficently far away
1060 // (to avoid intensive resizing that slows scrolling)
1061 if (sticky == shouldBeSticky) {
1062 return;
1063 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001064
1065 // If sticky header visible and position is now near top, hide sticky
1066 if (sticky && !shouldBeSticky) {
1067 sticky = false;
1068 hiding = true;
1069 // make the sidenav static again
1070 $('#devdoc-nav')
1071 .removeClass('fixed')
1072 .css({'width':'auto','margin':''})
1073 .prependTo('#side-nav');
1074 // delay hide the sticky
1075 $menuEl.removeClass('sticky-menu');
1076 $stickyEl.fadeOut(250);
1077 hiding = false;
1078
1079 // update the sidenaav position for side scrolling
1080 updateSideNavPosition();
1081 } else if (!sticky && shouldBeSticky) {
1082 sticky = true;
1083 $stickyEl.fadeIn(10);
1084 $menuEl.addClass('sticky-menu');
1085
1086 // make the sidenav fixed
1087 var width = $('#devdoc-nav').width();
1088 $('#devdoc-nav')
1089 .addClass('fixed')
1090 .css({'width':width+'px'})
1091 .prependTo('#body-content');
1092
1093 // update the sidenaav position for side scrolling
1094 updateSideNavPosition();
1095
1096 } else if (hiding && top < 15) {
1097 $menuEl.removeClass('sticky-menu');
1098 $stickyEl.hide();
1099 hiding = false;
1100 }
1101 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
1102});
1103
1104/*
1105 * Manages secion card states and nav resize to conclude loading
1106 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001107(function() {
1108 $(document).ready(function() {
1109
Dirk Doughertyc3921652014-05-13 16:55:26 -07001110 // Stack hover states
1111 $('.section-card-menu').each(function(index, el) {
1112 var height = $(el).height();
1113 $(el).css({height:height+'px', position:'relative'});
1114 var $cardInfo = $(el).find('.card-info');
1115
1116 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1117 });
1118
Dirk Doughertyc3921652014-05-13 16:55:26 -07001119 });
1120
1121})();
1122
Scott Maine4d8f1b2012-06-21 18:03:05 -07001123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
Scott Maind7026f72013-06-17 15:08:49 -07001136/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001137
1138
1139
1140
1141
1142function toggle(obj, slide) {
1143 var ul = $("ul:first", obj);
1144 var li = ul.parent();
1145 if (li.hasClass("closed")) {
1146 if (slide) {
1147 ul.slideDown("fast");
1148 } else {
1149 ul.show();
1150 }
1151 li.removeClass("closed");
1152 li.addClass("open");
1153 $(".toggle-img", li).attr("title", "hide pages");
1154 } else {
1155 ul.slideUp("fast");
1156 li.removeClass("open");
1157 li.addClass("closed");
1158 $(".toggle-img", li).attr("title", "show pages");
1159 }
1160}
1161
1162
Scott Maine4d8f1b2012-06-21 18:03:05 -07001163function buildToggleLists() {
1164 $(".toggle-list").each(
1165 function(i) {
1166 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1167 $(this).addClass("closed");
1168 });
1169}
1170
1171
1172
Scott Maind7026f72013-06-17 15:08:49 -07001173function hideNestedItems(list, toggle) {
1174 $list = $(list);
1175 // hide nested lists
1176 if($list.hasClass('showing')) {
1177 $("li ol", $list).hide('fast');
1178 $list.removeClass('showing');
1179 // show nested lists
1180 } else {
1181 $("li ol", $list).show('fast');
1182 $list.addClass('showing');
1183 }
1184 $(".more,.less",$(toggle)).toggle();
1185}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001186
1187
smain@google.com95948b82014-06-16 19:24:25 -07001188/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1189function setupIdeDocToggle() {
1190 $( "select.ide" ).change(function() {
1191 var selected = $(this).find("option:selected").attr("value");
1192 $(".select-ide").hide();
1193 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001194
smain@google.com95948b82014-06-16 19:24:25 -07001195 $("select.ide").val(selected);
1196 });
1197}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222/* REFERENCE NAV SWAP */
1223
1224
1225function getNavPref() {
1226 var v = readCookie('reference_nav');
1227 if (v != NAV_PREF_TREE) {
1228 v = NAV_PREF_PANELS;
1229 }
1230 return v;
1231}
1232
1233function chooseDefaultNav() {
1234 nav_pref = getNavPref();
1235 if (nav_pref == NAV_PREF_TREE) {
1236 $("#nav-panels").toggle();
1237 $("#panel-link").toggle();
1238 $("#nav-tree").toggle();
1239 $("#tree-link").toggle();
1240 }
1241}
1242
1243function swapNav() {
1244 if (nav_pref == NAV_PREF_TREE) {
1245 nav_pref = NAV_PREF_PANELS;
1246 } else {
1247 nav_pref = NAV_PREF_TREE;
1248 init_default_navtree(toRoot);
1249 }
smain@google.com6bdcb982014-11-14 11:53:07 -08001250 writeCookie("nav", nav_pref, "reference");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001251
1252 $("#nav-panels").toggle();
1253 $("#panel-link").toggle();
1254 $("#nav-tree").toggle();
1255 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001256
Scott Maine4d8f1b2012-06-21 18:03:05 -07001257 resizeNav();
1258
1259 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1260 $("#nav-tree .jspContainer:visible")
1261 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1262 // Another nasty hack to make the scrollbar appear now that we have height
1263 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001264
Scott Maine4d8f1b2012-06-21 18:03:05 -07001265 if ($("#nav-tree").is(':visible')) {
1266 scrollIntoView("nav-tree");
1267 } else {
1268 scrollIntoView("packages-nav");
1269 scrollIntoView("classes-nav");
1270 }
1271}
1272
1273
1274
Scott Mainf5089842012-08-14 16:31:07 -07001275/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001276/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001277/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001278
1279function getBaseUri(uri) {
1280 var intlUrl = (uri.substring(0,6) == "/intl/");
1281 if (intlUrl) {
1282 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1283 base = base.substring(base.indexOf('/')+1, base.length);
1284 //alert("intl, returning base url: /" + base);
1285 return ("/" + base);
1286 } else {
1287 //alert("not intl, returning uri as found.");
1288 return uri;
1289 }
1290}
1291
1292function requestAppendHL(uri) {
1293//append "?hl=<lang> to an outgoing request (such as to blog)
1294 var lang = getLangPref();
1295 if (lang) {
1296 var q = 'hl=' + lang;
1297 uri += '?' + q;
1298 window.location = uri;
1299 return false;
1300 } else {
1301 return true;
1302 }
1303}
1304
1305
Scott Maine4d8f1b2012-06-21 18:03:05 -07001306function changeNavLang(lang) {
Scott Main6eb95f12012-10-02 17:12:23 -07001307 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1308 $links.each(function(i){ // for each link with a translation
1309 var $link = $(this);
1310 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1311 // put the desired language from the attribute as the text
1312 $link.text($link.attr(lang+"-lang"))
Scott Maine4d8f1b2012-06-21 18:03:05 -07001313 }
Scott Main6eb95f12012-10-02 17:12:23 -07001314 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001315}
1316
Scott Main015d6162013-01-29 09:01:52 -08001317function changeLangPref(lang, submit) {
smain@google.com6bdcb982014-11-14 11:53:07 -08001318 writeCookie("pref_lang", lang, null);
Scott Main015d6162013-01-29 09:01:52 -08001319
1320 // ####### TODO: Remove this condition once we're stable on devsite #######
1321 // This condition is only needed if we still need to support legacy GAE server
1322 if (devsite) {
1323 // Switch language when on Devsite server
1324 if (submit) {
1325 $("#setlang").submit();
1326 }
1327 } else {
1328 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001329 if (submit) {
1330 window.location = getBaseUri(location.pathname);
1331 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001332 }
1333}
1334
1335function loadLangPref() {
1336 var lang = readCookie("pref_lang");
1337 if (lang != 0) {
1338 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1339 }
1340}
1341
1342function getLangPref() {
1343 var lang = $("#language").find(":selected").attr("value");
1344 if (!lang) {
1345 lang = readCookie("pref_lang");
1346 }
1347 return (lang != 0) ? lang : 'en';
1348}
1349
1350/* ########## END LOCALIZATION ############ */
1351
1352
1353
1354
1355
1356
1357/* Used to hide and reveal supplemental content, such as long code samples.
1358 See the companion CSS in android-developer-docs.css */
1359function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001360 var div = $(obj).closest(".toggle-content");
1361 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001362 if (div.hasClass("closed")) { // if it's closed, open it
1363 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001364 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001365 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001366 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001367 + "assets/images/triangle-opened.png");
1368 } else { // if it's open, close it
1369 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001370 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001371 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001372 div.find(".toggle-content").removeClass("open").addClass("closed")
1373 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001374 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001375 + "assets/images/triangle-closed.png");
1376 });
1377 }
1378 return false;
1379}
Scott Mainf5089842012-08-14 16:31:07 -07001380
1381
Scott Maindb3678b2012-10-23 14:13:41 -07001382/* New version of expandable content */
1383function toggleExpandable(link,id) {
1384 if($(id).is(':visible')) {
1385 $(id).slideUp();
1386 $(link).removeClass('expanded');
1387 } else {
1388 $(id).slideDown();
1389 $(link).addClass('expanded');
1390 }
1391}
1392
1393function hideExpandable(ids) {
1394 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001395 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001396}
1397
Scott Mainf5089842012-08-14 16:31:07 -07001398
1399
1400
1401
Scott Main3b90aff2013-08-01 18:09:35 -07001402/*
Scott Mainf5089842012-08-14 16:31:07 -07001403 * Slideshow 1.0
1404 * Used on /index.html and /develop/index.html for carousel
1405 *
1406 * Sample usage:
1407 * HTML -
1408 * <div class="slideshow-container">
1409 * <a href="" class="slideshow-prev">Prev</a>
1410 * <a href="" class="slideshow-next">Next</a>
1411 * <ul>
1412 * <li class="item"><img src="images/marquee1.jpg"></li>
1413 * <li class="item"><img src="images/marquee2.jpg"></li>
1414 * <li class="item"><img src="images/marquee3.jpg"></li>
1415 * <li class="item"><img src="images/marquee4.jpg"></li>
1416 * </ul>
1417 * </div>
1418 *
1419 * <script type="text/javascript">
1420 * $('.slideshow-container').dacSlideshow({
1421 * auto: true,
1422 * btnPrev: '.slideshow-prev',
1423 * btnNext: '.slideshow-next'
1424 * });
1425 * </script>
1426 *
1427 * Options:
1428 * btnPrev: optional identifier for previous button
1429 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001430 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001431 * auto: whether or not to auto-proceed
1432 * speed: animation speed
1433 * autoTime: time between auto-rotation
1434 * easing: easing function for transition
1435 * start: item to select by default
1436 * scroll: direction to scroll in
1437 * pagination: whether or not to include dotted pagination
1438 *
1439 */
1440
1441 (function($) {
1442 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001443
Scott Mainf5089842012-08-14 16:31:07 -07001444 //Options - see above
1445 o = $.extend({
1446 btnPrev: null,
1447 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001448 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001449 auto: true,
1450 speed: 500,
1451 autoTime: 12000,
1452 easing: null,
1453 start: 0,
1454 scroll: 1,
1455 pagination: true
1456
1457 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001458
1459 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001460 return this.each(function() {
1461
1462 var running = false;
1463 var animCss = o.vertical ? "top" : "left";
1464 var sizeCss = o.vertical ? "height" : "width";
1465 var div = $(this);
1466 var ul = $("ul", div);
1467 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001468 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001469 var timer = null;
1470
1471 var li = $("li", ul);
1472 var itemLength = li.size();
1473 var curr = o.start;
1474
1475 li.css({float: o.vertical ? "none" : "left"});
1476 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1477 div.css({position: "relative", "z-index": "2", left: "0px"});
1478
1479 var liSize = o.vertical ? height(li) : width(li);
1480 var ulSize = liSize * itemLength;
1481 var divSize = liSize;
1482
1483 li.css({width: li.width(), height: li.height()});
1484 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1485
1486 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001487
Scott Mainf5089842012-08-14 16:31:07 -07001488 //Pagination
1489 if (o.pagination) {
1490 var pagination = $("<div class='pagination'></div>");
1491 var pag_ul = $("<ul></ul>");
1492 if (tl > 1) {
1493 for (var i=0;i<tl;i++) {
1494 var li = $("<li>"+i+"</li>");
1495 pag_ul.append(li);
1496 if (i==o.start) li.addClass('active');
1497 li.click(function() {
1498 go(parseInt($(this).text()));
1499 })
1500 }
1501 pagination.append(pag_ul);
1502 div.append(pagination);
1503 }
1504 }
Scott Main3b90aff2013-08-01 18:09:35 -07001505
Scott Mainf5089842012-08-14 16:31:07 -07001506 //Previous button
1507 if(o.btnPrev)
1508 $(o.btnPrev).click(function(e) {
1509 e.preventDefault();
1510 return go(curr-o.scroll);
1511 });
1512
1513 //Next button
1514 if(o.btnNext)
1515 $(o.btnNext).click(function(e) {
1516 e.preventDefault();
1517 return go(curr+o.scroll);
1518 });
Scott Maineb410352013-01-14 19:03:40 -08001519
1520 //Pause button
1521 if(o.btnPause)
1522 $(o.btnPause).click(function(e) {
1523 e.preventDefault();
1524 if ($(this).hasClass('paused')) {
1525 startRotateTimer();
1526 } else {
1527 pauseRotateTimer();
1528 }
1529 });
Scott Main3b90aff2013-08-01 18:09:35 -07001530
Scott Mainf5089842012-08-14 16:31:07 -07001531 //Auto rotation
1532 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001533
Scott Mainf5089842012-08-14 16:31:07 -07001534 function startRotateTimer() {
1535 clearInterval(timer);
1536 timer = setInterval(function() {
1537 if (curr == tl-1) {
1538 go(0);
1539 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001540 go(curr+o.scroll);
1541 }
Scott Mainf5089842012-08-14 16:31:07 -07001542 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001543 $(o.btnPause).removeClass('paused');
1544 }
1545
1546 function pauseRotateTimer() {
1547 clearInterval(timer);
1548 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001549 }
1550
1551 //Go to an item
1552 function go(to) {
1553 if(!running) {
1554
1555 if(to<0) {
1556 to = itemLength-1;
1557 } else if (to>itemLength-1) {
1558 to = 0;
1559 }
1560 curr = to;
1561
1562 running = true;
1563
1564 ul.animate(
1565 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1566 function() {
1567 running = false;
1568 }
1569 );
1570
1571 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1572 $( (curr-o.scroll<0 && o.btnPrev)
1573 ||
1574 (curr+o.scroll > itemLength && o.btnNext)
1575 ||
1576 []
1577 ).addClass("disabled");
1578
Scott Main3b90aff2013-08-01 18:09:35 -07001579
Scott Mainf5089842012-08-14 16:31:07 -07001580 var nav_items = $('li', pagination);
1581 nav_items.removeClass('active');
1582 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001583
Scott Mainf5089842012-08-14 16:31:07 -07001584
1585 }
1586 if(o.auto) startRotateTimer();
1587 return false;
1588 };
1589 });
1590 };
1591
1592 function css(el, prop) {
1593 return parseInt($.css(el[0], prop)) || 0;
1594 };
1595 function width(el) {
1596 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1597 };
1598 function height(el) {
1599 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1600 };
1601
1602 })(jQuery);
1603
1604
Scott Main3b90aff2013-08-01 18:09:35 -07001605/*
Scott Mainf5089842012-08-14 16:31:07 -07001606 * dacSlideshow 1.0
1607 * Used on develop/index.html for side-sliding tabs
1608 *
1609 * Sample usage:
1610 * HTML -
1611 * <div class="slideshow-container">
1612 * <a href="" class="slideshow-prev">Prev</a>
1613 * <a href="" class="slideshow-next">Next</a>
1614 * <ul>
1615 * <li class="item"><img src="images/marquee1.jpg"></li>
1616 * <li class="item"><img src="images/marquee2.jpg"></li>
1617 * <li class="item"><img src="images/marquee3.jpg"></li>
1618 * <li class="item"><img src="images/marquee4.jpg"></li>
1619 * </ul>
1620 * </div>
1621 *
1622 * <script type="text/javascript">
1623 * $('.slideshow-container').dacSlideshow({
1624 * auto: true,
1625 * btnPrev: '.slideshow-prev',
1626 * btnNext: '.slideshow-next'
1627 * });
1628 * </script>
1629 *
1630 * Options:
1631 * btnPrev: optional identifier for previous button
1632 * btnNext: optional identifier for next button
1633 * auto: whether or not to auto-proceed
1634 * speed: animation speed
1635 * autoTime: time between auto-rotation
1636 * easing: easing function for transition
1637 * start: item to select by default
1638 * scroll: direction to scroll in
1639 * pagination: whether or not to include dotted pagination
1640 *
1641 */
1642 (function($) {
1643 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001644
Scott Mainf5089842012-08-14 16:31:07 -07001645 //Options - see above
1646 o = $.extend({
1647 speed : 250,
1648 easing: null,
1649 nav_id: null,
1650 frame_id: null
1651 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001652
1653 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001654 return this.each(function() {
1655
1656 var curr = 0;
1657 var running = false;
1658 var animCss = "margin-left";
1659 var sizeCss = "width";
1660 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001661
Scott Mainf5089842012-08-14 16:31:07 -07001662 var nav = $(o.nav_id, div);
1663 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001664 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001665 var frame = div.find(o.frame_id);
1666 var content_width = $(frame).find('ul').width();
1667 //Buttons
1668 $(nav_li).click(function(e) {
1669 go($(nav_li).index($(this)));
1670 })
Scott Main3b90aff2013-08-01 18:09:35 -07001671
Scott Mainf5089842012-08-14 16:31:07 -07001672 //Go to an item
1673 function go(to) {
1674 if(!running) {
1675 curr = to;
1676 running = true;
1677
1678 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1679 function() {
1680 running = false;
1681 }
1682 );
1683
Scott Main3b90aff2013-08-01 18:09:35 -07001684
Scott Mainf5089842012-08-14 16:31:07 -07001685 nav_li.removeClass('active');
1686 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001687
Scott Mainf5089842012-08-14 16:31:07 -07001688
1689 }
1690 return false;
1691 };
1692 });
1693 };
1694
1695 function css(el, prop) {
1696 return parseInt($.css(el[0], prop)) || 0;
1697 };
1698 function width(el) {
1699 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1700 };
1701 function height(el) {
1702 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1703 };
1704
1705 })(jQuery);
1706
1707
1708
1709
1710
1711/* ######################################################## */
1712/* ################ SEARCH SUGGESTIONS ################## */
1713/* ######################################################## */
1714
1715
Scott Main7e447ed2013-02-19 17:22:37 -08001716
Scott Main0e76e7e2013-03-12 10:24:07 -07001717var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1718var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1719
Scott Mainf5089842012-08-14 16:31:07 -07001720var gMatches = new Array();
1721var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001722var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001723var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1724var gListLength = 0;
1725
1726
1727var gGoogleMatches = new Array();
1728var ROW_COUNT_GOOGLE = 15; // max number of results in list
1729var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001730
Scott Main0e76e7e2013-03-12 10:24:07 -07001731var gDocsMatches = new Array();
1732var ROW_COUNT_DOCS = 100; // max number of results in list
1733var gDocsListLength = 0;
1734
Scott Mainde295272013-03-25 15:48:35 -07001735function onSuggestionClick(link) {
1736 // When user clicks a suggested document, track it
smain@google.comed677d72014-12-12 10:19:17 -08001737 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).attr('href'),
1738 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07001739}
1740
Scott Mainf5089842012-08-14 16:31:07 -07001741function set_item_selected($li, selected)
1742{
1743 if (selected) {
1744 $li.attr('class','jd-autocomplete jd-selected');
1745 } else {
1746 $li.attr('class','jd-autocomplete');
1747 }
1748}
1749
1750function set_item_values(toroot, $li, match)
1751{
1752 var $link = $('a',$li);
1753 $link.html(match.__hilabel || match.label);
1754 $link.attr('href',toroot + match.link);
1755}
1756
Scott Main719acb42013-12-05 16:05:09 -08001757function set_item_values_jd(toroot, $li, match)
1758{
1759 var $link = $('a',$li);
1760 $link.html(match.title);
1761 $link.attr('href',toroot + match.url);
1762}
1763
Scott Main0e76e7e2013-03-12 10:24:07 -07001764function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001765 var $li = $("<li class='jd-autocomplete'></li>");
1766 $list.append($li);
1767
1768 $li.mousedown(function() {
1769 window.location = this.firstChild.getAttribute("href");
1770 });
1771 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001772 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001773 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001774 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1775 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001776 });
Scott Mainde295272013-03-25 15:48:35 -07001777 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001778 $li.attr('class','show-item');
1779 return $li;
1780}
1781
Scott Mainf5089842012-08-14 16:31:07 -07001782function sync_selection_table(toroot)
1783{
Scott Mainf5089842012-08-14 16:31:07 -07001784 var $li; //list item jquery object
1785 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001786
Scott Main0e76e7e2013-03-12 10:24:07 -07001787 // if there are NO results at all, hide all columns
1788 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1789 $('.suggest-card').hide(300);
1790 return;
1791 }
1792
1793 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001794 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001795 // reveal suggestion list
1796 $('.suggest-card.dummy').show();
1797 $('.suggest-card.reference').show();
1798 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001799
Scott Main0e76e7e2013-03-12 10:24:07 -07001800 // reset the lists
1801 $(".search_filtered_wrapper.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001802
Scott Main0e76e7e2013-03-12 10:24:07 -07001803 // ########### ANDROID RESULTS #############
1804 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001805
Scott Main0e76e7e2013-03-12 10:24:07 -07001806 // determine android results to show
1807 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1808 gMatches.length : ROW_COUNT_FRAMEWORK;
1809 for (i=0; i<gListLength; i++) {
1810 var $li = new_suggestion($(".suggest-card.reference ul"));
1811 set_item_values(toroot, $li, gMatches[i]);
1812 set_item_selected($li, i == gSelectedIndex);
1813 }
1814 }
Scott Main7e447ed2013-02-19 17:22:37 -08001815
Scott Main0e76e7e2013-03-12 10:24:07 -07001816 // ########### GOOGLE RESULTS #############
1817 if (gGoogleMatches.length > 0) {
1818 // show header for list
1819 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001820
Scott Main0e76e7e2013-03-12 10:24:07 -07001821 // determine google results to show
1822 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1823 for (i=0; i<gGoogleListLength; i++) {
1824 var $li = new_suggestion($(".suggest-card.reference ul"));
1825 set_item_values(toroot, $li, gGoogleMatches[i]);
1826 set_item_selected($li, i == gSelectedIndex);
1827 }
1828 }
Scott Mainf5089842012-08-14 16:31:07 -07001829 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001830 $('.suggest-card.reference').hide();
1831 $('.suggest-card.dummy').hide();
1832 }
1833
1834 // ########### JD DOC RESULTS #############
1835 if (gDocsMatches.length > 0) {
1836 // reset the lists
1837 $(".search_filtered_wrapper.docs li").remove();
1838
1839 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001840 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1841 // The order must match the reverse order that each section appears as a card in
1842 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001843 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1844 for (i=0; i<gDocsListLength; i++) {
1845 var sugg = gDocsMatches[i];
1846 var $li;
1847 if (sugg.type == "design") {
1848 $li = new_suggestion($(".suggest-card.design ul"));
1849 } else
1850 if (sugg.type == "distribute") {
1851 $li = new_suggestion($(".suggest-card.distribute ul"));
1852 } else
Scott Main719acb42013-12-05 16:05:09 -08001853 if (sugg.type == "samples") {
1854 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1855 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001856 if (sugg.type == "training") {
1857 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1858 } else
Scott Main719acb42013-12-05 16:05:09 -08001859 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001860 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1861 } else {
1862 continue;
1863 }
1864
Scott Main719acb42013-12-05 16:05:09 -08001865 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001866 set_item_selected($li, i == gSelectedIndex);
1867 }
1868
1869 // add heading and show or hide card
1870 if ($(".suggest-card.design li").length > 0) {
1871 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1872 $(".suggest-card.design").show(300);
1873 } else {
1874 $('.suggest-card.design').hide(300);
1875 }
1876 if ($(".suggest-card.distribute li").length > 0) {
1877 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1878 $(".suggest-card.distribute").show(300);
1879 } else {
1880 $('.suggest-card.distribute').hide(300);
1881 }
1882 if ($(".child-card.guides li").length > 0) {
1883 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1884 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1885 }
1886 if ($(".child-card.training li").length > 0) {
1887 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1888 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1889 }
Scott Main719acb42013-12-05 16:05:09 -08001890 if ($(".child-card.samples li").length > 0) {
1891 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1892 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1893 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001894
1895 if ($(".suggest-card.develop li").length > 0) {
1896 $(".suggest-card.develop").show(300);
1897 } else {
1898 $('.suggest-card.develop').hide(300);
1899 }
1900
1901 } else {
1902 $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001903 }
1904}
1905
Scott Main0e76e7e2013-03-12 10:24:07 -07001906/** Called by the search input's onkeydown and onkeyup events.
1907 * Handles navigation with keyboard arrows, Enter key to invoke search,
1908 * otherwise invokes search suggestions on key-up event.
1909 * @param e The JS event
1910 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001911 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001912 * @returns True if the event should bubble up
1913 */
Scott Mainf5089842012-08-14 16:31:07 -07001914function search_changed(e, kd, toroot)
1915{
Scott Main719acb42013-12-05 16:05:09 -08001916 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001917 var search = document.getElementById("search_autocomplete");
1918 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001919 // get the ul hosting the currently selected item
1920 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1921 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1922 var $selectedUl = $columns[gSelectedColumn];
1923
Scott Mainf5089842012-08-14 16:31:07 -07001924 // show/hide the close button
1925 if (text != '') {
1926 $(".search .close").removeClass("hide");
1927 } else {
1928 $(".search .close").addClass("hide");
1929 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001930 // 27 = esc
1931 if (e.keyCode == 27) {
1932 // close all search results
1933 if (kd) $('.search .close').trigger('click');
1934 return true;
1935 }
Scott Mainf5089842012-08-14 16:31:07 -07001936 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001937 else if (e.keyCode == 13) {
1938 if (gSelectedIndex < 0) {
1939 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001940 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1941 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001942 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001943 return true;
1944 } else {
1945 // otherwise, results are already showing, so allow ajax to auto refresh the results
1946 // and ignore this Enter press to avoid the reload.
1947 return false;
1948 }
1949 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001950 // click the link corresponding to selected item
1951 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001952 return false;
1953 }
1954 }
Scott Mainb16376f2014-05-21 20:35:47 -07001955 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001956 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001957 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001958 if ((sticky ) && (search.value != "")) {
1959 $('body,html').animate({scrollTop:0}, '500', 'swing');
1960 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001961 return true;
1962 }
1963 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001964 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001965 // if the next item is a header, skip it
1966 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001967 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001968 }
1969 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001970 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001971 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001972 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1973 // If user reaches top, reset selected column
1974 if (gSelectedIndex < 0) {
1975 gSelectedColumn = -1;
1976 }
Scott Mainf5089842012-08-14 16:31:07 -07001977 }
1978 return false;
1979 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001980 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001981 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001982 // if the next item is a header, skip it
1983 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001984 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08001985 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001986 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
1987 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
1988 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001989 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07001990 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07001991 }
1992 return false;
1993 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001994 // Consider left/right arrow navigation
1995 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
1996 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
1997 // 37 LEFT ARROW
1998 // go left only if current column is not left-most column (last column)
1999 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
2000 $('li', $selectedUl).removeClass('jd-selected');
2001 gSelectedColumn++;
2002 $selectedUl = $columns[gSelectedColumn];
2003 // keep or reset the selected item to last item as appropriate
2004 gSelectedIndex = gSelectedIndex >
2005 $("li", $selectedUl).length-1 ?
2006 $("li", $selectedUl).length-1 : gSelectedIndex;
2007 // if the corresponding item is a header, move down
2008 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2009 gSelectedIndex++;
2010 }
Scott Main3b90aff2013-08-01 18:09:35 -07002011 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002012 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2013 return false;
2014 }
2015 // 39 RIGHT ARROW
2016 // go right only if current column is not the right-most column (first column)
2017 else if (e.keyCode == 39 && gSelectedColumn > 0) {
2018 $('li', $selectedUl).removeClass('jd-selected');
2019 gSelectedColumn--;
2020 $selectedUl = $columns[gSelectedColumn];
2021 // keep or reset the selected item to last item as appropriate
2022 gSelectedIndex = gSelectedIndex >
2023 $("li", $selectedUl).length-1 ?
2024 $("li", $selectedUl).length-1 : gSelectedIndex;
2025 // if the corresponding item is a header, move down
2026 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2027 gSelectedIndex++;
2028 }
Scott Main3b90aff2013-08-01 18:09:35 -07002029 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002030 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2031 return false;
2032 }
2033 }
2034
Scott Main719acb42013-12-05 16:05:09 -08002035 // if key-up event and not arrow down/up/left/right,
2036 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07002037 else if (!kd && (e.keyCode != 40)
2038 && (e.keyCode != 38)
2039 && (e.keyCode != 37)
2040 && (e.keyCode != 39)) {
2041 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07002042 gMatches = new Array();
2043 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002044 gGoogleMatches = new Array();
2045 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07002046 gDocsMatches = new Array();
2047 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002048
2049 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07002050 for (var i=0; i<DATA.length; i++) {
2051 var s = DATA[i];
2052 if (text.length != 0 &&
2053 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2054 gMatches[matchedCount] = s;
2055 matchedCount++;
2056 }
2057 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002058 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07002059 for (var i=0; i<gMatches.length; i++) {
2060 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08002061 }
2062
2063
2064 // Search for Google matches
2065 for (var i=0; i<GOOGLE_DATA.length; i++) {
2066 var s = GOOGLE_DATA[i];
2067 if (text.length != 0 &&
2068 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2069 gGoogleMatches[matchedCountGoogle] = s;
2070 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07002071 }
2072 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002073 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08002074 for (var i=0; i<gGoogleMatches.length; i++) {
2075 var s = gGoogleMatches[i];
2076 }
2077
Scott Mainf5089842012-08-14 16:31:07 -07002078 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07002079
2080
2081
Scott Main719acb42013-12-05 16:05:09 -08002082 // Search for matching JD docs
Dirk Dougherty9b7f8f22014-11-01 17:08:56 -07002083 if (text.length >= 2) {
Scott Main719acb42013-12-05 16:05:09 -08002084 // Regex to match only the beginning of a word
2085 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
2086
2087
2088 // Search for Training classes
2089 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002090 // current search comparison, with counters for tag and title,
2091 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08002092 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07002093 s.matched_tag = 0;
2094 s.matched_title = 0;
2095 var matched = false;
2096
2097 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08002098 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002099 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08002100 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002101 matched = true;
2102 s.matched_tag = j + 1; // add 1 to index position
2103 }
2104 }
Scott Main719acb42013-12-05 16:05:09 -08002105 // Don't consider doc title for lessons (only for class landing pages),
2106 // unless the lesson has a tag that already matches
2107 if ((s.lang == currentLang) &&
2108 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002109 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08002110 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002111 matched = true;
2112 s.matched_title = 1;
2113 }
2114 }
2115 if (matched) {
2116 gDocsMatches[matchedCountDocs] = s;
2117 matchedCountDocs++;
2118 }
2119 }
Scott Main719acb42013-12-05 16:05:09 -08002120
2121
2122 // Search for API Guides
2123 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2124 // current search comparison, with counters for tag and title,
2125 // used later to improve ranking
2126 var s = GUIDE_RESOURCES[i];
2127 s.matched_tag = 0;
2128 s.matched_title = 0;
2129 var matched = false;
2130
2131 // Check if query matches any tags; work backwards toward 1 to assist ranking
2132 for (var j = s.keywords.length - 1; j >= 0; j--) {
2133 // it matches a tag
2134 if (s.keywords[j].toLowerCase().match(textRegex)) {
2135 matched = true;
2136 s.matched_tag = j + 1; // add 1 to index position
2137 }
2138 }
2139 // Check if query matches the doc title, but only for current language
2140 if (s.lang == currentLang) {
2141 // if query matches the doc title
2142 if (s.title.toLowerCase().match(textRegex)) {
2143 matched = true;
2144 s.matched_title = 1;
2145 }
2146 }
2147 if (matched) {
2148 gDocsMatches[matchedCountDocs] = s;
2149 matchedCountDocs++;
2150 }
2151 }
2152
2153
2154 // Search for Tools Guides
2155 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2156 // current search comparison, with counters for tag and title,
2157 // used later to improve ranking
2158 var s = TOOLS_RESOURCES[i];
2159 s.matched_tag = 0;
2160 s.matched_title = 0;
2161 var matched = false;
2162
2163 // Check if query matches any tags; work backwards toward 1 to assist ranking
2164 for (var j = s.keywords.length - 1; j >= 0; j--) {
2165 // it matches a tag
2166 if (s.keywords[j].toLowerCase().match(textRegex)) {
2167 matched = true;
2168 s.matched_tag = j + 1; // add 1 to index position
2169 }
2170 }
2171 // Check if query matches the doc title, but only for current language
2172 if (s.lang == currentLang) {
2173 // if query matches the doc title
2174 if (s.title.toLowerCase().match(textRegex)) {
2175 matched = true;
2176 s.matched_title = 1;
2177 }
2178 }
2179 if (matched) {
2180 gDocsMatches[matchedCountDocs] = s;
2181 matchedCountDocs++;
2182 }
2183 }
2184
2185
2186 // Search for About docs
2187 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2188 // current search comparison, with counters for tag and title,
2189 // used later to improve ranking
2190 var s = ABOUT_RESOURCES[i];
2191 s.matched_tag = 0;
2192 s.matched_title = 0;
2193 var matched = false;
2194
2195 // Check if query matches any tags; work backwards toward 1 to assist ranking
2196 for (var j = s.keywords.length - 1; j >= 0; j--) {
2197 // it matches a tag
2198 if (s.keywords[j].toLowerCase().match(textRegex)) {
2199 matched = true;
2200 s.matched_tag = j + 1; // add 1 to index position
2201 }
2202 }
2203 // Check if query matches the doc title, but only for current language
2204 if (s.lang == currentLang) {
2205 // if query matches the doc title
2206 if (s.title.toLowerCase().match(textRegex)) {
2207 matched = true;
2208 s.matched_title = 1;
2209 }
2210 }
2211 if (matched) {
2212 gDocsMatches[matchedCountDocs] = s;
2213 matchedCountDocs++;
2214 }
2215 }
2216
2217
2218 // Search for Design guides
2219 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2220 // current search comparison, with counters for tag and title,
2221 // used later to improve ranking
2222 var s = DESIGN_RESOURCES[i];
2223 s.matched_tag = 0;
2224 s.matched_title = 0;
2225 var matched = false;
2226
2227 // Check if query matches any tags; work backwards toward 1 to assist ranking
2228 for (var j = s.keywords.length - 1; j >= 0; j--) {
2229 // it matches a tag
2230 if (s.keywords[j].toLowerCase().match(textRegex)) {
2231 matched = true;
2232 s.matched_tag = j + 1; // add 1 to index position
2233 }
2234 }
2235 // Check if query matches the doc title, but only for current language
2236 if (s.lang == currentLang) {
2237 // if query matches the doc title
2238 if (s.title.toLowerCase().match(textRegex)) {
2239 matched = true;
2240 s.matched_title = 1;
2241 }
2242 }
2243 if (matched) {
2244 gDocsMatches[matchedCountDocs] = s;
2245 matchedCountDocs++;
2246 }
2247 }
2248
2249
2250 // Search for Distribute guides
2251 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2252 // current search comparison, with counters for tag and title,
2253 // used later to improve ranking
2254 var s = DISTRIBUTE_RESOURCES[i];
2255 s.matched_tag = 0;
2256 s.matched_title = 0;
2257 var matched = false;
2258
2259 // Check if query matches any tags; work backwards toward 1 to assist ranking
2260 for (var j = s.keywords.length - 1; j >= 0; j--) {
2261 // it matches a tag
2262 if (s.keywords[j].toLowerCase().match(textRegex)) {
2263 matched = true;
2264 s.matched_tag = j + 1; // add 1 to index position
2265 }
2266 }
2267 // Check if query matches the doc title, but only for current language
2268 if (s.lang == currentLang) {
2269 // if query matches the doc title
2270 if (s.title.toLowerCase().match(textRegex)) {
2271 matched = true;
2272 s.matched_title = 1;
2273 }
2274 }
2275 if (matched) {
2276 gDocsMatches[matchedCountDocs] = s;
2277 matchedCountDocs++;
2278 }
2279 }
2280
2281
2282 // Search for Google guides
2283 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2284 // current search comparison, with counters for tag and title,
2285 // used later to improve ranking
2286 var s = GOOGLE_RESOURCES[i];
2287 s.matched_tag = 0;
2288 s.matched_title = 0;
2289 var matched = false;
2290
2291 // Check if query matches any tags; work backwards toward 1 to assist ranking
2292 for (var j = s.keywords.length - 1; j >= 0; j--) {
2293 // it matches a tag
2294 if (s.keywords[j].toLowerCase().match(textRegex)) {
2295 matched = true;
2296 s.matched_tag = j + 1; // add 1 to index position
2297 }
2298 }
2299 // Check if query matches the doc title, but only for current language
2300 if (s.lang == currentLang) {
2301 // if query matches the doc title
2302 if (s.title.toLowerCase().match(textRegex)) {
2303 matched = true;
2304 s.matched_title = 1;
2305 }
2306 }
2307 if (matched) {
2308 gDocsMatches[matchedCountDocs] = s;
2309 matchedCountDocs++;
2310 }
2311 }
2312
2313
2314 // Search for Samples
2315 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2316 // current search comparison, with counters for tag and title,
2317 // used later to improve ranking
2318 var s = SAMPLES_RESOURCES[i];
2319 s.matched_tag = 0;
2320 s.matched_title = 0;
2321 var matched = false;
2322 // Check if query matches any tags; work backwards toward 1 to assist ranking
2323 for (var j = s.keywords.length - 1; j >= 0; j--) {
2324 // it matches a tag
2325 if (s.keywords[j].toLowerCase().match(textRegex)) {
2326 matched = true;
2327 s.matched_tag = j + 1; // add 1 to index position
2328 }
2329 }
2330 // Check if query matches the doc title, but only for current language
2331 if (s.lang == currentLang) {
2332 // if query matches the doc title.t
2333 if (s.title.toLowerCase().match(textRegex)) {
2334 matched = true;
2335 s.matched_title = 1;
2336 }
2337 }
2338 if (matched) {
2339 gDocsMatches[matchedCountDocs] = s;
2340 matchedCountDocs++;
2341 }
2342 }
2343
2344 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002345 rank_autocomplete_doc_results(text, gDocsMatches);
2346 }
2347
2348 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002349 sync_selection_table(toroot);
2350 return true; // allow the event to bubble up to the search api
2351 }
2352}
2353
Scott Main0e76e7e2013-03-12 10:24:07 -07002354/* Order the jd doc result list based on match quality */
2355function rank_autocomplete_doc_results(query, matches) {
2356 query = query || '';
2357 if (!matches || !matches.length)
2358 return;
2359
2360 var _resultScoreFn = function(match) {
2361 var score = 1.0;
2362
2363 // if the query matched a tag
2364 if (match.matched_tag > 0) {
2365 // multiply score by factor relative to position in tags list (max of 3)
2366 score *= 3 / match.matched_tag;
2367
2368 // if it also matched the title
2369 if (match.matched_title > 0) {
2370 score *= 2;
2371 }
2372 } else if (match.matched_title > 0) {
2373 score *= 3;
2374 }
2375
2376 return score;
2377 };
2378
2379 for (var i=0; i<matches.length; i++) {
2380 matches[i].__resultScore = _resultScoreFn(matches[i]);
2381 }
2382
2383 matches.sort(function(a,b){
2384 var n = b.__resultScore - a.__resultScore;
2385 if (n == 0) // lexicographical sort if scores are the same
2386 n = (a.label < b.label) ? -1 : 1;
2387 return n;
2388 });
2389}
2390
Scott Main7e447ed2013-02-19 17:22:37 -08002391/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002392function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002393 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002394 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002395 return;
2396
2397 // helper function that gets the last occurence index of the given regex
2398 // in the given string, or -1 if not found
2399 var _lastSearch = function(s, re) {
2400 if (s == '')
2401 return -1;
2402 var l = -1;
2403 var tmp;
2404 while ((tmp = s.search(re)) >= 0) {
2405 if (l < 0) l = 0;
2406 l += tmp;
2407 s = s.substr(tmp + 1);
2408 }
2409 return l;
2410 };
2411
2412 // helper function that counts the occurrences of a given character in
2413 // a given string
2414 var _countChar = function(s, c) {
2415 var n = 0;
2416 for (var i=0; i<s.length; i++)
2417 if (s.charAt(i) == c) ++n;
2418 return n;
2419 };
2420
2421 var queryLower = query.toLowerCase();
2422 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2423 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2424 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2425
2426 var _resultScoreFn = function(result) {
2427 // scores are calculated based on exact and prefix matches,
2428 // and then number of path separators (dots) from the last
2429 // match (i.e. favoring classes and deep package names)
2430 var score = 1.0;
2431 var labelLower = result.label.toLowerCase();
2432 var t;
2433 t = _lastSearch(labelLower, partExactAlnumRE);
2434 if (t >= 0) {
2435 // exact part match
2436 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2437 score *= 200 / (partsAfter + 1);
2438 } else {
2439 t = _lastSearch(labelLower, partPrefixAlnumRE);
2440 if (t >= 0) {
2441 // part prefix match
2442 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2443 score *= 20 / (partsAfter + 1);
2444 }
2445 }
2446
2447 return score;
2448 };
2449
Scott Main7e447ed2013-02-19 17:22:37 -08002450 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002451 // if the API is deprecated, default score is 0; otherwise, perform scoring
2452 if (matches[i].deprecated == "true") {
2453 matches[i].__resultScore = 0;
2454 } else {
2455 matches[i].__resultScore = _resultScoreFn(matches[i]);
2456 }
Scott Mainf5089842012-08-14 16:31:07 -07002457 }
2458
Scott Main7e447ed2013-02-19 17:22:37 -08002459 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002460 var n = b.__resultScore - a.__resultScore;
2461 if (n == 0) // lexicographical sort if scores are the same
2462 n = (a.label < b.label) ? -1 : 1;
2463 return n;
2464 });
2465}
2466
Scott Main7e447ed2013-02-19 17:22:37 -08002467/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002468function highlight_autocomplete_result_labels(query) {
2469 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002470 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002471 return;
2472
2473 var queryLower = query.toLowerCase();
2474 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2475 var queryRE = new RegExp(
2476 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2477 for (var i=0; i<gMatches.length; i++) {
2478 gMatches[i].__hilabel = gMatches[i].label.replace(
2479 queryRE, '<b>$1</b>');
2480 }
Scott Main7e447ed2013-02-19 17:22:37 -08002481 for (var i=0; i<gGoogleMatches.length; i++) {
2482 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2483 queryRE, '<b>$1</b>');
2484 }
Scott Mainf5089842012-08-14 16:31:07 -07002485}
2486
2487function search_focus_changed(obj, focused)
2488{
Scott Main3b90aff2013-08-01 18:09:35 -07002489 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002490 if(obj.value == ""){
2491 $(".search .close").addClass("hide");
2492 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002493 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002494 }
2495}
2496
2497function submit_search() {
2498 var query = document.getElementById('search_autocomplete').value;
2499 location.hash = 'q=' + query;
2500 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002501 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002502 return false;
2503}
2504
2505
2506function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002507 $("#searchResults").slideUp('fast', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002508 $(".search .close").addClass("hide");
2509 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002510
Scott Mainf5089842012-08-14 16:31:07 -07002511 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002512
Scott Mainf5089842012-08-14 16:31:07 -07002513 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2514 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002515
2516 // forcefully regain key-up event control (previously jacked by search api)
2517 $("#search_autocomplete").keyup(function(event) {
2518 return search_changed(event, false, toRoot);
2519 });
2520
Scott Mainf5089842012-08-14 16:31:07 -07002521 return false;
2522}
2523
2524
2525
2526/* ########################################################## */
2527/* ################ CUSTOM SEARCH ENGINE ################## */
2528/* ########################################################## */
2529
Scott Mainf5089842012-08-14 16:31:07 -07002530var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002531google.load('search', '1', {"callback" : function() {
2532 searchControl = new google.search.SearchControl();
2533 } });
Scott Mainf5089842012-08-14 16:31:07 -07002534
2535function loadSearchResults() {
2536 document.getElementById("search_autocomplete").style.color = "#000";
2537
Scott Mainf5089842012-08-14 16:31:07 -07002538 searchControl = new google.search.SearchControl();
2539
2540 // use our existing search form and use tabs when multiple searchers are used
2541 drawOptions = new google.search.DrawOptions();
2542 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2543 drawOptions.setInput(document.getElementById("search_autocomplete"));
2544
2545 // configure search result options
2546 searchOptions = new google.search.SearcherOptions();
2547 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2548
2549 // configure each of the searchers, for each tab
2550 devSiteSearcher = new google.search.WebSearch();
2551 devSiteSearcher.setUserDefinedLabel("All");
2552 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2553
2554 designSearcher = new google.search.WebSearch();
2555 designSearcher.setUserDefinedLabel("Design");
2556 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2557
2558 trainingSearcher = new google.search.WebSearch();
2559 trainingSearcher.setUserDefinedLabel("Training");
2560 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2561
2562 guidesSearcher = new google.search.WebSearch();
2563 guidesSearcher.setUserDefinedLabel("Guides");
2564 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2565
2566 referenceSearcher = new google.search.WebSearch();
2567 referenceSearcher.setUserDefinedLabel("Reference");
2568 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2569
Scott Maindf08ada2012-12-03 08:54:37 -08002570 googleSearcher = new google.search.WebSearch();
2571 googleSearcher.setUserDefinedLabel("Google Services");
2572 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2573
Scott Mainf5089842012-08-14 16:31:07 -07002574 blogSearcher = new google.search.WebSearch();
2575 blogSearcher.setUserDefinedLabel("Blog");
2576 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2577
2578 // add each searcher to the search control
2579 searchControl.addSearcher(devSiteSearcher, searchOptions);
2580 searchControl.addSearcher(designSearcher, searchOptions);
2581 searchControl.addSearcher(trainingSearcher, searchOptions);
2582 searchControl.addSearcher(guidesSearcher, searchOptions);
2583 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002584 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002585 searchControl.addSearcher(blogSearcher, searchOptions);
2586
2587 // configure result options
2588 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2589 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2590 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2591 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2592
2593 // upon ajax search, refresh the url and search title
2594 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2595 updateResultTitle(query);
2596 var query = document.getElementById('search_autocomplete').value;
2597 location.hash = 'q=' + query;
2598 });
2599
Scott Mainde295272013-03-25 15:48:35 -07002600 // once search results load, set up click listeners
2601 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2602 addResultClickListeners();
2603 });
2604
Scott Mainf5089842012-08-14 16:31:07 -07002605 // draw the search results box
2606 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2607
2608 // get query and execute the search
2609 searchControl.execute(decodeURI(getQuery(location.hash)));
2610
2611 document.getElementById("search_autocomplete").focus();
2612 addTabListeners();
2613}
2614// End of loadSearchResults
2615
2616
2617google.setOnLoadCallback(function(){
2618 if (location.hash.indexOf("q=") == -1) {
2619 // if there's no query in the url, don't search and make sure results are hidden
2620 $('#searchResults').hide();
2621 return;
2622 } else {
2623 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002624 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002625 $(".search .close").removeClass("hide");
2626 loadSearchResults();
2627 }
2628}, true);
2629
smain@google.com9a818f52014-10-03 09:25:59 -07002630/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2631 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002632function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002633 // Ignore if there's no search bar (some special pages have no header)
2634 if ($("#search-container").length < 1) return;
2635
smain@google.com3b77ab52014-06-17 11:57:27 -07002636 var hash = escape(location.hash.substr(1));
2637 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002638 // Sanity check that there's an element with that ID on the page
2639 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002640 // If the position of the target element is near the top of the page (<20px, where we expect it
2641 // to be because we need to move it down 60px to become in view), then move it down 60px
2642 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2643 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002644 }
2645 }
2646}
2647
Scott Mainf5089842012-08-14 16:31:07 -07002648// when an event on the browser history occurs (back, forward, load) requery hash and do search
2649$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002650 // Ignore if there's no search bar (some special pages have no header)
2651 if ($("#search-container").length < 1) return;
2652
Dirk Doughertyc3921652014-05-13 16:55:26 -07002653 // If the hash isn't a search query or there's an error in the query,
2654 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002655 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2656 // If the results pane is open, close it.
2657 if (!$("#searchResults").is(":hidden")) {
2658 hideResults();
2659 }
Scott Mainb16376f2014-05-21 20:35:47 -07002660 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002661 return;
2662 }
2663
2664 // Otherwise, we have a search to do
2665 var query = decodeURI(getQuery(location.hash));
2666 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002667 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002668 $("#search_autocomplete").focus();
2669 $(".search .close").removeClass("hide");
2670
2671 updateResultTitle(query);
2672});
2673
2674function updateResultTitle(query) {
2675 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2676}
2677
2678// forcefully regain key-up event control (previously jacked by search api)
2679$("#search_autocomplete").keyup(function(event) {
2680 return search_changed(event, false, toRoot);
2681});
2682
2683// add event listeners to each tab so we can track the browser history
2684function addTabListeners() {
2685 var tabHeaders = $(".gsc-tabHeader");
2686 for (var i = 0; i < tabHeaders.length; i++) {
2687 $(tabHeaders[i]).attr("id",i).click(function() {
2688 /*
2689 // make a copy of the page numbers for the search left pane
2690 setTimeout(function() {
2691 // remove any residual page numbers
2692 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002693 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002694 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002695 // and because we're going to remove it (previous line),
2696 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002697 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2698 .clone().appendTo('#searchResults .gsc-tabsArea');
2699 }, 200);
2700 */
2701 });
2702 }
2703 setTimeout(function(){$(tabHeaders[0]).click()},200);
2704}
2705
Scott Mainde295272013-03-25 15:48:35 -07002706// add analytics tracking events to each result link
2707function addResultClickListeners() {
2708 $("#searchResults a.gs-title").each(function(index, link) {
2709 // When user clicks enter for Google search results, track it
2710 $(link).click(function() {
smain@google.comed677d72014-12-12 10:19:17 -08002711 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).attr('href'),
2712 'query: ' + $("#search_autocomplete").val().toLowerCase());
Scott Mainde295272013-03-25 15:48:35 -07002713 });
2714 });
2715}
2716
Scott Mainf5089842012-08-14 16:31:07 -07002717
2718function getQuery(hash) {
2719 var queryParts = hash.split('=');
2720 return queryParts[1];
2721}
2722
2723/* returns the given string with all HTML brackets converted to entities
2724 TODO: move this to the site's JS library */
2725function escapeHTML(string) {
2726 return string.replace(/</g,"&lt;")
2727 .replace(/>/g,"&gt;");
2728}
2729
2730
2731
2732
2733
2734
2735
2736/* ######################################################## */
2737/* ################# JAVADOC REFERENCE ################### */
2738/* ######################################################## */
2739
Scott Main65511c02012-09-07 15:51:32 -07002740/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002741if (location.pathname.indexOf("/reference") == 0) {
2742 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2743 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2744 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002745 $(document).ready(function() {
2746 // init available apis based on user pref
2747 changeApiLevel();
2748 initSidenavHeightResize()
2749 });
2750 }
Scott Main65511c02012-09-07 15:51:32 -07002751}
Scott Mainf5089842012-08-14 16:31:07 -07002752
2753var API_LEVEL_COOKIE = "api_level";
2754var minLevel = 1;
2755var maxLevel = 1;
2756
2757/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002758
Scott Mainf5089842012-08-14 16:31:07 -07002759 function initSidenavHeightResize() {
2760 // Change the drag bar size to nicely fit the scrollbar positions
2761 var $dragBar = $(".ui-resizable-s");
2762 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002763
2764 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002765 containment: "#nav-panels",
2766 handles: "s",
2767 alsoResize: "#packages-nav",
2768 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2769 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2770 });
Scott Main3b90aff2013-08-01 18:09:35 -07002771
Scott Mainf5089842012-08-14 16:31:07 -07002772 }
Scott Main3b90aff2013-08-01 18:09:35 -07002773
Scott Mainf5089842012-08-14 16:31:07 -07002774function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002775 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002776 $('#devdoc-nav').css({
2777 'width' : $('#side-nav').css('width'),
2778 'margin' : $('#side-nav').css('margin')
2779 });
2780 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002781
Scott Mainf5089842012-08-14 16:31:07 -07002782 initSidenavHeightResize();
2783}
2784
2785function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002786 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002787 $('#devdoc-nav').css({
2788 'width' : $('#side-nav').css('width'),
2789 'margin' : $('#side-nav').css('margin')
2790 });
2791 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002792
Scott Mainf5089842012-08-14 16:31:07 -07002793 initSidenavHeightResize();
2794}
2795
2796function buildApiLevelSelector() {
2797 maxLevel = SINCE_DATA.length;
2798 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2799 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2800
2801 minLevel = parseInt($("#doc-api-level").attr("class"));
2802 // Handle provisional api levels; the provisional level will always be the highest possible level
2803 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2804 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2805 if (isNaN(minLevel) && minLevel.length) {
2806 minLevel = maxLevel;
2807 }
2808 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2809 for (var i = maxLevel-1; i >= 0; i--) {
2810 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2811 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2812 select.append(option);
2813 }
2814
2815 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2816 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2817 selectedLevelItem.setAttribute('selected',true);
2818}
2819
2820function changeApiLevel() {
2821 maxLevel = SINCE_DATA.length;
2822 var selectedLevel = maxLevel;
2823
2824 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2825 toggleVisisbleApis(selectedLevel, "body");
2826
smain@google.com6bdcb982014-11-14 11:53:07 -08002827 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002828
2829 if (selectedLevel < minLevel) {
2830 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002831 $("#naMessage").show().html("<div><p><strong>This " + thing
2832 + " requires API level " + minLevel + " or higher.</strong></p>"
2833 + "<p>This document is hidden because your selected API level for the documentation is "
2834 + selectedLevel + ". You can change the documentation API level with the selector "
2835 + "above the left navigation.</p>"
2836 + "<p>For more information about specifying the API level your app requires, "
2837 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2838 + ">Supporting Different Platform Versions</a>.</p>"
2839 + "<input type='button' value='OK, make this page visible' "
2840 + "title='Change the API level to " + minLevel + "' "
2841 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2842 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002843 } else {
2844 $("#naMessage").hide();
2845 }
2846}
2847
2848function toggleVisisbleApis(selectedLevel, context) {
2849 var apis = $(".api",context);
2850 apis.each(function(i) {
2851 var obj = $(this);
2852 var className = obj.attr("class");
2853 var apiLevelIndex = className.lastIndexOf("-")+1;
2854 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2855 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2856 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2857 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2858 return;
2859 }
2860 apiLevel = parseInt(apiLevel);
2861
2862 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2863 var selectedLevelNum = parseInt(selectedLevel)
2864 var apiLevelNum = parseInt(apiLevel);
2865 if (isNaN(apiLevelNum)) {
2866 apiLevelNum = maxLevel;
2867 }
2868
2869 // Grey things out that aren't available and give a tooltip title
2870 if (apiLevelNum > selectedLevelNum) {
2871 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002872 + apiLevel + "\" or higher. To reveal, change the target API level "
2873 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002874 }
Scott Mainf5089842012-08-14 16:31:07 -07002875 else obj.removeClass("absent").removeAttr("title");
2876 });
2877}
2878
2879
2880
2881
2882/* ################# SIDENAV TREE VIEW ################### */
2883
2884function new_node(me, mom, text, link, children_data, api_level)
2885{
2886 var node = new Object();
2887 node.children = Array();
2888 node.children_data = children_data;
2889 node.depth = mom.depth + 1;
2890
2891 node.li = document.createElement("li");
2892 mom.get_children_ul().appendChild(node.li);
2893
2894 node.label_div = document.createElement("div");
2895 node.label_div.className = "label";
2896 if (api_level != null) {
2897 $(node.label_div).addClass("api");
2898 $(node.label_div).addClass("api-level-"+api_level);
2899 }
2900 node.li.appendChild(node.label_div);
2901
2902 if (children_data != null) {
2903 node.expand_toggle = document.createElement("a");
2904 node.expand_toggle.href = "javascript:void(0)";
2905 node.expand_toggle.onclick = function() {
2906 if (node.expanded) {
2907 $(node.get_children_ul()).slideUp("fast");
2908 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2909 node.expanded = false;
2910 } else {
2911 expand_node(me, node);
2912 }
2913 };
2914 node.label_div.appendChild(node.expand_toggle);
2915
2916 node.plus_img = document.createElement("img");
2917 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2918 node.plus_img.className = "plus";
2919 node.plus_img.width = "8";
2920 node.plus_img.border = "0";
2921 node.expand_toggle.appendChild(node.plus_img);
2922
2923 node.expanded = false;
2924 }
2925
2926 var a = document.createElement("a");
2927 node.label_div.appendChild(a);
2928 node.label = document.createTextNode(text);
2929 a.appendChild(node.label);
2930 if (link) {
2931 a.href = me.toroot + link;
2932 } else {
2933 if (children_data != null) {
2934 a.className = "nolink";
2935 a.href = "javascript:void(0)";
2936 a.onclick = node.expand_toggle.onclick;
2937 // This next line shouldn't be necessary. I'll buy a beer for the first
2938 // person who figures out how to remove this line and have the link
2939 // toggle shut on the first try. --joeo@android.com
2940 node.expanded = false;
2941 }
2942 }
Scott Main3b90aff2013-08-01 18:09:35 -07002943
Scott Mainf5089842012-08-14 16:31:07 -07002944
2945 node.children_ul = null;
2946 node.get_children_ul = function() {
2947 if (!node.children_ul) {
2948 node.children_ul = document.createElement("ul");
2949 node.children_ul.className = "children_ul";
2950 node.children_ul.style.display = "none";
2951 node.li.appendChild(node.children_ul);
2952 }
2953 return node.children_ul;
2954 };
2955
2956 return node;
2957}
2958
Robert Lyd2dd6e52012-11-29 21:28:48 -08002959
2960
2961
Scott Mainf5089842012-08-14 16:31:07 -07002962function expand_node(me, node)
2963{
2964 if (node.children_data && !node.expanded) {
2965 if (node.children_visited) {
2966 $(node.get_children_ul()).slideDown("fast");
2967 } else {
2968 get_node(me, node);
2969 if ($(node.label_div).hasClass("absent")) {
2970 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07002971 }
Scott Mainf5089842012-08-14 16:31:07 -07002972 $(node.get_children_ul()).slideDown("fast");
2973 }
2974 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2975 node.expanded = true;
2976
2977 // perform api level toggling because new nodes are new to the DOM
2978 var selectedLevel = $("#apiLevelSelector option:selected").val();
2979 toggleVisisbleApis(selectedLevel, "#side-nav");
2980 }
2981}
2982
2983function get_node(me, mom)
2984{
2985 mom.children_visited = true;
2986 for (var i in mom.children_data) {
2987 var node_data = mom.children_data[i];
2988 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2989 node_data[2], node_data[3]);
2990 }
2991}
2992
2993function this_page_relative(toroot)
2994{
2995 var full = document.location.pathname;
2996 var file = "";
2997 if (toroot.substr(0, 1) == "/") {
2998 if (full.substr(0, toroot.length) == toroot) {
2999 return full.substr(toroot.length);
3000 } else {
3001 // the file isn't under toroot. Fail.
3002 return null;
3003 }
3004 } else {
3005 if (toroot != "./") {
3006 toroot = "./" + toroot;
3007 }
3008 do {
3009 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3010 var pos = full.lastIndexOf("/");
3011 file = full.substr(pos) + file;
3012 full = full.substr(0, pos);
3013 toroot = toroot.substr(0, toroot.length-3);
3014 }
3015 } while (toroot != "" && toroot != "/");
3016 return file.substr(1);
3017 }
3018}
3019
3020function find_page(url, data)
3021{
3022 var nodes = data;
3023 var result = null;
3024 for (var i in nodes) {
3025 var d = nodes[i];
3026 if (d[1] == url) {
3027 return new Array(i);
3028 }
3029 else if (d[2] != null) {
3030 result = find_page(url, d[2]);
3031 if (result != null) {
3032 return (new Array(i).concat(result));
3033 }
3034 }
3035 }
3036 return null;
3037}
3038
Scott Mainf5089842012-08-14 16:31:07 -07003039function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07003040 // load json file for navtree data
3041 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3042 // when the file is loaded, initialize the tree
3043 if(jqxhr.status === 200) {
3044 init_navtree("tree-list", toroot, NAVTREE_DATA);
3045 }
3046 });
Scott Main3b90aff2013-08-01 18:09:35 -07003047
Scott Mainf5089842012-08-14 16:31:07 -07003048 // perform api level toggling because because the whole tree is new to the DOM
3049 var selectedLevel = $("#apiLevelSelector option:selected").val();
3050 toggleVisisbleApis(selectedLevel, "#side-nav");
3051}
3052
3053function init_navtree(navtree_id, toroot, root_nodes)
3054{
3055 var me = new Object();
3056 me.toroot = toroot;
3057 me.node = new Object();
3058
3059 me.node.li = document.getElementById(navtree_id);
3060 me.node.children_data = root_nodes;
3061 me.node.children = new Array();
3062 me.node.children_ul = document.createElement("ul");
3063 me.node.get_children_ul = function() { return me.node.children_ul; };
3064 //me.node.children_ul.className = "children_ul";
3065 me.node.li.appendChild(me.node.children_ul);
3066 me.node.depth = 0;
3067
3068 get_node(me, me.node);
3069
3070 me.this_page = this_page_relative(toroot);
3071 me.breadcrumbs = find_page(me.this_page, root_nodes);
3072 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3073 var mom = me.node;
3074 for (var i in me.breadcrumbs) {
3075 var j = me.breadcrumbs[i];
3076 mom = mom.children[j];
3077 expand_node(me, mom);
3078 }
3079 mom.label_div.className = mom.label_div.className + " selected";
3080 addLoadEvent(function() {
3081 scrollIntoView("nav-tree");
3082 });
3083 }
3084}
3085
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003086
3087
3088
3089
3090
3091
3092
Robert Lyd2dd6e52012-11-29 21:28:48 -08003093/* TODO: eliminate redundancy with non-google functions */
3094function init_google_navtree(navtree_id, toroot, root_nodes)
3095{
3096 var me = new Object();
3097 me.toroot = toroot;
3098 me.node = new Object();
3099
3100 me.node.li = document.getElementById(navtree_id);
3101 me.node.children_data = root_nodes;
3102 me.node.children = new Array();
3103 me.node.children_ul = document.createElement("ul");
3104 me.node.get_children_ul = function() { return me.node.children_ul; };
3105 //me.node.children_ul.className = "children_ul";
3106 me.node.li.appendChild(me.node.children_ul);
3107 me.node.depth = 0;
3108
3109 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003110}
3111
3112function new_google_node(me, mom, text, link, children_data, api_level)
3113{
3114 var node = new Object();
3115 var child;
3116 node.children = Array();
3117 node.children_data = children_data;
3118 node.depth = mom.depth + 1;
3119 node.get_children_ul = function() {
3120 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003121 node.children_ul = document.createElement("ul");
3122 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003123 node.li.appendChild(node.children_ul);
3124 }
3125 return node.children_ul;
3126 };
3127 node.li = document.createElement("li");
3128
3129 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003130
3131
Robert Lyd2dd6e52012-11-29 21:28:48 -08003132 if(link) {
3133 child = document.createElement("a");
3134
3135 }
3136 else {
3137 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003138 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003139
3140 }
3141 if (children_data != null) {
3142 node.li.className="nav-section";
3143 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003144 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003145 node.li.appendChild(node.label_div);
3146 get_google_node(me, node);
3147 node.label_div.appendChild(child);
3148 }
3149 else {
3150 node.li.appendChild(child);
3151 }
3152 if(link) {
3153 child.href = me.toroot + link;
3154 }
3155 node.label = document.createTextNode(text);
3156 child.appendChild(node.label);
3157
3158 node.children_ul = null;
3159
3160 return node;
3161}
3162
3163function get_google_node(me, mom)
3164{
3165 mom.children_visited = true;
3166 var linkText;
3167 for (var i in mom.children_data) {
3168 var node_data = mom.children_data[i];
3169 linkText = node_data[0];
3170
3171 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3172 linkText = linkText.substr(19, linkText.length);
3173 }
3174 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3175 node_data[2], node_data[3]);
3176 }
3177}
Scott Mainad08f072013-08-20 16:49:57 -07003178
3179
3180
3181
3182
3183
3184/****** NEW version of script to build google and sample navs dynamically ******/
3185// TODO: update Google reference docs to tolerate this new implementation
3186
Scott Maine624b3f2013-09-12 12:56:41 -07003187var NODE_NAME = 0;
3188var NODE_HREF = 1;
3189var NODE_GROUP = 2;
3190var NODE_TAGS = 3;
3191var NODE_CHILDREN = 4;
3192
Scott Mainad08f072013-08-20 16:49:57 -07003193function init_google_navtree2(navtree_id, data)
3194{
3195 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003196 for (var i in data) {
3197 var node_data = data[i];
3198 $containerUl.append(new_google_node2(node_data));
3199 }
3200
Scott Main70557ee2013-10-30 14:47:40 -07003201 // Make all third-generation list items 'sticky' to prevent them from collapsing
3202 $containerUl.find('li li li.nav-section').addClass('sticky');
3203
Scott Mainad08f072013-08-20 16:49:57 -07003204 initExpandableNavItems("#"+navtree_id);
3205}
3206
3207function new_google_node2(node_data)
3208{
Scott Maine624b3f2013-09-12 12:56:41 -07003209 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003210 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3211 linkText = linkText.substr(19, linkText.length);
3212 }
3213 var $li = $('<li>');
3214 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003215 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003216 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3217 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003218 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003219 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3220 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003221 }
3222 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003223 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003224 $li.addClass("nav-section");
3225 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003226 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003227
Scott Maine624b3f2013-09-12 12:56:41 -07003228 for (var i in node_data[NODE_CHILDREN]) {
3229 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003230 $childUl.append(new_google_node2(child_node_data));
3231 }
3232 $li.append($childUl);
3233 }
3234 $li.prepend($a);
3235
3236 return $li;
3237}
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
Robert Lyd2dd6e52012-11-29 21:28:48 -08003249function showGoogleRefTree() {
3250 init_default_google_navtree(toRoot);
3251 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003252}
3253
3254function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003255 // load json file for navtree data
3256 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3257 // when the file is loaded, initialize the tree
3258 if(jqxhr.status === 200) {
3259 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3260 highlightSidenav();
3261 resizeNav();
3262 }
3263 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003264}
3265
3266function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003267 // load json file for navtree data
3268 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3269 // when the file is loaded, initialize the tree
3270 if(jqxhr.status === 200) {
3271 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3272 highlightSidenav();
3273 resizeNav();
3274 }
3275 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003276}
3277
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003278function showSamplesRefTree() {
3279 init_default_samples_navtree(toRoot);
3280}
3281
3282function init_default_samples_navtree(toroot) {
3283 // load json file for navtree data
3284 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3285 // when the file is loaded, initialize the tree
3286 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003287 // hack to remove the "about the samples" link then put it back in
3288 // after we nuke the list to remove the dummy static list of samples
3289 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3290 $("#nav.samples-nav").empty();
3291 $("#nav.samples-nav").append($firstLi);
3292
Scott Mainad08f072013-08-20 16:49:57 -07003293 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003294 highlightSidenav();
3295 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003296 if ($("#jd-content #samples").length) {
3297 showSamples();
3298 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003299 }
3300 });
3301}
3302
Scott Mainf5089842012-08-14 16:31:07 -07003303/* TOGGLE INHERITED MEMBERS */
3304
3305/* Toggle an inherited class (arrow toggle)
3306 * @param linkObj The link that was clicked.
3307 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3308 * 'null' to simply toggle.
3309 */
3310function toggleInherited(linkObj, expand) {
3311 var base = linkObj.getAttribute("id");
3312 var list = document.getElementById(base + "-list");
3313 var summary = document.getElementById(base + "-summary");
3314 var trigger = document.getElementById(base + "-trigger");
3315 var a = $(linkObj);
3316 if ( (expand == null && a.hasClass("closed")) || expand ) {
3317 list.style.display = "none";
3318 summary.style.display = "block";
3319 trigger.src = toRoot + "assets/images/triangle-opened.png";
3320 a.removeClass("closed");
3321 a.addClass("opened");
3322 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3323 list.style.display = "block";
3324 summary.style.display = "none";
3325 trigger.src = toRoot + "assets/images/triangle-closed.png";
3326 a.removeClass("opened");
3327 a.addClass("closed");
3328 }
3329 return false;
3330}
3331
3332/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3333 * @param linkObj The link that was clicked.
3334 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3335 * 'null' to simply toggle.
3336 */
3337function toggleAllInherited(linkObj, expand) {
3338 var a = $(linkObj);
3339 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3340 var expandos = $(".jd-expando-trigger", table);
3341 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3342 expandos.each(function(i) {
3343 toggleInherited(this, true);
3344 });
3345 a.text("[Collapse]");
3346 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3347 expandos.each(function(i) {
3348 toggleInherited(this, false);
3349 });
3350 a.text("[Expand]");
3351 }
3352 return false;
3353}
3354
3355/* Toggle all inherited members in the class (link in the class title)
3356 */
3357function toggleAllClassInherited() {
3358 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3359 var toggles = $(".toggle-all", $("#body-content"));
3360 if (a.text() == "[Expand All]") {
3361 toggles.each(function(i) {
3362 toggleAllInherited(this, true);
3363 });
3364 a.text("[Collapse All]");
3365 } else {
3366 toggles.each(function(i) {
3367 toggleAllInherited(this, false);
3368 });
3369 a.text("[Expand All]");
3370 }
3371 return false;
3372}
3373
3374/* Expand all inherited members in the class. Used when initiating page search */
3375function ensureAllInheritedExpanded() {
3376 var toggles = $(".toggle-all", $("#body-content"));
3377 toggles.each(function(i) {
3378 toggleAllInherited(this, true);
3379 });
3380 $("#toggleAllClassInherited").text("[Collapse All]");
3381}
3382
3383
3384/* HANDLE KEY EVENTS
3385 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3386 */
3387var agent = navigator['userAgent'].toLowerCase();
3388var mac = agent.indexOf("macintosh") != -1;
3389
3390$(document).keydown( function(e) {
3391var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3392 if (control && e.which == 70) { // 70 is "F"
3393 ensureAllInheritedExpanded();
3394 }
3395});
Scott Main498d7102013-08-21 15:47:38 -07003396
3397
3398
3399
3400
3401
3402/* On-demand functions */
3403
3404/** Move sample code line numbers out of PRE block and into non-copyable column */
3405function initCodeLineNumbers() {
3406 var numbers = $("#codesample-block a.number");
3407 if (numbers.length) {
3408 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3409 }
3410
3411 $(document).ready(function() {
3412 // select entire line when clicked
3413 $("span.code-line").click(function() {
3414 if (!shifted) {
3415 selectText(this);
3416 }
3417 });
3418 // invoke line link on double click
3419 $(".code-line").dblclick(function() {
3420 document.location.hash = $(this).attr('id');
3421 });
3422 // highlight the line when hovering on the number
3423 $("#codesample-line-numbers a.number").mouseover(function() {
3424 var id = $(this).attr('href');
3425 $(id).css('background','#e7e7e7');
3426 });
3427 $("#codesample-line-numbers a.number").mouseout(function() {
3428 var id = $(this).attr('href');
3429 $(id).css('background','none');
3430 });
3431 });
3432}
3433
3434// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3435var shifted = false;
3436$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3437
3438// courtesy of jasonedelman.com
3439function selectText(element) {
3440 var doc = document
3441 , range, selection
3442 ;
3443 if (doc.body.createTextRange) { //ms
3444 range = doc.body.createTextRange();
3445 range.moveToElementText(element);
3446 range.select();
3447 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003448 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003449 range = doc.createRange();
3450 range.selectNodeContents(element);
3451 selection.removeAllRanges();
3452 selection.addRange(range);
3453 }
Scott Main285f0772013-08-22 23:22:09 +00003454}
Scott Main03aca9a2013-10-31 07:20:55 -07003455
3456
3457
3458
3459/** Display links and other information about samples that match the
3460 group specified by the URL */
3461function showSamples() {
3462 var group = $("#samples").attr('class');
3463 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3464
3465 var $ul = $("<ul>");
3466 $selectedLi = $("#nav li.selected");
3467
3468 $selectedLi.children("ul").children("li").each(function() {
3469 var $li = $("<li>").append($(this).find("a").first().clone());
3470 $ul.append($li);
3471 });
3472
3473 $("#samples").append($ul);
3474
3475}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003476
3477
3478
3479/* ########################################################## */
3480/* ################### RESOURCE CARDS ##################### */
3481/* ########################################################## */
3482
3483/** Handle resource queries, collections, and grids (sections). Requires
3484 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3485
3486(function() {
3487 // Prevent the same resource from being loaded more than once per page.
3488 var addedPageResources = {};
3489
3490 $(document).ready(function() {
3491 $('.resource-widget').each(function() {
3492 initResourceWidget(this);
3493 });
3494
3495 /* Pass the line height to ellipsisfade() to adjust the height of the
3496 text container to show the max number of lines possible, without
3497 showing lines that are cut off. This works with the css ellipsis
3498 classes to fade last text line and apply an ellipsis char. */
3499
Scott Mainb16376f2014-05-21 20:35:47 -07003500 //card text currently uses 15px line height.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003501 var lineHeight = 15;
3502 $('.card-info .text').ellipsisfade(lineHeight);
3503 });
3504
3505 /*
3506 Three types of resource layouts:
3507 Flow - Uses a fixed row-height flow using float left style.
3508 Carousel - Single card slideshow all same dimension absolute.
3509 Stack - Uses fixed columns and flexible element height.
3510 */
3511 function initResourceWidget(widget) {
3512 var $widget = $(widget);
3513 var isFlow = $widget.hasClass('resource-flow-layout'),
3514 isCarousel = $widget.hasClass('resource-carousel-layout'),
3515 isStack = $widget.hasClass('resource-stack-layout');
3516
3517 // find size of widget by pulling out its class name
3518 var sizeCols = 1;
3519 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
3520 if (m) {
3521 sizeCols = parseInt(m[1], 10);
3522 }
3523
3524 var opts = {
3525 cardSizes: ($widget.data('cardsizes') || '').split(','),
3526 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3527 itemsPerPage: $widget.data('itemsperpage'),
3528 sortOrder: $widget.data('sortorder'),
3529 query: $widget.data('query'),
3530 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003531 sizeCols: sizeCols,
3532 /* Added by LFL 6/6/14 */
3533 resourceStyle: $widget.data('resourcestyle') || 'card',
3534 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003535 };
3536
3537 // run the search for the set of resources to show
3538
3539 var resources = buildResourceList(opts);
3540
3541 if (isFlow) {
3542 drawResourcesFlowWidget($widget, opts, resources);
3543 } else if (isCarousel) {
3544 drawResourcesCarouselWidget($widget, opts, resources);
3545 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003546 /* Looks like this got removed and is not used, so repurposing for the
3547 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003548 Modified by LFL 6/6/14
3549 */
3550 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003551 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003552 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003553 }
3554 }
3555
3556 /* Initializes a Resource Carousel Widget */
3557 function drawResourcesCarouselWidget($widget, opts, resources) {
3558 $widget.empty();
3559 var plusone = true; //always show plusone on carousel
3560
3561 $widget.addClass('resource-card slideshow-container')
3562 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3563 .append($('<a>').addClass('slideshow-next').text('Next'));
3564
3565 var css = { 'width': $widget.width() + 'px',
3566 'height': $widget.height() + 'px' };
3567
3568 var $ul = $('<ul>');
3569
3570 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003571 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003572 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003573 .decorateResourceCard(resources[i],plusone);
3574
3575 $('<li>').css(css)
3576 .append($card)
3577 .appendTo($ul);
3578 }
3579
3580 $('<div>').addClass('frame')
3581 .append($ul)
3582 .appendTo($widget);
3583
3584 $widget.dacSlideshow({
3585 auto: true,
3586 btnPrev: '.slideshow-prev',
3587 btnNext: '.slideshow-next'
3588 });
3589 };
3590
Robert Lye7eeb402014-06-03 19:35:24 -07003591 /* Initializes a Resource Card Stack Widget (column-based layout)
3592 Modified by LFL 6/6/14
3593 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003594 function drawResourcesStackWidget($widget, opts, resources, sections) {
3595 // Don't empty widget, grab all items inside since they will be the first
3596 // items stacked, followed by the resource query
3597 var plusone = true; //by default show plusone on section cards
3598 var cards = $widget.find('.resource-card').detach().toArray();
3599 var numStacks = opts.numStacks || 1;
3600 var $stacks = [];
3601 var urlString;
3602
3603 for (var i = 0; i < numStacks; ++i) {
3604 $stacks[i] = $('<div>').addClass('resource-card-stack')
3605 .appendTo($widget);
3606 }
3607
3608 var sectionResources = [];
3609
3610 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003611 if (sections) {
3612 for (var i = 0; i < sections.length; ++i) {
3613 if (!sections[i].sections || !sections[i].sections.length) {
3614 // Render it as a resource card
3615 sectionResources.push(
3616 $('<a>')
3617 .addClass('resource-card section-card')
3618 .attr('href', cleanUrl(sections[i].resource.url))
3619 .decorateResourceCard(sections[i].resource,plusone)[0]
3620 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003621
Robert Lye7eeb402014-06-03 19:35:24 -07003622 } else {
3623 cards.push(
3624 $('<div>')
3625 .addClass('resource-card section-card-menu')
3626 .decorateResourceSection(sections[i],plusone)[0]
3627 );
3628 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003629 }
3630 }
3631
3632 cards = cards.concat(sectionResources);
3633
3634 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003635 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003636
Robert Lye7eeb402014-06-03 19:35:24 -07003637 if (opts.resourceStyle.indexOf('related') > -1) {
3638 $card.addClass('related-card');
3639 }
smain@google.com95948b82014-06-16 19:24:25 -07003640
Dirk Doughertyc3921652014-05-13 16:55:26 -07003641 cards.push($card[0]);
3642 }
3643
Robert Lye7eeb402014-06-03 19:35:24 -07003644 if (opts.stackSort != 'false') {
3645 for (var i = 0; i < cards.length; ++i) {
3646 // Find the stack with the shortest height, but give preference to
3647 // left to right order.
3648 var minHeight = $stacks[0].height();
3649 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003650
Robert Lye7eeb402014-06-03 19:35:24 -07003651 for (var j = 1; j < numStacks; ++j) {
3652 var height = $stacks[j].height();
3653 if (height < minHeight - 45) {
3654 minHeight = height;
3655 minIndex = j;
3656 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003657 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003658
Robert Lye7eeb402014-06-03 19:35:24 -07003659 $stacks[minIndex].append($(cards[i]));
3660 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003661 }
3662
3663 };
smain@google.com95948b82014-06-16 19:24:25 -07003664
3665 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003666 Create a resource card using the given resource object and a list of html
3667 configured options. Returns a jquery object containing the element.
3668 */
smain@google.com95948b82014-06-16 19:24:25 -07003669 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003670 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003671
Robert Lye7eeb402014-06-03 19:35:24 -07003672 // The difference here is that generic cards are not entirely clickable
3673 // so its a div instead of an a tag, also the generic one is not given
3674 // the resource-card class so it appears with a transparent background
3675 // and can be styled in whatever way the css setup.
3676 if (opts.resourceStyle == 'generic') {
3677 $el = $('<div>')
3678 .addClass('resource')
3679 .attr('href', cleanUrl(resource.url))
3680 .decorateResource(resource, opts);
3681 } else {
3682 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003683
Robert Lye7eeb402014-06-03 19:35:24 -07003684 $el = $('<a>')
3685 .addClass(cls)
3686 .attr('href', cleanUrl(resource.url))
3687 .decorateResourceCard(resource, plusone);
3688 }
smain@google.com95948b82014-06-16 19:24:25 -07003689
Robert Lye7eeb402014-06-03 19:35:24 -07003690 return $el;
3691 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003692
3693 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3694 function drawResourcesFlowWidget($widget, opts, resources) {
3695 $widget.empty();
3696 var cardSizes = opts.cardSizes || ['6x6'];
3697 var i = 0, j = 0;
3698 var plusone = true; // by default show plusone on resource cards
3699
3700 while (i < resources.length) {
3701 var cardSize = cardSizes[j++ % cardSizes.length];
3702 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003703 // Some card sizes do not get a plusone button, such as where space is constrained
3704 // or for cards commonly embedded in docs (to improve overall page speed).
3705 plusone = !((cardSize == "6x2") || (cardSize == "6x3") ||
3706 (cardSize == "9x2") || (cardSize == "9x3") ||
3707 (cardSize == "12x2") || (cardSize == "12x3"));
3708
3709 // A stack has a third dimension which is the number of stacked items
3710 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3711 var stackCount = 0;
3712 var $stackDiv = null;
3713
3714 if (isStack) {
3715 // Create a stack container which should have the dimensions defined
3716 // by the product of the items inside.
3717 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
3718 + 'x' + isStack[2] * isStack[3]) .appendTo($widget);
3719 }
3720
3721 // Build each stack item or just a single item
3722 do {
3723 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003724
Robert Lye7eeb402014-06-03 19:35:24 -07003725 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003726
3727 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003728 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003729
Dirk Doughertyc3921652014-05-13 16:55:26 -07003730 if (isStack) {
3731 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3732 if (++stackCount == parseInt(isStack[3])) {
3733 $card.addClass('resource-card-row-stack-last');
3734 stackCount = 0;
3735 }
3736 } else {
3737 stackCount = 0;
3738 }
3739
Robert Lye7eeb402014-06-03 19:35:24 -07003740 $card.appendTo($stackDiv || $widget);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003741
3742 } while (++i < resources.length && stackCount > 0);
3743 }
3744 }
3745
3746 /* Build a site map of resources using a section as a root. */
3747 function buildSectionList(opts) {
3748 if (opts.section && SECTION_BY_ID[opts.section]) {
3749 return SECTION_BY_ID[opts.section].sections || [];
3750 }
3751 return [];
3752 }
3753
3754 function buildResourceList(opts) {
3755 var maxResults = opts.maxResults || 100;
3756
3757 var query = opts.query || '';
3758 var expressions = parseResourceQuery(query);
3759 var addedResourceIndices = {};
3760 var results = [];
3761
3762 for (var i = 0; i < expressions.length; i++) {
3763 var clauses = expressions[i];
3764
3765 // build initial set of resources from first clause
3766 var firstClause = clauses[0];
3767 var resources = [];
3768 switch (firstClause.attr) {
3769 case 'type':
3770 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3771 break;
3772 case 'lang':
3773 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3774 break;
3775 case 'tag':
3776 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3777 break;
3778 case 'collection':
3779 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3780 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3781 break;
3782 case 'section':
3783 var urls = SITE_MAP[firstClause.value].sections || [];
3784 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3785 break;
3786 }
3787 // console.log(firstClause.attr + ':' + firstClause.value);
3788 resources = resources || [];
3789
3790 // use additional clauses to filter corpus
3791 if (clauses.length > 1) {
3792 var otherClauses = clauses.slice(1);
3793 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3794 }
3795
3796 // filter out resources already added
3797 if (i > 1) {
3798 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3799 }
3800
3801 // add to list of already added indices
3802 for (var j = 0; j < resources.length; j++) {
3803 // console.log(resources[j].title);
3804 addedResourceIndices[resources[j].index] = 1;
3805 }
3806
3807 // concat to final results list
3808 results = results.concat(resources);
3809 }
3810
3811 if (opts.sortOrder && results.length) {
3812 var attr = opts.sortOrder;
3813
3814 if (opts.sortOrder == 'random') {
3815 var i = results.length, j, temp;
3816 while (--i) {
3817 j = Math.floor(Math.random() * (i + 1));
3818 temp = results[i];
3819 results[i] = results[j];
3820 results[j] = temp;
3821 }
3822 } else {
3823 var desc = attr.charAt(0) == '-';
3824 if (desc) {
3825 attr = attr.substring(1);
3826 }
3827 results = results.sort(function(x,y) {
3828 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3829 });
3830 }
3831 }
3832
3833 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3834 results = results.slice(0, maxResults);
3835
3836 for (var j = 0; j < results.length; ++j) {
3837 addedPageResources[results[j].index] = 1;
3838 }
3839
3840 return results;
3841 }
3842
3843
3844 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3845 return function(resource) {
3846 return !addedResourceIndices[resource.index];
3847 };
3848 }
3849
3850
3851 function getResourceMatchesClausesFilter(clauses) {
3852 return function(resource) {
3853 return doesResourceMatchClauses(resource, clauses);
3854 };
3855 }
3856
3857
3858 function doesResourceMatchClauses(resource, clauses) {
3859 for (var i = 0; i < clauses.length; i++) {
3860 var map;
3861 switch (clauses[i].attr) {
3862 case 'type':
3863 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3864 break;
3865 case 'lang':
3866 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3867 break;
3868 case 'tag':
3869 map = IS_RESOURCE_TAGGED[clauses[i].value];
3870 break;
3871 }
3872
3873 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3874 return clauses[i].negative;
3875 }
3876 }
3877 return true;
3878 }
smain@google.com95948b82014-06-16 19:24:25 -07003879
Robert Lye7eeb402014-06-03 19:35:24 -07003880 function cleanUrl(url)
3881 {
3882 if (url && url.indexOf('//') === -1) {
3883 url = toRoot + url;
3884 }
smain@google.com95948b82014-06-16 19:24:25 -07003885
Robert Lye7eeb402014-06-03 19:35:24 -07003886 return url;
3887 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003888
3889
3890 function parseResourceQuery(query) {
3891 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3892 var expressions = [];
3893 var expressionStrs = query.split(',') || [];
3894 for (var i = 0; i < expressionStrs.length; i++) {
3895 var expr = expressionStrs[i] || '';
3896
3897 // Break expression into clauses (clause e.g. 'tag:foo')
3898 var clauses = [];
3899 var clauseStrs = expr.split(/(?=[\+\-])/);
3900 for (var j = 0; j < clauseStrs.length; j++) {
3901 var clauseStr = clauseStrs[j] || '';
3902
3903 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3904 var parts = clauseStr.split(':');
3905 var clause = {};
3906
3907 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3908 if (clause.attr) {
3909 if (clause.attr.charAt(0) == '+') {
3910 clause.attr = clause.attr.substring(1);
3911 } else if (clause.attr.charAt(0) == '-') {
3912 clause.negative = true;
3913 clause.attr = clause.attr.substring(1);
3914 }
3915 }
3916
3917 if (parts.length > 1) {
3918 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3919 }
3920
3921 clauses.push(clause);
3922 }
3923
3924 if (!clauses.length) {
3925 continue;
3926 }
3927
3928 expressions.push(clauses);
3929 }
3930
3931 return expressions;
3932 }
3933})();
3934
3935(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07003936
smain@google.com95948b82014-06-16 19:24:25 -07003937 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003938 Utility method for creating dom for the description area of a card.
3939 Used in decorateResourceCard and decorateResource.
3940 */
3941 function buildResourceCardDescription(resource, plusone) {
3942 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07003943
Robert Lye7eeb402014-06-03 19:35:24 -07003944 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07003945
Robert Lye7eeb402014-06-03 19:35:24 -07003946 if (resource.cta) {
3947 $description.append($('<a>').addClass('cta').html(resource.cta));
3948 }
smain@google.com95948b82014-06-16 19:24:25 -07003949
Robert Lye7eeb402014-06-03 19:35:24 -07003950 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07003951 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07003952 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07003953
Robert Lye7eeb402014-06-03 19:35:24 -07003954 $description.append($('<div>').addClass('util')
3955 .append($('<div>').addClass('g-plusone')
3956 .attr('data-size', 'small')
3957 .attr('data-align', 'right')
3958 .attr('data-href', plusurl)));
3959 }
smain@google.com95948b82014-06-16 19:24:25 -07003960
Robert Lye7eeb402014-06-03 19:35:24 -07003961 return $description;
3962 }
smain@google.com95948b82014-06-16 19:24:25 -07003963
3964
Dirk Doughertyc3921652014-05-13 16:55:26 -07003965 /* Simple jquery function to create dom for a standard resource card */
3966 $.fn.decorateResourceCard = function(resource,plusone) {
3967 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07003968 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07003969 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07003970
Robert Lye7eeb402014-06-03 19:35:24 -07003971 if (imgUrl.indexOf('//') === -1) {
3972 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003973 }
Robert Lye7eeb402014-06-03 19:35:24 -07003974
3975 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07003976 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07003977 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07003978 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07003979
Robert Lye7eeb402014-06-03 19:35:24 -07003980 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
3981 .append($('<div>').addClass('section').text(section))
3982 .append($('<div>').addClass('title').html(resource.title))
3983 .append(buildResourceCardDescription(resource, plusone))
3984 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003985
3986 return this;
3987 };
3988
3989 /* Simple jquery function to create dom for a resource section card (menu) */
3990 $.fn.decorateResourceSection = function(section,plusone) {
3991 var resource = section.resource;
3992 //keep url clean for matching and offline mode handling
3993 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
3994 var $base = $('<a>')
3995 .addClass('card-bg')
3996 .attr('href', resource.url)
3997 .append($('<div>').addClass('card-section-icon')
3998 .append($('<div>').addClass('icon'))
3999 .append($('<div>').addClass('section').html(resource.title)))
4000 .appendTo(this);
4001
4002 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
4003
4004 if (section.sections && section.sections.length) {
4005 // Recurse the section sub-tree to find a resource image.
4006 var stack = [section];
4007
4008 while (stack.length) {
4009 if (stack[0].resource.image) {
4010 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4011 break;
4012 }
4013
4014 if (stack[0].sections) {
4015 stack = stack.concat(stack[0].sections);
4016 }
4017
4018 stack.shift();
4019 }
4020
4021 var $ul = $('<ul>')
4022 .appendTo($cardInfo);
4023
4024 var max = section.sections.length > 3 ? 3 : section.sections.length;
4025
4026 for (var i = 0; i < max; ++i) {
4027
4028 var subResource = section.sections[i];
4029 if (!plusone) {
4030 $('<li>')
4031 .append($('<a>').attr('href', subResource.url)
4032 .append($('<div>').addClass('title').html(subResource.title))
4033 .append($('<div>').addClass('description ellipsis')
4034 .append($('<div>').addClass('text').html(subResource.summary))
4035 .append($('<div>').addClass('util'))))
4036 .appendTo($ul);
4037 } else {
4038 $('<li>')
4039 .append($('<a>').attr('href', subResource.url)
4040 .append($('<div>').addClass('title').html(subResource.title))
4041 .append($('<div>').addClass('description ellipsis')
4042 .append($('<div>').addClass('text').html(subResource.summary))
4043 .append($('<div>').addClass('util')
4044 .append($('<div>').addClass('g-plusone')
4045 .attr('data-size', 'small')
4046 .attr('data-align', 'right')
4047 .attr('data-href', resource.url)))))
4048 .appendTo($ul);
4049 }
4050 }
4051
4052 // Add a more row
4053 if (max < section.sections.length) {
4054 $('<li>')
4055 .append($('<a>').attr('href', resource.url)
4056 .append($('<div>')
4057 .addClass('title')
4058 .text('More')))
4059 .appendTo($ul);
4060 }
4061 } else {
4062 // No sub-resources, just render description?
4063 }
4064
4065 return this;
4066 };
smain@google.com95948b82014-06-16 19:24:25 -07004067
4068
4069
4070
Robert Lye7eeb402014-06-03 19:35:24 -07004071 /* Render other types of resource styles that are not cards. */
4072 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07004073 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004074 'assets/images/resource-card-default-android.jpg';
4075 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004076
Robert Lye7eeb402014-06-03 19:35:24 -07004077 if (imgUrl.indexOf('//') === -1) {
4078 imgUrl = toRoot + imgUrl;
4079 }
smain@google.com95948b82014-06-16 19:24:25 -07004080
Robert Lye7eeb402014-06-03 19:35:24 -07004081 if (linkUrl && linkUrl.indexOf('//') === -1) {
4082 linkUrl = toRoot + linkUrl;
4083 }
4084
4085 $(this).append(
4086 $('<div>').addClass('image')
4087 .css('background-image', 'url(' + imgUrl + ')'),
4088 $('<div>').addClass('info').append(
4089 $('<h4>').addClass('title').html(resource.title),
4090 $('<p>').addClass('summary').html(resource.summary),
4091 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4092 )
4093 );
4094
4095 return this;
4096 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004097})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004098
4099
Dirk Doughertyc3921652014-05-13 16:55:26 -07004100/* Calculate the vertical area remaining */
4101(function($) {
4102 $.fn.ellipsisfade= function(lineHeight) {
4103 this.each(function() {
4104 // get element text
4105 var $this = $(this);
4106 var remainingHeight = $this.parent().parent().height();
4107 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004108 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004109 if ($(this).is(":visible")) {
4110 var h = $(this).height();
4111 remainingHeight = remainingHeight - h;
4112 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004113 });
4114
4115 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4116 $this.parent().css({'height': adjustedRemainingHeight});
4117 $this.css({'height': "auto"});
4118 });
4119
4120 return this;
4121 };
4122}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004123
4124/*
4125 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004126
Robert Lye7eeb402014-06-03 19:35:24 -07004127 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004128 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004129 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004130
Robert Lye7eeb402014-06-03 19:35:24 -07004131 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004132
Robert Lye7eeb402014-06-03 19:35:24 -07004133 <div class="fullscreen-carousel">
4134 <div class="fullscreen-carousel-content">
4135 <!-- content here -->
4136 </div>
4137 <div class="fullscreen-carousel-content">
4138 <!-- content here -->
4139 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004140
Robert Lye7eeb402014-06-03 19:35:24 -07004141 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004142
Robert Lye7eeb402014-06-03 19:35:24 -07004143 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004144
Robert Lye7eeb402014-06-03 19:35:24 -07004145 Control over how the carousel takes over the screen can mostly be defined in
4146 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004147 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004148 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004149 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004150 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004151
Robert Lye7eeb402014-06-03 19:35:24 -07004152 There is limited functionality for having multiple sections since that request
4153 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4154 scroll between multiple content areas.
4155*/
4156
4157(function() {
4158 $(document).ready(function() {
4159 $('.fullscreen-carousel').each(function() {
4160 initWidget(this);
4161 });
4162 });
4163
4164 function initWidget(widget) {
4165 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004166
Robert Lye7eeb402014-06-03 19:35:24 -07004167 var topOffset = $widget.offset().top;
4168 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4169 var maxHeight = 0;
4170 var minHeight = 0;
4171 var $content = $widget.find('.fullscreen-carousel-content');
4172 var $nextArrow = $widget.find('.next-arrow');
4173 var $prevArrow = $widget.find('.prev-arrow');
4174 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004175
Robert Lye7eeb402014-06-03 19:35:24 -07004176 if ($content.length <= 1) {
4177 $nextArrow.hide();
4178 $prevArrow.hide();
4179 } else {
4180 $nextArrow.click(function() {
4181 var index = ($content.index($curSection) + 1);
4182 $curSection.hide();
4183 $curSection = $($content[index >= $content.length ? 0 : index]);
4184 $curSection.show();
4185 });
smain@google.com95948b82014-06-16 19:24:25 -07004186
Robert Lye7eeb402014-06-03 19:35:24 -07004187 $prevArrow.click(function() {
4188 var index = ($content.index($curSection) - 1);
4189 $curSection.hide();
4190 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4191 $curSection.show();
4192 });
4193 }
4194
4195 // Just hide all content sections except first.
4196 $content.each(function(index) {
4197 if ($(this).height() > minHeight) minHeight = $(this).height();
4198 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4199 });
4200
4201 // Register for changes to window size, and trigger.
4202 $(window).resize(resizeWidget);
4203 resizeWidget();
4204
4205 function resizeWidget() {
4206 var height = $(window).height() - topOffset - padBottom;
4207 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004208 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004209 (maxHeight && height > maxHeight ? maxHeight : height));
4210 }
smain@google.com95948b82014-06-16 19:24:25 -07004211 }
Robert Lye7eeb402014-06-03 19:35:24 -07004212})();
4213
4214
4215
4216
4217
4218/*
4219 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004220
Robert Lye7eeb402014-06-03 19:35:24 -07004221 The following allows tab widgets to be installed via the html below. Each
4222 tab content section should have a data-tab attribute matching one of the
4223 nav items'. Also each tab content section should have a width matching the
4224 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004225
Robert Lye7eeb402014-06-03 19:35:24 -07004226 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004227
Robert Lye7eeb402014-06-03 19:35:24 -07004228 <div class="tab-carousel">
4229 <ul class="tab-nav">
4230 <li><a href="#" data-tab="handsets">Handsets</a>
4231 <li><a href="#" data-tab="wearable">Wearable</a>
4232 <li><a href="#" data-tab="tv">TV</a>
4233 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004234
Robert Lye7eeb402014-06-03 19:35:24 -07004235 <div class="tab-carousel-content">
4236 <div data-tab="handsets">
4237 <!--Full width content here-->
4238 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004239
Robert Lye7eeb402014-06-03 19:35:24 -07004240 <div data-tab="wearable">
4241 <!--Full width content here-->
4242 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004243
Robert Lye7eeb402014-06-03 19:35:24 -07004244 <div data-tab="tv">
4245 <!--Full width content here-->
4246 </div>
4247 </div>
4248 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004249
Robert Lye7eeb402014-06-03 19:35:24 -07004250*/
4251(function() {
4252 $(document).ready(function() {
4253 $('.tab-carousel').each(function() {
4254 initWidget(this);
4255 });
4256 });
4257
4258 function initWidget(widget) {
4259 var $widget = $(widget);
4260 var $nav = $widget.find('.tab-nav');
4261 var $anchors = $nav.find('[data-tab]');
4262 var $li = $nav.find('li');
4263 var $contentContainer = $widget.find('.tab-carousel-content');
4264 var $tabs = $contentContainer.find('[data-tab]');
4265 var $curTab = $($tabs[0]); // Current tab is first tab.
4266 var width = $widget.width();
4267
4268 // Setup nav interactivity.
4269 $anchors.click(function(evt) {
4270 evt.preventDefault();
4271 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004272 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004273 });
smain@google.com95948b82014-06-16 19:24:25 -07004274
Robert Lye7eeb402014-06-03 19:35:24 -07004275 // Add highlight for navigation on first item.
4276 var $highlight = $('<div>').addClass('highlight')
4277 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4278 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004279
Robert Lye7eeb402014-06-03 19:35:24 -07004280 // Store height since we will change contents to absolute.
4281 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004282
Robert Lye7eeb402014-06-03 19:35:24 -07004283 // Absolutely position tabs so they're ready for transition.
4284 $tabs.each(function(index) {
4285 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4286 });
smain@google.com95948b82014-06-16 19:24:25 -07004287
Robert Lye7eeb402014-06-03 19:35:24 -07004288 function transitionWidget($toTab) {
4289 if (!$curTab.is($toTab)) {
4290 var curIndex = $tabs.index($curTab[0]);
4291 var toIndex = $tabs.index($toTab[0]);
4292 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004293
Robert Lye7eeb402014-06-03 19:35:24 -07004294 // Animate content sections.
4295 $toTab.css({left:(width * dir) + 'px'});
4296 $curTab.animate({left:(width * -dir) + 'px'});
4297 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004298
Robert Lye7eeb402014-06-03 19:35:24 -07004299 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004300 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004301 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004302
Robert Lye7eeb402014-06-03 19:35:24 -07004303 // Store new current section.
4304 $curTab = $toTab;
4305 }
4306 }
smain@google.com95948b82014-06-16 19:24:25 -07004307 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08004308})();