blob: 9a081bf00e4816f77556d5534b9ba92981f53f91 [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();
620}
621
622function closeVideo() {
623 try {
smain@google.comf75ee212014-11-24 09:42:59 -0800624 youTubePlayer.pauseVideo();
smain@google.com698fff02014-11-20 20:39:33 -0800625 $("#video-container").fadeOut(200);
626 } catch(e) {
627 console.log('Video not available');
628 $("#video-container").fadeOut(200);
629 }
630}
631
smain@google.comf75ee212014-11-24 09:42:59 -0800632/* Track youtube playback for analytics */
633function onPlayerStateChange(event) {
634 // Video starts, send the video ID
635 if (event.data == YT.PlayerState.PLAYING) {
636 ga('send', 'event', 'Videos', 'Play', youTubePlayer.getVideoUrl().split('?v=')[1]);
637 }
638 // Video paused, send video ID and video elapsed time
639 if (event.data == YT.PlayerState.PAUSED) {
640 ga('send', 'event', 'Videos', 'Paused', youTubePlayer.getVideoUrl().split('?v=')[1], youTubePlayer.getCurrentTime());
641 }
642 // Video finished, send video ID and video elapsed time
643 if (event.data == YT.PlayerState.ENDED) {
644 ga('send', 'event', 'Videos', 'Finished', youTubePlayer.getVideoUrl().split('?v=')[1], youTubePlayer.getCurrentTime());
645 }
646}
647
smain@google.com698fff02014-11-20 20:39:33 -0800648
649
Scott Mainad08f072013-08-20 16:49:57 -0700650function initExpandableNavItems(rootTag) {
651 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
652 var section = $(this).closest('li.nav-section');
653 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700654 /* hide me and descendants */
655 section.find('ul').slideUp(250, function() {
656 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700657 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700658 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700659 resizeNav();
660 });
661 } else {
662 /* show me */
663 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700664 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700665 $others.removeClass('expanded').children('ul').slideUp(250);
666
667 // now expand me
668 section.closest('li').addClass('expanded');
669 section.children('ul').slideDown(250, function() {
670 resizeNav();
671 });
672 }
673 });
Scott Mainf0093852013-08-22 11:37:11 -0700674
675 // Stop expand/collapse behavior when clicking on nav section links
676 // (since we're navigating away from the page)
677 // This selector captures the first instance of <a>, but not those with "#" as the href.
678 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
679 window.location.href = $(this).attr('href');
680 return false;
681 });
Scott Mainad08f072013-08-20 16:49:57 -0700682}
683
Dirk Doughertyc3921652014-05-13 16:55:26 -0700684
685/** Create the list of breadcrumb links in the sticky header */
686function buildBreadcrumbs() {
687 var $breadcrumbUl = $("#sticky-header ul.breadcrumb");
688 // Add the secondary horizontal nav item, if provided
689 var $selectedSecondNav = $("div#nav-x ul.nav-x a.selected").clone().removeClass("selected");
690 if ($selectedSecondNav.length) {
691 $breadcrumbUl.prepend($("<li>").append($selectedSecondNav))
692 }
693 // Add the primary horizontal nav
694 var $selectedFirstNav = $("div#header-wrap ul.nav-x a.selected").clone().removeClass("selected");
695 // If there's no header nav item, use the logo link and title from alt text
696 if ($selectedFirstNav.length < 1) {
697 $selectedFirstNav = $("<a>")
698 .attr('href', $("div#header .logo a").attr('href'))
699 .text($("div#header .logo img").attr('alt'));
700 }
701 $breadcrumbUl.prepend($("<li>").append($selectedFirstNav));
702}
703
704
705
Scott Maine624b3f2013-09-12 12:56:41 -0700706/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700707function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700708 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
709 if ($("ul#nav li.selected").length) {
710 unHighlightSidenav();
711 }
712 // look for URL in sidenav, including the hash
713 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
714
715 // If the selNavLink is still empty, look for it without the hash
716 if ($selNavLink.length == 0) {
717 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
718 }
719
Scott Mainf6145542013-04-01 16:38:11 -0700720 var $selListItem;
721 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700722 // Find this page's <li> in sidenav and set selected
723 $selListItem = $selNavLink.closest('li');
724 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700725
Scott Mainf6145542013-04-01 16:38:11 -0700726 // Traverse up the tree and expand all parent nav-sections
727 $selNavLink.parents('li.nav-section').each(function() {
728 $(this).addClass('expanded');
729 $(this).children('ul').show();
730 });
731 }
732}
733
Scott Maine624b3f2013-09-12 12:56:41 -0700734function unHighlightSidenav() {
735 $("ul#nav li.selected").removeClass("selected");
736 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
737}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700738
739function toggleFullscreen(enable) {
740 var delay = 20;
741 var enabled = true;
742 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
743 if (enable) {
744 // Currently NOT USING fullscreen; enable fullscreen
745 stylesheet.removeAttr('disabled');
746 $('#nav-swap .fullscreen').removeClass('disabled');
747 $('#devdoc-nav').css({left:''});
748 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
749 enabled = true;
750 } else {
751 // Currently USING fullscreen; disable fullscreen
752 stylesheet.attr('disabled', 'disabled');
753 $('#nav-swap .fullscreen').addClass('disabled');
754 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
755 enabled = false;
756 }
smain@google.com6bdcb982014-11-14 11:53:07 -0800757 writeCookie("fullscreen", enabled, null);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700758 setNavBarLeftPos();
759 resizeNav(delay);
760 updateSideNavPosition();
761 setTimeout(initSidenavHeightResize,delay);
762}
763
764
765function setNavBarLeftPos() {
766 navBarLeftPos = $('#body-content').offset().left;
767}
768
769
770function updateSideNavPosition() {
771 var newLeft = $(window).scrollLeft() - navBarLeftPos;
772 $('#devdoc-nav').css({left: -newLeft});
773 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
774}
Scott Main3b90aff2013-08-01 18:09:35 -0700775
Scott Maine4d8f1b2012-06-21 18:03:05 -0700776// TODO: use $(document).ready instead
777function addLoadEvent(newfun) {
778 var current = window.onload;
779 if (typeof window.onload != 'function') {
780 window.onload = newfun;
781 } else {
782 window.onload = function() {
783 current();
784 newfun();
785 }
786 }
787}
788
789var agent = navigator['userAgent'].toLowerCase();
790// If a mobile phone, set flag and do mobile setup
791if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
792 (agent.indexOf("blackberry") != -1) ||
793 (agent.indexOf("webos") != -1) ||
794 (agent.indexOf("mini") != -1)) { // opera mini browsers
795 isMobile = true;
796}
797
798
Scott Main498d7102013-08-21 15:47:38 -0700799$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700800 $("pre:not(.no-pretty-print)").addClass("prettyprint");
801 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700802});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700803
Scott Maine4d8f1b2012-06-21 18:03:05 -0700804
805
806
807/* ######### RESIZE THE SIDENAV HEIGHT ########## */
808
809function resizeNav(delay) {
810 var $nav = $("#devdoc-nav");
811 var $window = $(window);
812 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700813
Scott Maine4d8f1b2012-06-21 18:03:05 -0700814 // Get the height of entire window and the total header height.
815 // Then figure out based on scroll position whether the header is visible
816 var windowHeight = $window.height();
817 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700818 var headerHeight = $('#header-wrapper').outerHeight();
819 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700820
821 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700822 // Could be either margin or top position, depending on whether the nav is fixed.
Scott Main3b90aff2013-08-01 18:09:35 -0700823 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700824 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700825
Scott Maine4d8f1b2012-06-21 18:03:05 -0700826 // Depending on whether the header is visible, set the side nav's height.
827 if (headerVisible) {
828 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700829 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700830 } else {
831 // Once header is off screen, the nav height is almost full window height
832 navHeight = windowHeight - topMargin;
833 }
Scott Main3b90aff2013-08-01 18:09:35 -0700834
835
836
Scott Maine4d8f1b2012-06-21 18:03:05 -0700837 $scrollPanes = $(".scroll-pane");
838 if ($scrollPanes.length > 1) {
839 // subtract the height of the api level widget and nav swapper from the available nav height
840 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700841
Scott Maine4d8f1b2012-06-21 18:03:05 -0700842 $("#swapper").css({height:navHeight + "px"});
843 if ($("#nav-tree").is(":visible")) {
844 $("#nav-tree").css({height:navHeight});
845 }
Scott Main3b90aff2013-08-01 18:09:35 -0700846
847 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700848 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700849
850 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700851 // then the package panel should begin to shrink
852 if (parseInt(classesHeight) <= 0) {
853 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
854 $("#packages-nav").css({height:navHeight - 10});
855 }
Scott Main3b90aff2013-08-01 18:09:35 -0700856
Scott Maine4d8f1b2012-06-21 18:03:05 -0700857 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
858 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700859
860
Scott Maine4d8f1b2012-06-21 18:03:05 -0700861 } else {
862 $nav.height(navHeight);
863 }
Scott Main3b90aff2013-08-01 18:09:35 -0700864
Scott Maine4d8f1b2012-06-21 18:03:05 -0700865 if (delay) {
866 updateFromResize = true;
867 delayedReInitScrollbars(delay);
868 } else {
869 reInitScrollbars();
870 }
Scott Main3b90aff2013-08-01 18:09:35 -0700871
Scott Maine4d8f1b2012-06-21 18:03:05 -0700872}
873
874var updateScrollbars = false;
875var updateFromResize = false;
876
877/* Re-initialize the scrollbars to account for changed nav size.
878 * This method postpones the actual update by a 1/4 second in order to optimize the
879 * scroll performance while the header is still visible, because re-initializing the
880 * scroll panes is an intensive process.
881 */
882function delayedReInitScrollbars(delay) {
883 // If we're scheduled for an update, but have received another resize request
884 // before the scheduled resize has occured, just ignore the new request
885 // (and wait for the scheduled one).
886 if (updateScrollbars && updateFromResize) {
887 updateFromResize = false;
888 return;
889 }
Scott Main3b90aff2013-08-01 18:09:35 -0700890
Scott Maine4d8f1b2012-06-21 18:03:05 -0700891 // We're scheduled for an update and the update request came from this method's setTimeout
892 if (updateScrollbars && !updateFromResize) {
893 reInitScrollbars();
894 updateScrollbars = false;
895 } else {
896 updateScrollbars = true;
897 updateFromResize = false;
898 setTimeout('delayedReInitScrollbars()',delay);
899 }
900}
901
902/* Re-initialize the scrollbars to account for changed nav size. */
903function reInitScrollbars() {
904 var pane = $(".scroll-pane").each(function(){
905 var api = $(this).data('jsp');
906 if (!api) { setTimeout(reInitScrollbars,300); return;}
907 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700908 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700909 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
910}
911
912
913/* Resize the height of the nav panels in the reference,
914 * and save the new size to a cookie */
915function saveNavPanels() {
916 var basePath = getBaseUri(location.pathname);
917 var section = basePath.substring(1,basePath.indexOf("/",1));
smain@google.com6bdcb982014-11-14 11:53:07 -0800918 writeCookie("height", resizePackagesNav.css("height"), section);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700919}
920
921
922
923function restoreHeight(packageHeight) {
924 $("#resize-packages-nav").height(packageHeight);
925 $("#packages-nav").height(packageHeight);
926 // var classesHeight = navHeight - packageHeight;
927 // $("#classes-nav").css({height:classesHeight});
928 // $("#classes-nav .jspContainer").css({height:classesHeight});
929}
930
931
932
933/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
934
935
936
937
938
Scott Main3b90aff2013-08-01 18:09:35 -0700939/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700940 This is called when the page finished loading. */
941function scrollIntoView(nav) {
942 var $nav = $("#"+nav);
943 var element = $nav.jScrollPane({/* ...settings... */});
944 var api = element.data('jsp');
945
946 if ($nav.is(':visible')) {
947 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700948 if ($selected.length == 0) {
949 // If no selected item found, exit
950 return;
951 }
Scott Main52dd2062013-08-15 12:22:28 -0700952 // get the selected item's offset from its container nav by measuring the item's offset
953 // relative to the document then subtract the container nav's offset relative to the document
954 var selectedOffset = $selected.offset().top - $nav.offset().top;
955 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
956 // if it's more than 80% down the nav
957 // scroll the item up by an amount equal to 80% the container nav's height
958 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700959 }
960 }
961}
962
963
964
965
966
967
968/* Show popup dialogs */
969function showDialog(id) {
970 $dialog = $("#"+id);
971 $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>');
972 $dialog.wrapInner('<div/>');
973 $dialog.removeClass("hide");
974}
975
976
977
978
979
980/* ######### COOKIES! ########## */
981
982function readCookie(cookie) {
983 var myCookie = cookie_namespace+"_"+cookie+"=";
984 if (document.cookie) {
985 var index = document.cookie.indexOf(myCookie);
986 if (index != -1) {
987 var valStart = index + myCookie.length;
988 var valEnd = document.cookie.indexOf(";", valStart);
989 if (valEnd == -1) {
990 valEnd = document.cookie.length;
991 }
992 var val = document.cookie.substring(valStart, valEnd);
993 return val;
994 }
995 }
996 return 0;
997}
998
smain@google.com6bdcb982014-11-14 11:53:07 -0800999function writeCookie(cookie, val, section) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001000 if (val==undefined) return;
1001 section = section == null ? "_" : "_"+section+"_";
smain@google.com6bdcb982014-11-14 11:53:07 -08001002 var age = 2*365*24*60*60; // set max-age to 2 years
Scott Main3b90aff2013-08-01 18:09:35 -07001003 var cookieValue = cookie_namespace + section + cookie + "=" + val
smain@google.com80e38f42014-11-03 10:47:12 -08001004 + "; max-age=" + age +"; path=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -07001005 document.cookie = cookieValue;
1006}
1007
1008/* ######### END COOKIES! ########## */
1009
1010
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001011var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -07001012var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001013var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -07001014/* Sets the vertical scoll position at which the sticky bar should appear.
1015 This method is called to reset the position when search results appear or hide */
1016function setStickyTop() {
1017 stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight();
1018}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001019
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001020/*
Scott Mainb16376f2014-05-21 20:35:47 -07001021 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -07001022 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001023$(window).scroll(function(event) {
1024
1025 setStickyTop();
1026 var hiding = false;
1027 var $stickyEl = $('#sticky-header');
1028 var $menuEl = $('.menu-container');
1029 // Exit if there's no sidenav
1030 if ($('#side-nav').length == 0) return;
1031 // Exit if the mouse target is a DIV, because that means the event is coming
1032 // from a scrollable div and so there's no need to make adjustments to our layout
1033 if ($(event.target).nodeName == "DIV") {
1034 return;
1035 }
1036
1037 var top = $(window).scrollTop();
1038 // we set the navbar fixed when the scroll position is beyond the height of the site header...
1039 var shouldBeSticky = top >= stickyTop;
1040 // ... except if the document content is shorter than the sidenav height.
1041 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1042 if ($("#doc-col").height() < $("#side-nav").height()) {
1043 shouldBeSticky = false;
1044 }
Scott Mainf5257812014-05-22 17:26:38 -07001045 // Account for horizontal scroll
1046 var scrollLeft = $(window).scrollLeft();
1047 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1048 if (sticky && (scrollLeft != prevScrollLeft)) {
1049 updateSideNavPosition();
1050 prevScrollLeft = scrollLeft;
1051 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001052
1053 // Don't continue if the header is sufficently far away
1054 // (to avoid intensive resizing that slows scrolling)
1055 if (sticky == shouldBeSticky) {
1056 return;
1057 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001058
1059 // If sticky header visible and position is now near top, hide sticky
1060 if (sticky && !shouldBeSticky) {
1061 sticky = false;
1062 hiding = true;
1063 // make the sidenav static again
1064 $('#devdoc-nav')
1065 .removeClass('fixed')
1066 .css({'width':'auto','margin':''})
1067 .prependTo('#side-nav');
1068 // delay hide the sticky
1069 $menuEl.removeClass('sticky-menu');
1070 $stickyEl.fadeOut(250);
1071 hiding = false;
1072
1073 // update the sidenaav position for side scrolling
1074 updateSideNavPosition();
1075 } else if (!sticky && shouldBeSticky) {
1076 sticky = true;
1077 $stickyEl.fadeIn(10);
1078 $menuEl.addClass('sticky-menu');
1079
1080 // make the sidenav fixed
1081 var width = $('#devdoc-nav').width();
1082 $('#devdoc-nav')
1083 .addClass('fixed')
1084 .css({'width':width+'px'})
1085 .prependTo('#body-content');
1086
1087 // update the sidenaav position for side scrolling
1088 updateSideNavPosition();
1089
1090 } else if (hiding && top < 15) {
1091 $menuEl.removeClass('sticky-menu');
1092 $stickyEl.hide();
1093 hiding = false;
1094 }
1095 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
1096});
1097
1098/*
1099 * Manages secion card states and nav resize to conclude loading
1100 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001101(function() {
1102 $(document).ready(function() {
1103
Dirk Doughertyc3921652014-05-13 16:55:26 -07001104 // Stack hover states
1105 $('.section-card-menu').each(function(index, el) {
1106 var height = $(el).height();
1107 $(el).css({height:height+'px', position:'relative'});
1108 var $cardInfo = $(el).find('.card-info');
1109
1110 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1111 });
1112
Dirk Doughertyc3921652014-05-13 16:55:26 -07001113 });
1114
1115})();
1116
Scott Maine4d8f1b2012-06-21 18:03:05 -07001117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
Scott Maind7026f72013-06-17 15:08:49 -07001130/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001131
1132
1133
1134
1135
1136function toggle(obj, slide) {
1137 var ul = $("ul:first", obj);
1138 var li = ul.parent();
1139 if (li.hasClass("closed")) {
1140 if (slide) {
1141 ul.slideDown("fast");
1142 } else {
1143 ul.show();
1144 }
1145 li.removeClass("closed");
1146 li.addClass("open");
1147 $(".toggle-img", li).attr("title", "hide pages");
1148 } else {
1149 ul.slideUp("fast");
1150 li.removeClass("open");
1151 li.addClass("closed");
1152 $(".toggle-img", li).attr("title", "show pages");
1153 }
1154}
1155
1156
Scott Maine4d8f1b2012-06-21 18:03:05 -07001157function buildToggleLists() {
1158 $(".toggle-list").each(
1159 function(i) {
1160 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1161 $(this).addClass("closed");
1162 });
1163}
1164
1165
1166
Scott Maind7026f72013-06-17 15:08:49 -07001167function hideNestedItems(list, toggle) {
1168 $list = $(list);
1169 // hide nested lists
1170 if($list.hasClass('showing')) {
1171 $("li ol", $list).hide('fast');
1172 $list.removeClass('showing');
1173 // show nested lists
1174 } else {
1175 $("li ol", $list).show('fast');
1176 $list.addClass('showing');
1177 }
1178 $(".more,.less",$(toggle)).toggle();
1179}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001180
1181
smain@google.com95948b82014-06-16 19:24:25 -07001182/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1183function setupIdeDocToggle() {
1184 $( "select.ide" ).change(function() {
1185 var selected = $(this).find("option:selected").attr("value");
1186 $(".select-ide").hide();
1187 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001188
smain@google.com95948b82014-06-16 19:24:25 -07001189 $("select.ide").val(selected);
1190 });
1191}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216/* REFERENCE NAV SWAP */
1217
1218
1219function getNavPref() {
1220 var v = readCookie('reference_nav');
1221 if (v != NAV_PREF_TREE) {
1222 v = NAV_PREF_PANELS;
1223 }
1224 return v;
1225}
1226
1227function chooseDefaultNav() {
1228 nav_pref = getNavPref();
1229 if (nav_pref == NAV_PREF_TREE) {
1230 $("#nav-panels").toggle();
1231 $("#panel-link").toggle();
1232 $("#nav-tree").toggle();
1233 $("#tree-link").toggle();
1234 }
1235}
1236
1237function swapNav() {
1238 if (nav_pref == NAV_PREF_TREE) {
1239 nav_pref = NAV_PREF_PANELS;
1240 } else {
1241 nav_pref = NAV_PREF_TREE;
1242 init_default_navtree(toRoot);
1243 }
smain@google.com6bdcb982014-11-14 11:53:07 -08001244 writeCookie("nav", nav_pref, "reference");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001245
1246 $("#nav-panels").toggle();
1247 $("#panel-link").toggle();
1248 $("#nav-tree").toggle();
1249 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001250
Scott Maine4d8f1b2012-06-21 18:03:05 -07001251 resizeNav();
1252
1253 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1254 $("#nav-tree .jspContainer:visible")
1255 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1256 // Another nasty hack to make the scrollbar appear now that we have height
1257 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001258
Scott Maine4d8f1b2012-06-21 18:03:05 -07001259 if ($("#nav-tree").is(':visible')) {
1260 scrollIntoView("nav-tree");
1261 } else {
1262 scrollIntoView("packages-nav");
1263 scrollIntoView("classes-nav");
1264 }
1265}
1266
1267
1268
Scott Mainf5089842012-08-14 16:31:07 -07001269/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001270/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001271/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001272
1273function getBaseUri(uri) {
1274 var intlUrl = (uri.substring(0,6) == "/intl/");
1275 if (intlUrl) {
1276 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1277 base = base.substring(base.indexOf('/')+1, base.length);
1278 //alert("intl, returning base url: /" + base);
1279 return ("/" + base);
1280 } else {
1281 //alert("not intl, returning uri as found.");
1282 return uri;
1283 }
1284}
1285
1286function requestAppendHL(uri) {
1287//append "?hl=<lang> to an outgoing request (such as to blog)
1288 var lang = getLangPref();
1289 if (lang) {
1290 var q = 'hl=' + lang;
1291 uri += '?' + q;
1292 window.location = uri;
1293 return false;
1294 } else {
1295 return true;
1296 }
1297}
1298
1299
Scott Maine4d8f1b2012-06-21 18:03:05 -07001300function changeNavLang(lang) {
Scott Main6eb95f12012-10-02 17:12:23 -07001301 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1302 $links.each(function(i){ // for each link with a translation
1303 var $link = $(this);
1304 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1305 // put the desired language from the attribute as the text
1306 $link.text($link.attr(lang+"-lang"))
Scott Maine4d8f1b2012-06-21 18:03:05 -07001307 }
Scott Main6eb95f12012-10-02 17:12:23 -07001308 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001309}
1310
Scott Main015d6162013-01-29 09:01:52 -08001311function changeLangPref(lang, submit) {
smain@google.com6bdcb982014-11-14 11:53:07 -08001312 writeCookie("pref_lang", lang, null);
Scott Main015d6162013-01-29 09:01:52 -08001313
1314 // ####### TODO: Remove this condition once we're stable on devsite #######
1315 // This condition is only needed if we still need to support legacy GAE server
1316 if (devsite) {
1317 // Switch language when on Devsite server
1318 if (submit) {
1319 $("#setlang").submit();
1320 }
1321 } else {
1322 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001323 if (submit) {
1324 window.location = getBaseUri(location.pathname);
1325 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001326 }
1327}
1328
1329function loadLangPref() {
1330 var lang = readCookie("pref_lang");
1331 if (lang != 0) {
1332 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1333 }
1334}
1335
1336function getLangPref() {
1337 var lang = $("#language").find(":selected").attr("value");
1338 if (!lang) {
1339 lang = readCookie("pref_lang");
1340 }
1341 return (lang != 0) ? lang : 'en';
1342}
1343
1344/* ########## END LOCALIZATION ############ */
1345
1346
1347
1348
1349
1350
1351/* Used to hide and reveal supplemental content, such as long code samples.
1352 See the companion CSS in android-developer-docs.css */
1353function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001354 var div = $(obj).closest(".toggle-content");
1355 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001356 if (div.hasClass("closed")) { // if it's closed, open it
1357 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001358 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001359 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001360 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001361 + "assets/images/triangle-opened.png");
1362 } else { // if it's open, close it
1363 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
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("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001366 div.find(".toggle-content").removeClass("open").addClass("closed")
1367 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001368 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001369 + "assets/images/triangle-closed.png");
1370 });
1371 }
1372 return false;
1373}
Scott Mainf5089842012-08-14 16:31:07 -07001374
1375
Scott Maindb3678b2012-10-23 14:13:41 -07001376/* New version of expandable content */
1377function toggleExpandable(link,id) {
1378 if($(id).is(':visible')) {
1379 $(id).slideUp();
1380 $(link).removeClass('expanded');
1381 } else {
1382 $(id).slideDown();
1383 $(link).addClass('expanded');
1384 }
1385}
1386
1387function hideExpandable(ids) {
1388 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001389 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001390}
1391
Scott Mainf5089842012-08-14 16:31:07 -07001392
1393
1394
1395
Scott Main3b90aff2013-08-01 18:09:35 -07001396/*
Scott Mainf5089842012-08-14 16:31:07 -07001397 * Slideshow 1.0
1398 * Used on /index.html and /develop/index.html for carousel
1399 *
1400 * Sample usage:
1401 * HTML -
1402 * <div class="slideshow-container">
1403 * <a href="" class="slideshow-prev">Prev</a>
1404 * <a href="" class="slideshow-next">Next</a>
1405 * <ul>
1406 * <li class="item"><img src="images/marquee1.jpg"></li>
1407 * <li class="item"><img src="images/marquee2.jpg"></li>
1408 * <li class="item"><img src="images/marquee3.jpg"></li>
1409 * <li class="item"><img src="images/marquee4.jpg"></li>
1410 * </ul>
1411 * </div>
1412 *
1413 * <script type="text/javascript">
1414 * $('.slideshow-container').dacSlideshow({
1415 * auto: true,
1416 * btnPrev: '.slideshow-prev',
1417 * btnNext: '.slideshow-next'
1418 * });
1419 * </script>
1420 *
1421 * Options:
1422 * btnPrev: optional identifier for previous button
1423 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001424 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001425 * auto: whether or not to auto-proceed
1426 * speed: animation speed
1427 * autoTime: time between auto-rotation
1428 * easing: easing function for transition
1429 * start: item to select by default
1430 * scroll: direction to scroll in
1431 * pagination: whether or not to include dotted pagination
1432 *
1433 */
1434
1435 (function($) {
1436 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001437
Scott Mainf5089842012-08-14 16:31:07 -07001438 //Options - see above
1439 o = $.extend({
1440 btnPrev: null,
1441 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001442 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001443 auto: true,
1444 speed: 500,
1445 autoTime: 12000,
1446 easing: null,
1447 start: 0,
1448 scroll: 1,
1449 pagination: true
1450
1451 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001452
1453 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001454 return this.each(function() {
1455
1456 var running = false;
1457 var animCss = o.vertical ? "top" : "left";
1458 var sizeCss = o.vertical ? "height" : "width";
1459 var div = $(this);
1460 var ul = $("ul", div);
1461 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001462 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001463 var timer = null;
1464
1465 var li = $("li", ul);
1466 var itemLength = li.size();
1467 var curr = o.start;
1468
1469 li.css({float: o.vertical ? "none" : "left"});
1470 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1471 div.css({position: "relative", "z-index": "2", left: "0px"});
1472
1473 var liSize = o.vertical ? height(li) : width(li);
1474 var ulSize = liSize * itemLength;
1475 var divSize = liSize;
1476
1477 li.css({width: li.width(), height: li.height()});
1478 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1479
1480 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001481
Scott Mainf5089842012-08-14 16:31:07 -07001482 //Pagination
1483 if (o.pagination) {
1484 var pagination = $("<div class='pagination'></div>");
1485 var pag_ul = $("<ul></ul>");
1486 if (tl > 1) {
1487 for (var i=0;i<tl;i++) {
1488 var li = $("<li>"+i+"</li>");
1489 pag_ul.append(li);
1490 if (i==o.start) li.addClass('active');
1491 li.click(function() {
1492 go(parseInt($(this).text()));
1493 })
1494 }
1495 pagination.append(pag_ul);
1496 div.append(pagination);
1497 }
1498 }
Scott Main3b90aff2013-08-01 18:09:35 -07001499
Scott Mainf5089842012-08-14 16:31:07 -07001500 //Previous button
1501 if(o.btnPrev)
1502 $(o.btnPrev).click(function(e) {
1503 e.preventDefault();
1504 return go(curr-o.scroll);
1505 });
1506
1507 //Next button
1508 if(o.btnNext)
1509 $(o.btnNext).click(function(e) {
1510 e.preventDefault();
1511 return go(curr+o.scroll);
1512 });
Scott Maineb410352013-01-14 19:03:40 -08001513
1514 //Pause button
1515 if(o.btnPause)
1516 $(o.btnPause).click(function(e) {
1517 e.preventDefault();
1518 if ($(this).hasClass('paused')) {
1519 startRotateTimer();
1520 } else {
1521 pauseRotateTimer();
1522 }
1523 });
Scott Main3b90aff2013-08-01 18:09:35 -07001524
Scott Mainf5089842012-08-14 16:31:07 -07001525 //Auto rotation
1526 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001527
Scott Mainf5089842012-08-14 16:31:07 -07001528 function startRotateTimer() {
1529 clearInterval(timer);
1530 timer = setInterval(function() {
1531 if (curr == tl-1) {
1532 go(0);
1533 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001534 go(curr+o.scroll);
1535 }
Scott Mainf5089842012-08-14 16:31:07 -07001536 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001537 $(o.btnPause).removeClass('paused');
1538 }
1539
1540 function pauseRotateTimer() {
1541 clearInterval(timer);
1542 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001543 }
1544
1545 //Go to an item
1546 function go(to) {
1547 if(!running) {
1548
1549 if(to<0) {
1550 to = itemLength-1;
1551 } else if (to>itemLength-1) {
1552 to = 0;
1553 }
1554 curr = to;
1555
1556 running = true;
1557
1558 ul.animate(
1559 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1560 function() {
1561 running = false;
1562 }
1563 );
1564
1565 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1566 $( (curr-o.scroll<0 && o.btnPrev)
1567 ||
1568 (curr+o.scroll > itemLength && o.btnNext)
1569 ||
1570 []
1571 ).addClass("disabled");
1572
Scott Main3b90aff2013-08-01 18:09:35 -07001573
Scott Mainf5089842012-08-14 16:31:07 -07001574 var nav_items = $('li', pagination);
1575 nav_items.removeClass('active');
1576 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001577
Scott Mainf5089842012-08-14 16:31:07 -07001578
1579 }
1580 if(o.auto) startRotateTimer();
1581 return false;
1582 };
1583 });
1584 };
1585
1586 function css(el, prop) {
1587 return parseInt($.css(el[0], prop)) || 0;
1588 };
1589 function width(el) {
1590 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1591 };
1592 function height(el) {
1593 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1594 };
1595
1596 })(jQuery);
1597
1598
Scott Main3b90aff2013-08-01 18:09:35 -07001599/*
Scott Mainf5089842012-08-14 16:31:07 -07001600 * dacSlideshow 1.0
1601 * Used on develop/index.html for side-sliding tabs
1602 *
1603 * Sample usage:
1604 * HTML -
1605 * <div class="slideshow-container">
1606 * <a href="" class="slideshow-prev">Prev</a>
1607 * <a href="" class="slideshow-next">Next</a>
1608 * <ul>
1609 * <li class="item"><img src="images/marquee1.jpg"></li>
1610 * <li class="item"><img src="images/marquee2.jpg"></li>
1611 * <li class="item"><img src="images/marquee3.jpg"></li>
1612 * <li class="item"><img src="images/marquee4.jpg"></li>
1613 * </ul>
1614 * </div>
1615 *
1616 * <script type="text/javascript">
1617 * $('.slideshow-container').dacSlideshow({
1618 * auto: true,
1619 * btnPrev: '.slideshow-prev',
1620 * btnNext: '.slideshow-next'
1621 * });
1622 * </script>
1623 *
1624 * Options:
1625 * btnPrev: optional identifier for previous button
1626 * btnNext: optional identifier for next button
1627 * auto: whether or not to auto-proceed
1628 * speed: animation speed
1629 * autoTime: time between auto-rotation
1630 * easing: easing function for transition
1631 * start: item to select by default
1632 * scroll: direction to scroll in
1633 * pagination: whether or not to include dotted pagination
1634 *
1635 */
1636 (function($) {
1637 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001638
Scott Mainf5089842012-08-14 16:31:07 -07001639 //Options - see above
1640 o = $.extend({
1641 speed : 250,
1642 easing: null,
1643 nav_id: null,
1644 frame_id: null
1645 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001646
1647 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001648 return this.each(function() {
1649
1650 var curr = 0;
1651 var running = false;
1652 var animCss = "margin-left";
1653 var sizeCss = "width";
1654 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001655
Scott Mainf5089842012-08-14 16:31:07 -07001656 var nav = $(o.nav_id, div);
1657 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001658 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001659 var frame = div.find(o.frame_id);
1660 var content_width = $(frame).find('ul').width();
1661 //Buttons
1662 $(nav_li).click(function(e) {
1663 go($(nav_li).index($(this)));
1664 })
Scott Main3b90aff2013-08-01 18:09:35 -07001665
Scott Mainf5089842012-08-14 16:31:07 -07001666 //Go to an item
1667 function go(to) {
1668 if(!running) {
1669 curr = to;
1670 running = true;
1671
1672 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1673 function() {
1674 running = false;
1675 }
1676 );
1677
Scott Main3b90aff2013-08-01 18:09:35 -07001678
Scott Mainf5089842012-08-14 16:31:07 -07001679 nav_li.removeClass('active');
1680 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001681
Scott Mainf5089842012-08-14 16:31:07 -07001682
1683 }
1684 return false;
1685 };
1686 });
1687 };
1688
1689 function css(el, prop) {
1690 return parseInt($.css(el[0], prop)) || 0;
1691 };
1692 function width(el) {
1693 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1694 };
1695 function height(el) {
1696 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1697 };
1698
1699 })(jQuery);
1700
1701
1702
1703
1704
1705/* ######################################################## */
1706/* ################ SEARCH SUGGESTIONS ################## */
1707/* ######################################################## */
1708
1709
Scott Main7e447ed2013-02-19 17:22:37 -08001710
Scott Main0e76e7e2013-03-12 10:24:07 -07001711var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1712var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1713
Scott Mainf5089842012-08-14 16:31:07 -07001714var gMatches = new Array();
1715var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001716var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001717var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1718var gListLength = 0;
1719
1720
1721var gGoogleMatches = new Array();
1722var ROW_COUNT_GOOGLE = 15; // max number of results in list
1723var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001724
Scott Main0e76e7e2013-03-12 10:24:07 -07001725var gDocsMatches = new Array();
1726var ROW_COUNT_DOCS = 100; // max number of results in list
1727var gDocsListLength = 0;
1728
Scott Mainde295272013-03-25 15:48:35 -07001729function onSuggestionClick(link) {
1730 // When user clicks a suggested document, track it
smain@google.com633f3222014-10-03 15:49:45 -07001731 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).text(),
1732 'from: ' + $("#search_autocomplete").val());
Scott Mainde295272013-03-25 15:48:35 -07001733}
1734
Scott Mainf5089842012-08-14 16:31:07 -07001735function set_item_selected($li, selected)
1736{
1737 if (selected) {
1738 $li.attr('class','jd-autocomplete jd-selected');
1739 } else {
1740 $li.attr('class','jd-autocomplete');
1741 }
1742}
1743
1744function set_item_values(toroot, $li, match)
1745{
1746 var $link = $('a',$li);
1747 $link.html(match.__hilabel || match.label);
1748 $link.attr('href',toroot + match.link);
1749}
1750
Scott Main719acb42013-12-05 16:05:09 -08001751function set_item_values_jd(toroot, $li, match)
1752{
1753 var $link = $('a',$li);
1754 $link.html(match.title);
1755 $link.attr('href',toroot + match.url);
1756}
1757
Scott Main0e76e7e2013-03-12 10:24:07 -07001758function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001759 var $li = $("<li class='jd-autocomplete'></li>");
1760 $list.append($li);
1761
1762 $li.mousedown(function() {
1763 window.location = this.firstChild.getAttribute("href");
1764 });
1765 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001766 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001767 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001768 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1769 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001770 });
Scott Mainde295272013-03-25 15:48:35 -07001771 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001772 $li.attr('class','show-item');
1773 return $li;
1774}
1775
Scott Mainf5089842012-08-14 16:31:07 -07001776function sync_selection_table(toroot)
1777{
Scott Mainf5089842012-08-14 16:31:07 -07001778 var $li; //list item jquery object
1779 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001780
Scott Main0e76e7e2013-03-12 10:24:07 -07001781 // if there are NO results at all, hide all columns
1782 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1783 $('.suggest-card').hide(300);
1784 return;
1785 }
1786
1787 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001788 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001789 // reveal suggestion list
1790 $('.suggest-card.dummy').show();
1791 $('.suggest-card.reference').show();
1792 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001793
Scott Main0e76e7e2013-03-12 10:24:07 -07001794 // reset the lists
1795 $(".search_filtered_wrapper.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001796
Scott Main0e76e7e2013-03-12 10:24:07 -07001797 // ########### ANDROID RESULTS #############
1798 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001799
Scott Main0e76e7e2013-03-12 10:24:07 -07001800 // determine android results to show
1801 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1802 gMatches.length : ROW_COUNT_FRAMEWORK;
1803 for (i=0; i<gListLength; i++) {
1804 var $li = new_suggestion($(".suggest-card.reference ul"));
1805 set_item_values(toroot, $li, gMatches[i]);
1806 set_item_selected($li, i == gSelectedIndex);
1807 }
1808 }
Scott Main7e447ed2013-02-19 17:22:37 -08001809
Scott Main0e76e7e2013-03-12 10:24:07 -07001810 // ########### GOOGLE RESULTS #############
1811 if (gGoogleMatches.length > 0) {
1812 // show header for list
1813 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001814
Scott Main0e76e7e2013-03-12 10:24:07 -07001815 // determine google results to show
1816 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1817 for (i=0; i<gGoogleListLength; i++) {
1818 var $li = new_suggestion($(".suggest-card.reference ul"));
1819 set_item_values(toroot, $li, gGoogleMatches[i]);
1820 set_item_selected($li, i == gSelectedIndex);
1821 }
1822 }
Scott Mainf5089842012-08-14 16:31:07 -07001823 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001824 $('.suggest-card.reference').hide();
1825 $('.suggest-card.dummy').hide();
1826 }
1827
1828 // ########### JD DOC RESULTS #############
1829 if (gDocsMatches.length > 0) {
1830 // reset the lists
1831 $(".search_filtered_wrapper.docs li").remove();
1832
1833 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001834 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1835 // The order must match the reverse order that each section appears as a card in
1836 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001837 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1838 for (i=0; i<gDocsListLength; i++) {
1839 var sugg = gDocsMatches[i];
1840 var $li;
1841 if (sugg.type == "design") {
1842 $li = new_suggestion($(".suggest-card.design ul"));
1843 } else
1844 if (sugg.type == "distribute") {
1845 $li = new_suggestion($(".suggest-card.distribute ul"));
1846 } else
Scott Main719acb42013-12-05 16:05:09 -08001847 if (sugg.type == "samples") {
1848 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1849 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001850 if (sugg.type == "training") {
1851 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1852 } else
Scott Main719acb42013-12-05 16:05:09 -08001853 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001854 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1855 } else {
1856 continue;
1857 }
1858
Scott Main719acb42013-12-05 16:05:09 -08001859 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001860 set_item_selected($li, i == gSelectedIndex);
1861 }
1862
1863 // add heading and show or hide card
1864 if ($(".suggest-card.design li").length > 0) {
1865 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1866 $(".suggest-card.design").show(300);
1867 } else {
1868 $('.suggest-card.design').hide(300);
1869 }
1870 if ($(".suggest-card.distribute li").length > 0) {
1871 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1872 $(".suggest-card.distribute").show(300);
1873 } else {
1874 $('.suggest-card.distribute').hide(300);
1875 }
1876 if ($(".child-card.guides li").length > 0) {
1877 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1878 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1879 }
1880 if ($(".child-card.training li").length > 0) {
1881 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1882 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1883 }
Scott Main719acb42013-12-05 16:05:09 -08001884 if ($(".child-card.samples li").length > 0) {
1885 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1886 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1887 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001888
1889 if ($(".suggest-card.develop li").length > 0) {
1890 $(".suggest-card.develop").show(300);
1891 } else {
1892 $('.suggest-card.develop').hide(300);
1893 }
1894
1895 } else {
1896 $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001897 }
1898}
1899
Scott Main0e76e7e2013-03-12 10:24:07 -07001900/** Called by the search input's onkeydown and onkeyup events.
1901 * Handles navigation with keyboard arrows, Enter key to invoke search,
1902 * otherwise invokes search suggestions on key-up event.
1903 * @param e The JS event
1904 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001905 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001906 * @returns True if the event should bubble up
1907 */
Scott Mainf5089842012-08-14 16:31:07 -07001908function search_changed(e, kd, toroot)
1909{
Scott Main719acb42013-12-05 16:05:09 -08001910 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001911 var search = document.getElementById("search_autocomplete");
1912 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001913 // get the ul hosting the currently selected item
1914 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1915 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1916 var $selectedUl = $columns[gSelectedColumn];
1917
Scott Mainf5089842012-08-14 16:31:07 -07001918 // show/hide the close button
1919 if (text != '') {
1920 $(".search .close").removeClass("hide");
1921 } else {
1922 $(".search .close").addClass("hide");
1923 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001924 // 27 = esc
1925 if (e.keyCode == 27) {
1926 // close all search results
1927 if (kd) $('.search .close').trigger('click');
1928 return true;
1929 }
Scott Mainf5089842012-08-14 16:31:07 -07001930 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001931 else if (e.keyCode == 13) {
1932 if (gSelectedIndex < 0) {
1933 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001934 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1935 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001936 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001937 return true;
1938 } else {
1939 // otherwise, results are already showing, so allow ajax to auto refresh the results
1940 // and ignore this Enter press to avoid the reload.
1941 return false;
1942 }
1943 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001944 // click the link corresponding to selected item
1945 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001946 return false;
1947 }
1948 }
Scott Mainb16376f2014-05-21 20:35:47 -07001949 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001950 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001951 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001952 if ((sticky ) && (search.value != "")) {
1953 $('body,html').animate({scrollTop:0}, '500', 'swing');
1954 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001955 return true;
1956 }
1957 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001958 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001959 // if the next item is a header, skip it
1960 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001961 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001962 }
1963 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001964 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001965 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001966 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1967 // If user reaches top, reset selected column
1968 if (gSelectedIndex < 0) {
1969 gSelectedColumn = -1;
1970 }
Scott Mainf5089842012-08-14 16:31:07 -07001971 }
1972 return false;
1973 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001974 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001975 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001976 // if the next item is a header, skip it
1977 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001978 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08001979 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001980 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
1981 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
1982 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001983 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07001984 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07001985 }
1986 return false;
1987 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001988 // Consider left/right arrow navigation
1989 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
1990 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
1991 // 37 LEFT ARROW
1992 // go left only if current column is not left-most column (last column)
1993 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
1994 $('li', $selectedUl).removeClass('jd-selected');
1995 gSelectedColumn++;
1996 $selectedUl = $columns[gSelectedColumn];
1997 // keep or reset the selected item to last item as appropriate
1998 gSelectedIndex = gSelectedIndex >
1999 $("li", $selectedUl).length-1 ?
2000 $("li", $selectedUl).length-1 : gSelectedIndex;
2001 // if the corresponding item is a header, move down
2002 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2003 gSelectedIndex++;
2004 }
Scott Main3b90aff2013-08-01 18:09:35 -07002005 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002006 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2007 return false;
2008 }
2009 // 39 RIGHT ARROW
2010 // go right only if current column is not the right-most column (first column)
2011 else if (e.keyCode == 39 && gSelectedColumn > 0) {
2012 $('li', $selectedUl).removeClass('jd-selected');
2013 gSelectedColumn--;
2014 $selectedUl = $columns[gSelectedColumn];
2015 // keep or reset the selected item to last item as appropriate
2016 gSelectedIndex = gSelectedIndex >
2017 $("li", $selectedUl).length-1 ?
2018 $("li", $selectedUl).length-1 : gSelectedIndex;
2019 // if the corresponding item is a header, move down
2020 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2021 gSelectedIndex++;
2022 }
Scott Main3b90aff2013-08-01 18:09:35 -07002023 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002024 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2025 return false;
2026 }
2027 }
2028
Scott Main719acb42013-12-05 16:05:09 -08002029 // if key-up event and not arrow down/up/left/right,
2030 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07002031 else if (!kd && (e.keyCode != 40)
2032 && (e.keyCode != 38)
2033 && (e.keyCode != 37)
2034 && (e.keyCode != 39)) {
2035 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07002036 gMatches = new Array();
2037 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002038 gGoogleMatches = new Array();
2039 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07002040 gDocsMatches = new Array();
2041 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002042
2043 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07002044 for (var i=0; i<DATA.length; i++) {
2045 var s = DATA[i];
2046 if (text.length != 0 &&
2047 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2048 gMatches[matchedCount] = s;
2049 matchedCount++;
2050 }
2051 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002052 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07002053 for (var i=0; i<gMatches.length; i++) {
2054 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08002055 }
2056
2057
2058 // Search for Google matches
2059 for (var i=0; i<GOOGLE_DATA.length; i++) {
2060 var s = GOOGLE_DATA[i];
2061 if (text.length != 0 &&
2062 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2063 gGoogleMatches[matchedCountGoogle] = s;
2064 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07002065 }
2066 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002067 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08002068 for (var i=0; i<gGoogleMatches.length; i++) {
2069 var s = gGoogleMatches[i];
2070 }
2071
Scott Mainf5089842012-08-14 16:31:07 -07002072 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07002073
2074
2075
Scott Main719acb42013-12-05 16:05:09 -08002076 // Search for matching JD docs
Dirk Dougherty9b7f8f22014-11-01 17:08:56 -07002077 if (text.length >= 2) {
Scott Main719acb42013-12-05 16:05:09 -08002078 // Regex to match only the beginning of a word
2079 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
2080
2081
2082 // Search for Training classes
2083 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002084 // current search comparison, with counters for tag and title,
2085 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08002086 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07002087 s.matched_tag = 0;
2088 s.matched_title = 0;
2089 var matched = false;
2090
2091 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08002092 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002093 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08002094 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002095 matched = true;
2096 s.matched_tag = j + 1; // add 1 to index position
2097 }
2098 }
Scott Main719acb42013-12-05 16:05:09 -08002099 // Don't consider doc title for lessons (only for class landing pages),
2100 // unless the lesson has a tag that already matches
2101 if ((s.lang == currentLang) &&
2102 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002103 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08002104 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002105 matched = true;
2106 s.matched_title = 1;
2107 }
2108 }
2109 if (matched) {
2110 gDocsMatches[matchedCountDocs] = s;
2111 matchedCountDocs++;
2112 }
2113 }
Scott Main719acb42013-12-05 16:05:09 -08002114
2115
2116 // Search for API Guides
2117 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2118 // current search comparison, with counters for tag and title,
2119 // used later to improve ranking
2120 var s = GUIDE_RESOURCES[i];
2121 s.matched_tag = 0;
2122 s.matched_title = 0;
2123 var matched = false;
2124
2125 // Check if query matches any tags; work backwards toward 1 to assist ranking
2126 for (var j = s.keywords.length - 1; j >= 0; j--) {
2127 // it matches a tag
2128 if (s.keywords[j].toLowerCase().match(textRegex)) {
2129 matched = true;
2130 s.matched_tag = j + 1; // add 1 to index position
2131 }
2132 }
2133 // Check if query matches the doc title, but only for current language
2134 if (s.lang == currentLang) {
2135 // if query matches the doc title
2136 if (s.title.toLowerCase().match(textRegex)) {
2137 matched = true;
2138 s.matched_title = 1;
2139 }
2140 }
2141 if (matched) {
2142 gDocsMatches[matchedCountDocs] = s;
2143 matchedCountDocs++;
2144 }
2145 }
2146
2147
2148 // Search for Tools Guides
2149 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2150 // current search comparison, with counters for tag and title,
2151 // used later to improve ranking
2152 var s = TOOLS_RESOURCES[i];
2153 s.matched_tag = 0;
2154 s.matched_title = 0;
2155 var matched = false;
2156
2157 // Check if query matches any tags; work backwards toward 1 to assist ranking
2158 for (var j = s.keywords.length - 1; j >= 0; j--) {
2159 // it matches a tag
2160 if (s.keywords[j].toLowerCase().match(textRegex)) {
2161 matched = true;
2162 s.matched_tag = j + 1; // add 1 to index position
2163 }
2164 }
2165 // Check if query matches the doc title, but only for current language
2166 if (s.lang == currentLang) {
2167 // if query matches the doc title
2168 if (s.title.toLowerCase().match(textRegex)) {
2169 matched = true;
2170 s.matched_title = 1;
2171 }
2172 }
2173 if (matched) {
2174 gDocsMatches[matchedCountDocs] = s;
2175 matchedCountDocs++;
2176 }
2177 }
2178
2179
2180 // Search for About docs
2181 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2182 // current search comparison, with counters for tag and title,
2183 // used later to improve ranking
2184 var s = ABOUT_RESOURCES[i];
2185 s.matched_tag = 0;
2186 s.matched_title = 0;
2187 var matched = false;
2188
2189 // Check if query matches any tags; work backwards toward 1 to assist ranking
2190 for (var j = s.keywords.length - 1; j >= 0; j--) {
2191 // it matches a tag
2192 if (s.keywords[j].toLowerCase().match(textRegex)) {
2193 matched = true;
2194 s.matched_tag = j + 1; // add 1 to index position
2195 }
2196 }
2197 // Check if query matches the doc title, but only for current language
2198 if (s.lang == currentLang) {
2199 // if query matches the doc title
2200 if (s.title.toLowerCase().match(textRegex)) {
2201 matched = true;
2202 s.matched_title = 1;
2203 }
2204 }
2205 if (matched) {
2206 gDocsMatches[matchedCountDocs] = s;
2207 matchedCountDocs++;
2208 }
2209 }
2210
2211
2212 // Search for Design guides
2213 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2214 // current search comparison, with counters for tag and title,
2215 // used later to improve ranking
2216 var s = DESIGN_RESOURCES[i];
2217 s.matched_tag = 0;
2218 s.matched_title = 0;
2219 var matched = false;
2220
2221 // Check if query matches any tags; work backwards toward 1 to assist ranking
2222 for (var j = s.keywords.length - 1; j >= 0; j--) {
2223 // it matches a tag
2224 if (s.keywords[j].toLowerCase().match(textRegex)) {
2225 matched = true;
2226 s.matched_tag = j + 1; // add 1 to index position
2227 }
2228 }
2229 // Check if query matches the doc title, but only for current language
2230 if (s.lang == currentLang) {
2231 // if query matches the doc title
2232 if (s.title.toLowerCase().match(textRegex)) {
2233 matched = true;
2234 s.matched_title = 1;
2235 }
2236 }
2237 if (matched) {
2238 gDocsMatches[matchedCountDocs] = s;
2239 matchedCountDocs++;
2240 }
2241 }
2242
2243
2244 // Search for Distribute guides
2245 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2246 // current search comparison, with counters for tag and title,
2247 // used later to improve ranking
2248 var s = DISTRIBUTE_RESOURCES[i];
2249 s.matched_tag = 0;
2250 s.matched_title = 0;
2251 var matched = false;
2252
2253 // Check if query matches any tags; work backwards toward 1 to assist ranking
2254 for (var j = s.keywords.length - 1; j >= 0; j--) {
2255 // it matches a tag
2256 if (s.keywords[j].toLowerCase().match(textRegex)) {
2257 matched = true;
2258 s.matched_tag = j + 1; // add 1 to index position
2259 }
2260 }
2261 // Check if query matches the doc title, but only for current language
2262 if (s.lang == currentLang) {
2263 // if query matches the doc title
2264 if (s.title.toLowerCase().match(textRegex)) {
2265 matched = true;
2266 s.matched_title = 1;
2267 }
2268 }
2269 if (matched) {
2270 gDocsMatches[matchedCountDocs] = s;
2271 matchedCountDocs++;
2272 }
2273 }
2274
2275
2276 // Search for Google guides
2277 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2278 // current search comparison, with counters for tag and title,
2279 // used later to improve ranking
2280 var s = GOOGLE_RESOURCES[i];
2281 s.matched_tag = 0;
2282 s.matched_title = 0;
2283 var matched = false;
2284
2285 // Check if query matches any tags; work backwards toward 1 to assist ranking
2286 for (var j = s.keywords.length - 1; j >= 0; j--) {
2287 // it matches a tag
2288 if (s.keywords[j].toLowerCase().match(textRegex)) {
2289 matched = true;
2290 s.matched_tag = j + 1; // add 1 to index position
2291 }
2292 }
2293 // Check if query matches the doc title, but only for current language
2294 if (s.lang == currentLang) {
2295 // if query matches the doc title
2296 if (s.title.toLowerCase().match(textRegex)) {
2297 matched = true;
2298 s.matched_title = 1;
2299 }
2300 }
2301 if (matched) {
2302 gDocsMatches[matchedCountDocs] = s;
2303 matchedCountDocs++;
2304 }
2305 }
2306
2307
2308 // Search for Samples
2309 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2310 // current search comparison, with counters for tag and title,
2311 // used later to improve ranking
2312 var s = SAMPLES_RESOURCES[i];
2313 s.matched_tag = 0;
2314 s.matched_title = 0;
2315 var matched = false;
2316 // Check if query matches any tags; work backwards toward 1 to assist ranking
2317 for (var j = s.keywords.length - 1; j >= 0; j--) {
2318 // it matches a tag
2319 if (s.keywords[j].toLowerCase().match(textRegex)) {
2320 matched = true;
2321 s.matched_tag = j + 1; // add 1 to index position
2322 }
2323 }
2324 // Check if query matches the doc title, but only for current language
2325 if (s.lang == currentLang) {
2326 // if query matches the doc title.t
2327 if (s.title.toLowerCase().match(textRegex)) {
2328 matched = true;
2329 s.matched_title = 1;
2330 }
2331 }
2332 if (matched) {
2333 gDocsMatches[matchedCountDocs] = s;
2334 matchedCountDocs++;
2335 }
2336 }
2337
2338 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002339 rank_autocomplete_doc_results(text, gDocsMatches);
2340 }
2341
2342 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002343 sync_selection_table(toroot);
2344 return true; // allow the event to bubble up to the search api
2345 }
2346}
2347
Scott Main0e76e7e2013-03-12 10:24:07 -07002348/* Order the jd doc result list based on match quality */
2349function rank_autocomplete_doc_results(query, matches) {
2350 query = query || '';
2351 if (!matches || !matches.length)
2352 return;
2353
2354 var _resultScoreFn = function(match) {
2355 var score = 1.0;
2356
2357 // if the query matched a tag
2358 if (match.matched_tag > 0) {
2359 // multiply score by factor relative to position in tags list (max of 3)
2360 score *= 3 / match.matched_tag;
2361
2362 // if it also matched the title
2363 if (match.matched_title > 0) {
2364 score *= 2;
2365 }
2366 } else if (match.matched_title > 0) {
2367 score *= 3;
2368 }
2369
2370 return score;
2371 };
2372
2373 for (var i=0; i<matches.length; i++) {
2374 matches[i].__resultScore = _resultScoreFn(matches[i]);
2375 }
2376
2377 matches.sort(function(a,b){
2378 var n = b.__resultScore - a.__resultScore;
2379 if (n == 0) // lexicographical sort if scores are the same
2380 n = (a.label < b.label) ? -1 : 1;
2381 return n;
2382 });
2383}
2384
Scott Main7e447ed2013-02-19 17:22:37 -08002385/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002386function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002387 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002388 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002389 return;
2390
2391 // helper function that gets the last occurence index of the given regex
2392 // in the given string, or -1 if not found
2393 var _lastSearch = function(s, re) {
2394 if (s == '')
2395 return -1;
2396 var l = -1;
2397 var tmp;
2398 while ((tmp = s.search(re)) >= 0) {
2399 if (l < 0) l = 0;
2400 l += tmp;
2401 s = s.substr(tmp + 1);
2402 }
2403 return l;
2404 };
2405
2406 // helper function that counts the occurrences of a given character in
2407 // a given string
2408 var _countChar = function(s, c) {
2409 var n = 0;
2410 for (var i=0; i<s.length; i++)
2411 if (s.charAt(i) == c) ++n;
2412 return n;
2413 };
2414
2415 var queryLower = query.toLowerCase();
2416 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2417 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2418 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2419
2420 var _resultScoreFn = function(result) {
2421 // scores are calculated based on exact and prefix matches,
2422 // and then number of path separators (dots) from the last
2423 // match (i.e. favoring classes and deep package names)
2424 var score = 1.0;
2425 var labelLower = result.label.toLowerCase();
2426 var t;
2427 t = _lastSearch(labelLower, partExactAlnumRE);
2428 if (t >= 0) {
2429 // exact part match
2430 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2431 score *= 200 / (partsAfter + 1);
2432 } else {
2433 t = _lastSearch(labelLower, partPrefixAlnumRE);
2434 if (t >= 0) {
2435 // part prefix match
2436 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2437 score *= 20 / (partsAfter + 1);
2438 }
2439 }
2440
2441 return score;
2442 };
2443
Scott Main7e447ed2013-02-19 17:22:37 -08002444 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002445 // if the API is deprecated, default score is 0; otherwise, perform scoring
2446 if (matches[i].deprecated == "true") {
2447 matches[i].__resultScore = 0;
2448 } else {
2449 matches[i].__resultScore = _resultScoreFn(matches[i]);
2450 }
Scott Mainf5089842012-08-14 16:31:07 -07002451 }
2452
Scott Main7e447ed2013-02-19 17:22:37 -08002453 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002454 var n = b.__resultScore - a.__resultScore;
2455 if (n == 0) // lexicographical sort if scores are the same
2456 n = (a.label < b.label) ? -1 : 1;
2457 return n;
2458 });
2459}
2460
Scott Main7e447ed2013-02-19 17:22:37 -08002461/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002462function highlight_autocomplete_result_labels(query) {
2463 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002464 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002465 return;
2466
2467 var queryLower = query.toLowerCase();
2468 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2469 var queryRE = new RegExp(
2470 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2471 for (var i=0; i<gMatches.length; i++) {
2472 gMatches[i].__hilabel = gMatches[i].label.replace(
2473 queryRE, '<b>$1</b>');
2474 }
Scott Main7e447ed2013-02-19 17:22:37 -08002475 for (var i=0; i<gGoogleMatches.length; i++) {
2476 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2477 queryRE, '<b>$1</b>');
2478 }
Scott Mainf5089842012-08-14 16:31:07 -07002479}
2480
2481function search_focus_changed(obj, focused)
2482{
Scott Main3b90aff2013-08-01 18:09:35 -07002483 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002484 if(obj.value == ""){
2485 $(".search .close").addClass("hide");
2486 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002487 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002488 }
2489}
2490
2491function submit_search() {
2492 var query = document.getElementById('search_autocomplete').value;
2493 location.hash = 'q=' + query;
2494 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002495 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002496 return false;
2497}
2498
2499
2500function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002501 $("#searchResults").slideUp('fast', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002502 $(".search .close").addClass("hide");
2503 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002504
Scott Mainf5089842012-08-14 16:31:07 -07002505 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002506
Scott Mainf5089842012-08-14 16:31:07 -07002507 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2508 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002509
2510 // forcefully regain key-up event control (previously jacked by search api)
2511 $("#search_autocomplete").keyup(function(event) {
2512 return search_changed(event, false, toRoot);
2513 });
2514
Scott Mainf5089842012-08-14 16:31:07 -07002515 return false;
2516}
2517
2518
2519
2520/* ########################################################## */
2521/* ################ CUSTOM SEARCH ENGINE ################## */
2522/* ########################################################## */
2523
Scott Mainf5089842012-08-14 16:31:07 -07002524var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002525google.load('search', '1', {"callback" : function() {
2526 searchControl = new google.search.SearchControl();
2527 } });
Scott Mainf5089842012-08-14 16:31:07 -07002528
2529function loadSearchResults() {
2530 document.getElementById("search_autocomplete").style.color = "#000";
2531
Scott Mainf5089842012-08-14 16:31:07 -07002532 searchControl = new google.search.SearchControl();
2533
2534 // use our existing search form and use tabs when multiple searchers are used
2535 drawOptions = new google.search.DrawOptions();
2536 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2537 drawOptions.setInput(document.getElementById("search_autocomplete"));
2538
2539 // configure search result options
2540 searchOptions = new google.search.SearcherOptions();
2541 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2542
2543 // configure each of the searchers, for each tab
2544 devSiteSearcher = new google.search.WebSearch();
2545 devSiteSearcher.setUserDefinedLabel("All");
2546 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2547
2548 designSearcher = new google.search.WebSearch();
2549 designSearcher.setUserDefinedLabel("Design");
2550 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2551
2552 trainingSearcher = new google.search.WebSearch();
2553 trainingSearcher.setUserDefinedLabel("Training");
2554 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2555
2556 guidesSearcher = new google.search.WebSearch();
2557 guidesSearcher.setUserDefinedLabel("Guides");
2558 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2559
2560 referenceSearcher = new google.search.WebSearch();
2561 referenceSearcher.setUserDefinedLabel("Reference");
2562 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2563
Scott Maindf08ada2012-12-03 08:54:37 -08002564 googleSearcher = new google.search.WebSearch();
2565 googleSearcher.setUserDefinedLabel("Google Services");
2566 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2567
Scott Mainf5089842012-08-14 16:31:07 -07002568 blogSearcher = new google.search.WebSearch();
2569 blogSearcher.setUserDefinedLabel("Blog");
2570 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2571
2572 // add each searcher to the search control
2573 searchControl.addSearcher(devSiteSearcher, searchOptions);
2574 searchControl.addSearcher(designSearcher, searchOptions);
2575 searchControl.addSearcher(trainingSearcher, searchOptions);
2576 searchControl.addSearcher(guidesSearcher, searchOptions);
2577 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002578 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002579 searchControl.addSearcher(blogSearcher, searchOptions);
2580
2581 // configure result options
2582 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2583 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2584 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2585 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2586
2587 // upon ajax search, refresh the url and search title
2588 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2589 updateResultTitle(query);
2590 var query = document.getElementById('search_autocomplete').value;
2591 location.hash = 'q=' + query;
2592 });
2593
Scott Mainde295272013-03-25 15:48:35 -07002594 // once search results load, set up click listeners
2595 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2596 addResultClickListeners();
2597 });
2598
Scott Mainf5089842012-08-14 16:31:07 -07002599 // draw the search results box
2600 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2601
2602 // get query and execute the search
2603 searchControl.execute(decodeURI(getQuery(location.hash)));
2604
2605 document.getElementById("search_autocomplete").focus();
2606 addTabListeners();
2607}
2608// End of loadSearchResults
2609
2610
2611google.setOnLoadCallback(function(){
2612 if (location.hash.indexOf("q=") == -1) {
2613 // if there's no query in the url, don't search and make sure results are hidden
2614 $('#searchResults').hide();
2615 return;
2616 } else {
2617 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002618 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002619 $(".search .close").removeClass("hide");
2620 loadSearchResults();
2621 }
2622}, true);
2623
smain@google.com9a818f52014-10-03 09:25:59 -07002624/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2625 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002626function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002627 // Ignore if there's no search bar (some special pages have no header)
2628 if ($("#search-container").length < 1) return;
2629
smain@google.com3b77ab52014-06-17 11:57:27 -07002630 var hash = escape(location.hash.substr(1));
2631 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002632 // Sanity check that there's an element with that ID on the page
2633 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002634 // If the position of the target element is near the top of the page (<20px, where we expect it
2635 // to be because we need to move it down 60px to become in view), then move it down 60px
2636 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2637 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002638 }
2639 }
2640}
2641
Scott Mainf5089842012-08-14 16:31:07 -07002642// when an event on the browser history occurs (back, forward, load) requery hash and do search
2643$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002644 // Ignore if there's no search bar (some special pages have no header)
2645 if ($("#search-container").length < 1) return;
2646
Dirk Doughertyc3921652014-05-13 16:55:26 -07002647 // If the hash isn't a search query or there's an error in the query,
2648 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002649 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2650 // If the results pane is open, close it.
2651 if (!$("#searchResults").is(":hidden")) {
2652 hideResults();
2653 }
Scott Mainb16376f2014-05-21 20:35:47 -07002654 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002655 return;
2656 }
2657
2658 // Otherwise, we have a search to do
2659 var query = decodeURI(getQuery(location.hash));
2660 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002661 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002662 $("#search_autocomplete").focus();
2663 $(".search .close").removeClass("hide");
2664
2665 updateResultTitle(query);
2666});
2667
2668function updateResultTitle(query) {
2669 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2670}
2671
2672// forcefully regain key-up event control (previously jacked by search api)
2673$("#search_autocomplete").keyup(function(event) {
2674 return search_changed(event, false, toRoot);
2675});
2676
2677// add event listeners to each tab so we can track the browser history
2678function addTabListeners() {
2679 var tabHeaders = $(".gsc-tabHeader");
2680 for (var i = 0; i < tabHeaders.length; i++) {
2681 $(tabHeaders[i]).attr("id",i).click(function() {
2682 /*
2683 // make a copy of the page numbers for the search left pane
2684 setTimeout(function() {
2685 // remove any residual page numbers
2686 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002687 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002688 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002689 // and because we're going to remove it (previous line),
2690 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002691 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2692 .clone().appendTo('#searchResults .gsc-tabsArea');
2693 }, 200);
2694 */
2695 });
2696 }
2697 setTimeout(function(){$(tabHeaders[0]).click()},200);
2698}
2699
Scott Mainde295272013-03-25 15:48:35 -07002700// add analytics tracking events to each result link
2701function addResultClickListeners() {
2702 $("#searchResults a.gs-title").each(function(index, link) {
2703 // When user clicks enter for Google search results, track it
2704 $(link).click(function() {
smain@google.com633f3222014-10-03 15:49:45 -07002705 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).text(),
2706 'from: ' + $("#search_autocomplete").val());
Scott Mainde295272013-03-25 15:48:35 -07002707 });
2708 });
2709}
2710
Scott Mainf5089842012-08-14 16:31:07 -07002711
2712function getQuery(hash) {
2713 var queryParts = hash.split('=');
2714 return queryParts[1];
2715}
2716
2717/* returns the given string with all HTML brackets converted to entities
2718 TODO: move this to the site's JS library */
2719function escapeHTML(string) {
2720 return string.replace(/</g,"&lt;")
2721 .replace(/>/g,"&gt;");
2722}
2723
2724
2725
2726
2727
2728
2729
2730/* ######################################################## */
2731/* ################# JAVADOC REFERENCE ################### */
2732/* ######################################################## */
2733
Scott Main65511c02012-09-07 15:51:32 -07002734/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002735if (location.pathname.indexOf("/reference") == 0) {
2736 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2737 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2738 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002739 $(document).ready(function() {
2740 // init available apis based on user pref
2741 changeApiLevel();
2742 initSidenavHeightResize()
2743 });
2744 }
Scott Main65511c02012-09-07 15:51:32 -07002745}
Scott Mainf5089842012-08-14 16:31:07 -07002746
2747var API_LEVEL_COOKIE = "api_level";
2748var minLevel = 1;
2749var maxLevel = 1;
2750
2751/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002752
Scott Mainf5089842012-08-14 16:31:07 -07002753 function initSidenavHeightResize() {
2754 // Change the drag bar size to nicely fit the scrollbar positions
2755 var $dragBar = $(".ui-resizable-s");
2756 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002757
2758 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002759 containment: "#nav-panels",
2760 handles: "s",
2761 alsoResize: "#packages-nav",
2762 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2763 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2764 });
Scott Main3b90aff2013-08-01 18:09:35 -07002765
Scott Mainf5089842012-08-14 16:31:07 -07002766 }
Scott Main3b90aff2013-08-01 18:09:35 -07002767
Scott Mainf5089842012-08-14 16:31:07 -07002768function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002769 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002770 $('#devdoc-nav').css({
2771 'width' : $('#side-nav').css('width'),
2772 'margin' : $('#side-nav').css('margin')
2773 });
2774 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002775
Scott Mainf5089842012-08-14 16:31:07 -07002776 initSidenavHeightResize();
2777}
2778
2779function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002780 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002781 $('#devdoc-nav').css({
2782 'width' : $('#side-nav').css('width'),
2783 'margin' : $('#side-nav').css('margin')
2784 });
2785 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002786
Scott Mainf5089842012-08-14 16:31:07 -07002787 initSidenavHeightResize();
2788}
2789
2790function buildApiLevelSelector() {
2791 maxLevel = SINCE_DATA.length;
2792 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2793 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2794
2795 minLevel = parseInt($("#doc-api-level").attr("class"));
2796 // Handle provisional api levels; the provisional level will always be the highest possible level
2797 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2798 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2799 if (isNaN(minLevel) && minLevel.length) {
2800 minLevel = maxLevel;
2801 }
2802 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2803 for (var i = maxLevel-1; i >= 0; i--) {
2804 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2805 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2806 select.append(option);
2807 }
2808
2809 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2810 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2811 selectedLevelItem.setAttribute('selected',true);
2812}
2813
2814function changeApiLevel() {
2815 maxLevel = SINCE_DATA.length;
2816 var selectedLevel = maxLevel;
2817
2818 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2819 toggleVisisbleApis(selectedLevel, "body");
2820
smain@google.com6bdcb982014-11-14 11:53:07 -08002821 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002822
2823 if (selectedLevel < minLevel) {
2824 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002825 $("#naMessage").show().html("<div><p><strong>This " + thing
2826 + " requires API level " + minLevel + " or higher.</strong></p>"
2827 + "<p>This document is hidden because your selected API level for the documentation is "
2828 + selectedLevel + ". You can change the documentation API level with the selector "
2829 + "above the left navigation.</p>"
2830 + "<p>For more information about specifying the API level your app requires, "
2831 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2832 + ">Supporting Different Platform Versions</a>.</p>"
2833 + "<input type='button' value='OK, make this page visible' "
2834 + "title='Change the API level to " + minLevel + "' "
2835 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2836 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002837 } else {
2838 $("#naMessage").hide();
2839 }
2840}
2841
2842function toggleVisisbleApis(selectedLevel, context) {
2843 var apis = $(".api",context);
2844 apis.each(function(i) {
2845 var obj = $(this);
2846 var className = obj.attr("class");
2847 var apiLevelIndex = className.lastIndexOf("-")+1;
2848 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2849 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2850 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2851 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2852 return;
2853 }
2854 apiLevel = parseInt(apiLevel);
2855
2856 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2857 var selectedLevelNum = parseInt(selectedLevel)
2858 var apiLevelNum = parseInt(apiLevel);
2859 if (isNaN(apiLevelNum)) {
2860 apiLevelNum = maxLevel;
2861 }
2862
2863 // Grey things out that aren't available and give a tooltip title
2864 if (apiLevelNum > selectedLevelNum) {
2865 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002866 + apiLevel + "\" or higher. To reveal, change the target API level "
2867 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002868 }
Scott Mainf5089842012-08-14 16:31:07 -07002869 else obj.removeClass("absent").removeAttr("title");
2870 });
2871}
2872
2873
2874
2875
2876/* ################# SIDENAV TREE VIEW ################### */
2877
2878function new_node(me, mom, text, link, children_data, api_level)
2879{
2880 var node = new Object();
2881 node.children = Array();
2882 node.children_data = children_data;
2883 node.depth = mom.depth + 1;
2884
2885 node.li = document.createElement("li");
2886 mom.get_children_ul().appendChild(node.li);
2887
2888 node.label_div = document.createElement("div");
2889 node.label_div.className = "label";
2890 if (api_level != null) {
2891 $(node.label_div).addClass("api");
2892 $(node.label_div).addClass("api-level-"+api_level);
2893 }
2894 node.li.appendChild(node.label_div);
2895
2896 if (children_data != null) {
2897 node.expand_toggle = document.createElement("a");
2898 node.expand_toggle.href = "javascript:void(0)";
2899 node.expand_toggle.onclick = function() {
2900 if (node.expanded) {
2901 $(node.get_children_ul()).slideUp("fast");
2902 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2903 node.expanded = false;
2904 } else {
2905 expand_node(me, node);
2906 }
2907 };
2908 node.label_div.appendChild(node.expand_toggle);
2909
2910 node.plus_img = document.createElement("img");
2911 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2912 node.plus_img.className = "plus";
2913 node.plus_img.width = "8";
2914 node.plus_img.border = "0";
2915 node.expand_toggle.appendChild(node.plus_img);
2916
2917 node.expanded = false;
2918 }
2919
2920 var a = document.createElement("a");
2921 node.label_div.appendChild(a);
2922 node.label = document.createTextNode(text);
2923 a.appendChild(node.label);
2924 if (link) {
2925 a.href = me.toroot + link;
2926 } else {
2927 if (children_data != null) {
2928 a.className = "nolink";
2929 a.href = "javascript:void(0)";
2930 a.onclick = node.expand_toggle.onclick;
2931 // This next line shouldn't be necessary. I'll buy a beer for the first
2932 // person who figures out how to remove this line and have the link
2933 // toggle shut on the first try. --joeo@android.com
2934 node.expanded = false;
2935 }
2936 }
Scott Main3b90aff2013-08-01 18:09:35 -07002937
Scott Mainf5089842012-08-14 16:31:07 -07002938
2939 node.children_ul = null;
2940 node.get_children_ul = function() {
2941 if (!node.children_ul) {
2942 node.children_ul = document.createElement("ul");
2943 node.children_ul.className = "children_ul";
2944 node.children_ul.style.display = "none";
2945 node.li.appendChild(node.children_ul);
2946 }
2947 return node.children_ul;
2948 };
2949
2950 return node;
2951}
2952
Robert Lyd2dd6e52012-11-29 21:28:48 -08002953
2954
2955
Scott Mainf5089842012-08-14 16:31:07 -07002956function expand_node(me, node)
2957{
2958 if (node.children_data && !node.expanded) {
2959 if (node.children_visited) {
2960 $(node.get_children_ul()).slideDown("fast");
2961 } else {
2962 get_node(me, node);
2963 if ($(node.label_div).hasClass("absent")) {
2964 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07002965 }
Scott Mainf5089842012-08-14 16:31:07 -07002966 $(node.get_children_ul()).slideDown("fast");
2967 }
2968 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2969 node.expanded = true;
2970
2971 // perform api level toggling because new nodes are new to the DOM
2972 var selectedLevel = $("#apiLevelSelector option:selected").val();
2973 toggleVisisbleApis(selectedLevel, "#side-nav");
2974 }
2975}
2976
2977function get_node(me, mom)
2978{
2979 mom.children_visited = true;
2980 for (var i in mom.children_data) {
2981 var node_data = mom.children_data[i];
2982 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2983 node_data[2], node_data[3]);
2984 }
2985}
2986
2987function this_page_relative(toroot)
2988{
2989 var full = document.location.pathname;
2990 var file = "";
2991 if (toroot.substr(0, 1) == "/") {
2992 if (full.substr(0, toroot.length) == toroot) {
2993 return full.substr(toroot.length);
2994 } else {
2995 // the file isn't under toroot. Fail.
2996 return null;
2997 }
2998 } else {
2999 if (toroot != "./") {
3000 toroot = "./" + toroot;
3001 }
3002 do {
3003 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
3004 var pos = full.lastIndexOf("/");
3005 file = full.substr(pos) + file;
3006 full = full.substr(0, pos);
3007 toroot = toroot.substr(0, toroot.length-3);
3008 }
3009 } while (toroot != "" && toroot != "/");
3010 return file.substr(1);
3011 }
3012}
3013
3014function find_page(url, data)
3015{
3016 var nodes = data;
3017 var result = null;
3018 for (var i in nodes) {
3019 var d = nodes[i];
3020 if (d[1] == url) {
3021 return new Array(i);
3022 }
3023 else if (d[2] != null) {
3024 result = find_page(url, d[2]);
3025 if (result != null) {
3026 return (new Array(i).concat(result));
3027 }
3028 }
3029 }
3030 return null;
3031}
3032
Scott Mainf5089842012-08-14 16:31:07 -07003033function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07003034 // load json file for navtree data
3035 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3036 // when the file is loaded, initialize the tree
3037 if(jqxhr.status === 200) {
3038 init_navtree("tree-list", toroot, NAVTREE_DATA);
3039 }
3040 });
Scott Main3b90aff2013-08-01 18:09:35 -07003041
Scott Mainf5089842012-08-14 16:31:07 -07003042 // perform api level toggling because because the whole tree is new to the DOM
3043 var selectedLevel = $("#apiLevelSelector option:selected").val();
3044 toggleVisisbleApis(selectedLevel, "#side-nav");
3045}
3046
3047function init_navtree(navtree_id, toroot, root_nodes)
3048{
3049 var me = new Object();
3050 me.toroot = toroot;
3051 me.node = new Object();
3052
3053 me.node.li = document.getElementById(navtree_id);
3054 me.node.children_data = root_nodes;
3055 me.node.children = new Array();
3056 me.node.children_ul = document.createElement("ul");
3057 me.node.get_children_ul = function() { return me.node.children_ul; };
3058 //me.node.children_ul.className = "children_ul";
3059 me.node.li.appendChild(me.node.children_ul);
3060 me.node.depth = 0;
3061
3062 get_node(me, me.node);
3063
3064 me.this_page = this_page_relative(toroot);
3065 me.breadcrumbs = find_page(me.this_page, root_nodes);
3066 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3067 var mom = me.node;
3068 for (var i in me.breadcrumbs) {
3069 var j = me.breadcrumbs[i];
3070 mom = mom.children[j];
3071 expand_node(me, mom);
3072 }
3073 mom.label_div.className = mom.label_div.className + " selected";
3074 addLoadEvent(function() {
3075 scrollIntoView("nav-tree");
3076 });
3077 }
3078}
3079
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003080
3081
3082
3083
3084
3085
3086
Robert Lyd2dd6e52012-11-29 21:28:48 -08003087/* TODO: eliminate redundancy with non-google functions */
3088function init_google_navtree(navtree_id, toroot, root_nodes)
3089{
3090 var me = new Object();
3091 me.toroot = toroot;
3092 me.node = new Object();
3093
3094 me.node.li = document.getElementById(navtree_id);
3095 me.node.children_data = root_nodes;
3096 me.node.children = new Array();
3097 me.node.children_ul = document.createElement("ul");
3098 me.node.get_children_ul = function() { return me.node.children_ul; };
3099 //me.node.children_ul.className = "children_ul";
3100 me.node.li.appendChild(me.node.children_ul);
3101 me.node.depth = 0;
3102
3103 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003104}
3105
3106function new_google_node(me, mom, text, link, children_data, api_level)
3107{
3108 var node = new Object();
3109 var child;
3110 node.children = Array();
3111 node.children_data = children_data;
3112 node.depth = mom.depth + 1;
3113 node.get_children_ul = function() {
3114 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003115 node.children_ul = document.createElement("ul");
3116 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003117 node.li.appendChild(node.children_ul);
3118 }
3119 return node.children_ul;
3120 };
3121 node.li = document.createElement("li");
3122
3123 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003124
3125
Robert Lyd2dd6e52012-11-29 21:28:48 -08003126 if(link) {
3127 child = document.createElement("a");
3128
3129 }
3130 else {
3131 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003132 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003133
3134 }
3135 if (children_data != null) {
3136 node.li.className="nav-section";
3137 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003138 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003139 node.li.appendChild(node.label_div);
3140 get_google_node(me, node);
3141 node.label_div.appendChild(child);
3142 }
3143 else {
3144 node.li.appendChild(child);
3145 }
3146 if(link) {
3147 child.href = me.toroot + link;
3148 }
3149 node.label = document.createTextNode(text);
3150 child.appendChild(node.label);
3151
3152 node.children_ul = null;
3153
3154 return node;
3155}
3156
3157function get_google_node(me, mom)
3158{
3159 mom.children_visited = true;
3160 var linkText;
3161 for (var i in mom.children_data) {
3162 var node_data = mom.children_data[i];
3163 linkText = node_data[0];
3164
3165 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3166 linkText = linkText.substr(19, linkText.length);
3167 }
3168 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3169 node_data[2], node_data[3]);
3170 }
3171}
Scott Mainad08f072013-08-20 16:49:57 -07003172
3173
3174
3175
3176
3177
3178/****** NEW version of script to build google and sample navs dynamically ******/
3179// TODO: update Google reference docs to tolerate this new implementation
3180
Scott Maine624b3f2013-09-12 12:56:41 -07003181var NODE_NAME = 0;
3182var NODE_HREF = 1;
3183var NODE_GROUP = 2;
3184var NODE_TAGS = 3;
3185var NODE_CHILDREN = 4;
3186
Scott Mainad08f072013-08-20 16:49:57 -07003187function init_google_navtree2(navtree_id, data)
3188{
3189 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003190 for (var i in data) {
3191 var node_data = data[i];
3192 $containerUl.append(new_google_node2(node_data));
3193 }
3194
Scott Main70557ee2013-10-30 14:47:40 -07003195 // Make all third-generation list items 'sticky' to prevent them from collapsing
3196 $containerUl.find('li li li.nav-section').addClass('sticky');
3197
Scott Mainad08f072013-08-20 16:49:57 -07003198 initExpandableNavItems("#"+navtree_id);
3199}
3200
3201function new_google_node2(node_data)
3202{
Scott Maine624b3f2013-09-12 12:56:41 -07003203 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003204 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3205 linkText = linkText.substr(19, linkText.length);
3206 }
3207 var $li = $('<li>');
3208 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003209 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003210 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3211 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003212 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003213 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3214 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003215 }
3216 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003217 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003218 $li.addClass("nav-section");
3219 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003220 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003221
Scott Maine624b3f2013-09-12 12:56:41 -07003222 for (var i in node_data[NODE_CHILDREN]) {
3223 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003224 $childUl.append(new_google_node2(child_node_data));
3225 }
3226 $li.append($childUl);
3227 }
3228 $li.prepend($a);
3229
3230 return $li;
3231}
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
Robert Lyd2dd6e52012-11-29 21:28:48 -08003243function showGoogleRefTree() {
3244 init_default_google_navtree(toRoot);
3245 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003246}
3247
3248function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003249 // load json file for navtree data
3250 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3251 // when the file is loaded, initialize the tree
3252 if(jqxhr.status === 200) {
3253 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3254 highlightSidenav();
3255 resizeNav();
3256 }
3257 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003258}
3259
3260function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003261 // load json file for navtree data
3262 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3263 // when the file is loaded, initialize the tree
3264 if(jqxhr.status === 200) {
3265 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3266 highlightSidenav();
3267 resizeNav();
3268 }
3269 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003270}
3271
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003272function showSamplesRefTree() {
3273 init_default_samples_navtree(toRoot);
3274}
3275
3276function init_default_samples_navtree(toroot) {
3277 // load json file for navtree data
3278 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3279 // when the file is loaded, initialize the tree
3280 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003281 // hack to remove the "about the samples" link then put it back in
3282 // after we nuke the list to remove the dummy static list of samples
3283 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3284 $("#nav.samples-nav").empty();
3285 $("#nav.samples-nav").append($firstLi);
3286
Scott Mainad08f072013-08-20 16:49:57 -07003287 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003288 highlightSidenav();
3289 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003290 if ($("#jd-content #samples").length) {
3291 showSamples();
3292 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003293 }
3294 });
3295}
3296
Scott Mainf5089842012-08-14 16:31:07 -07003297/* TOGGLE INHERITED MEMBERS */
3298
3299/* Toggle an inherited class (arrow toggle)
3300 * @param linkObj The link that was clicked.
3301 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3302 * 'null' to simply toggle.
3303 */
3304function toggleInherited(linkObj, expand) {
3305 var base = linkObj.getAttribute("id");
3306 var list = document.getElementById(base + "-list");
3307 var summary = document.getElementById(base + "-summary");
3308 var trigger = document.getElementById(base + "-trigger");
3309 var a = $(linkObj);
3310 if ( (expand == null && a.hasClass("closed")) || expand ) {
3311 list.style.display = "none";
3312 summary.style.display = "block";
3313 trigger.src = toRoot + "assets/images/triangle-opened.png";
3314 a.removeClass("closed");
3315 a.addClass("opened");
3316 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3317 list.style.display = "block";
3318 summary.style.display = "none";
3319 trigger.src = toRoot + "assets/images/triangle-closed.png";
3320 a.removeClass("opened");
3321 a.addClass("closed");
3322 }
3323 return false;
3324}
3325
3326/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3327 * @param linkObj The link that was clicked.
3328 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3329 * 'null' to simply toggle.
3330 */
3331function toggleAllInherited(linkObj, expand) {
3332 var a = $(linkObj);
3333 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3334 var expandos = $(".jd-expando-trigger", table);
3335 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3336 expandos.each(function(i) {
3337 toggleInherited(this, true);
3338 });
3339 a.text("[Collapse]");
3340 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3341 expandos.each(function(i) {
3342 toggleInherited(this, false);
3343 });
3344 a.text("[Expand]");
3345 }
3346 return false;
3347}
3348
3349/* Toggle all inherited members in the class (link in the class title)
3350 */
3351function toggleAllClassInherited() {
3352 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3353 var toggles = $(".toggle-all", $("#body-content"));
3354 if (a.text() == "[Expand All]") {
3355 toggles.each(function(i) {
3356 toggleAllInherited(this, true);
3357 });
3358 a.text("[Collapse All]");
3359 } else {
3360 toggles.each(function(i) {
3361 toggleAllInherited(this, false);
3362 });
3363 a.text("[Expand All]");
3364 }
3365 return false;
3366}
3367
3368/* Expand all inherited members in the class. Used when initiating page search */
3369function ensureAllInheritedExpanded() {
3370 var toggles = $(".toggle-all", $("#body-content"));
3371 toggles.each(function(i) {
3372 toggleAllInherited(this, true);
3373 });
3374 $("#toggleAllClassInherited").text("[Collapse All]");
3375}
3376
3377
3378/* HANDLE KEY EVENTS
3379 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3380 */
3381var agent = navigator['userAgent'].toLowerCase();
3382var mac = agent.indexOf("macintosh") != -1;
3383
3384$(document).keydown( function(e) {
3385var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3386 if (control && e.which == 70) { // 70 is "F"
3387 ensureAllInheritedExpanded();
3388 }
3389});
Scott Main498d7102013-08-21 15:47:38 -07003390
3391
3392
3393
3394
3395
3396/* On-demand functions */
3397
3398/** Move sample code line numbers out of PRE block and into non-copyable column */
3399function initCodeLineNumbers() {
3400 var numbers = $("#codesample-block a.number");
3401 if (numbers.length) {
3402 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3403 }
3404
3405 $(document).ready(function() {
3406 // select entire line when clicked
3407 $("span.code-line").click(function() {
3408 if (!shifted) {
3409 selectText(this);
3410 }
3411 });
3412 // invoke line link on double click
3413 $(".code-line").dblclick(function() {
3414 document.location.hash = $(this).attr('id');
3415 });
3416 // highlight the line when hovering on the number
3417 $("#codesample-line-numbers a.number").mouseover(function() {
3418 var id = $(this).attr('href');
3419 $(id).css('background','#e7e7e7');
3420 });
3421 $("#codesample-line-numbers a.number").mouseout(function() {
3422 var id = $(this).attr('href');
3423 $(id).css('background','none');
3424 });
3425 });
3426}
3427
3428// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3429var shifted = false;
3430$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3431
3432// courtesy of jasonedelman.com
3433function selectText(element) {
3434 var doc = document
3435 , range, selection
3436 ;
3437 if (doc.body.createTextRange) { //ms
3438 range = doc.body.createTextRange();
3439 range.moveToElementText(element);
3440 range.select();
3441 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003442 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003443 range = doc.createRange();
3444 range.selectNodeContents(element);
3445 selection.removeAllRanges();
3446 selection.addRange(range);
3447 }
Scott Main285f0772013-08-22 23:22:09 +00003448}
Scott Main03aca9a2013-10-31 07:20:55 -07003449
3450
3451
3452
3453/** Display links and other information about samples that match the
3454 group specified by the URL */
3455function showSamples() {
3456 var group = $("#samples").attr('class');
3457 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3458
3459 var $ul = $("<ul>");
3460 $selectedLi = $("#nav li.selected");
3461
3462 $selectedLi.children("ul").children("li").each(function() {
3463 var $li = $("<li>").append($(this).find("a").first().clone());
3464 $ul.append($li);
3465 });
3466
3467 $("#samples").append($ul);
3468
3469}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003470
3471
3472
3473/* ########################################################## */
3474/* ################### RESOURCE CARDS ##################### */
3475/* ########################################################## */
3476
3477/** Handle resource queries, collections, and grids (sections). Requires
3478 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3479
3480(function() {
3481 // Prevent the same resource from being loaded more than once per page.
3482 var addedPageResources = {};
3483
3484 $(document).ready(function() {
3485 $('.resource-widget').each(function() {
3486 initResourceWidget(this);
3487 });
3488
3489 /* Pass the line height to ellipsisfade() to adjust the height of the
3490 text container to show the max number of lines possible, without
3491 showing lines that are cut off. This works with the css ellipsis
3492 classes to fade last text line and apply an ellipsis char. */
3493
Scott Mainb16376f2014-05-21 20:35:47 -07003494 //card text currently uses 15px line height.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003495 var lineHeight = 15;
3496 $('.card-info .text').ellipsisfade(lineHeight);
3497 });
3498
3499 /*
3500 Three types of resource layouts:
3501 Flow - Uses a fixed row-height flow using float left style.
3502 Carousel - Single card slideshow all same dimension absolute.
3503 Stack - Uses fixed columns and flexible element height.
3504 */
3505 function initResourceWidget(widget) {
3506 var $widget = $(widget);
3507 var isFlow = $widget.hasClass('resource-flow-layout'),
3508 isCarousel = $widget.hasClass('resource-carousel-layout'),
3509 isStack = $widget.hasClass('resource-stack-layout');
3510
3511 // find size of widget by pulling out its class name
3512 var sizeCols = 1;
3513 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
3514 if (m) {
3515 sizeCols = parseInt(m[1], 10);
3516 }
3517
3518 var opts = {
3519 cardSizes: ($widget.data('cardsizes') || '').split(','),
3520 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3521 itemsPerPage: $widget.data('itemsperpage'),
3522 sortOrder: $widget.data('sortorder'),
3523 query: $widget.data('query'),
3524 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003525 sizeCols: sizeCols,
3526 /* Added by LFL 6/6/14 */
3527 resourceStyle: $widget.data('resourcestyle') || 'card',
3528 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003529 };
3530
3531 // run the search for the set of resources to show
3532
3533 var resources = buildResourceList(opts);
3534
3535 if (isFlow) {
3536 drawResourcesFlowWidget($widget, opts, resources);
3537 } else if (isCarousel) {
3538 drawResourcesCarouselWidget($widget, opts, resources);
3539 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003540 /* Looks like this got removed and is not used, so repurposing for the
3541 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003542 Modified by LFL 6/6/14
3543 */
3544 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003545 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003546 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003547 }
3548 }
3549
3550 /* Initializes a Resource Carousel Widget */
3551 function drawResourcesCarouselWidget($widget, opts, resources) {
3552 $widget.empty();
3553 var plusone = true; //always show plusone on carousel
3554
3555 $widget.addClass('resource-card slideshow-container')
3556 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3557 .append($('<a>').addClass('slideshow-next').text('Next'));
3558
3559 var css = { 'width': $widget.width() + 'px',
3560 'height': $widget.height() + 'px' };
3561
3562 var $ul = $('<ul>');
3563
3564 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003565 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003566 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003567 .decorateResourceCard(resources[i],plusone);
3568
3569 $('<li>').css(css)
3570 .append($card)
3571 .appendTo($ul);
3572 }
3573
3574 $('<div>').addClass('frame')
3575 .append($ul)
3576 .appendTo($widget);
3577
3578 $widget.dacSlideshow({
3579 auto: true,
3580 btnPrev: '.slideshow-prev',
3581 btnNext: '.slideshow-next'
3582 });
3583 };
3584
Robert Lye7eeb402014-06-03 19:35:24 -07003585 /* Initializes a Resource Card Stack Widget (column-based layout)
3586 Modified by LFL 6/6/14
3587 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003588 function drawResourcesStackWidget($widget, opts, resources, sections) {
3589 // Don't empty widget, grab all items inside since they will be the first
3590 // items stacked, followed by the resource query
3591 var plusone = true; //by default show plusone on section cards
3592 var cards = $widget.find('.resource-card').detach().toArray();
3593 var numStacks = opts.numStacks || 1;
3594 var $stacks = [];
3595 var urlString;
3596
3597 for (var i = 0; i < numStacks; ++i) {
3598 $stacks[i] = $('<div>').addClass('resource-card-stack')
3599 .appendTo($widget);
3600 }
3601
3602 var sectionResources = [];
3603
3604 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003605 if (sections) {
3606 for (var i = 0; i < sections.length; ++i) {
3607 if (!sections[i].sections || !sections[i].sections.length) {
3608 // Render it as a resource card
3609 sectionResources.push(
3610 $('<a>')
3611 .addClass('resource-card section-card')
3612 .attr('href', cleanUrl(sections[i].resource.url))
3613 .decorateResourceCard(sections[i].resource,plusone)[0]
3614 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003615
Robert Lye7eeb402014-06-03 19:35:24 -07003616 } else {
3617 cards.push(
3618 $('<div>')
3619 .addClass('resource-card section-card-menu')
3620 .decorateResourceSection(sections[i],plusone)[0]
3621 );
3622 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003623 }
3624 }
3625
3626 cards = cards.concat(sectionResources);
3627
3628 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003629 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003630
Robert Lye7eeb402014-06-03 19:35:24 -07003631 if (opts.resourceStyle.indexOf('related') > -1) {
3632 $card.addClass('related-card');
3633 }
smain@google.com95948b82014-06-16 19:24:25 -07003634
Dirk Doughertyc3921652014-05-13 16:55:26 -07003635 cards.push($card[0]);
3636 }
3637
Robert Lye7eeb402014-06-03 19:35:24 -07003638 if (opts.stackSort != 'false') {
3639 for (var i = 0; i < cards.length; ++i) {
3640 // Find the stack with the shortest height, but give preference to
3641 // left to right order.
3642 var minHeight = $stacks[0].height();
3643 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003644
Robert Lye7eeb402014-06-03 19:35:24 -07003645 for (var j = 1; j < numStacks; ++j) {
3646 var height = $stacks[j].height();
3647 if (height < minHeight - 45) {
3648 minHeight = height;
3649 minIndex = j;
3650 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003651 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003652
Robert Lye7eeb402014-06-03 19:35:24 -07003653 $stacks[minIndex].append($(cards[i]));
3654 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003655 }
3656
3657 };
smain@google.com95948b82014-06-16 19:24:25 -07003658
3659 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003660 Create a resource card using the given resource object and a list of html
3661 configured options. Returns a jquery object containing the element.
3662 */
smain@google.com95948b82014-06-16 19:24:25 -07003663 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003664 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003665
Robert Lye7eeb402014-06-03 19:35:24 -07003666 // The difference here is that generic cards are not entirely clickable
3667 // so its a div instead of an a tag, also the generic one is not given
3668 // the resource-card class so it appears with a transparent background
3669 // and can be styled in whatever way the css setup.
3670 if (opts.resourceStyle == 'generic') {
3671 $el = $('<div>')
3672 .addClass('resource')
3673 .attr('href', cleanUrl(resource.url))
3674 .decorateResource(resource, opts);
3675 } else {
3676 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003677
Robert Lye7eeb402014-06-03 19:35:24 -07003678 $el = $('<a>')
3679 .addClass(cls)
3680 .attr('href', cleanUrl(resource.url))
3681 .decorateResourceCard(resource, plusone);
3682 }
smain@google.com95948b82014-06-16 19:24:25 -07003683
Robert Lye7eeb402014-06-03 19:35:24 -07003684 return $el;
3685 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003686
3687 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3688 function drawResourcesFlowWidget($widget, opts, resources) {
3689 $widget.empty();
3690 var cardSizes = opts.cardSizes || ['6x6'];
3691 var i = 0, j = 0;
3692 var plusone = true; // by default show plusone on resource cards
3693
3694 while (i < resources.length) {
3695 var cardSize = cardSizes[j++ % cardSizes.length];
3696 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003697 // Some card sizes do not get a plusone button, such as where space is constrained
3698 // or for cards commonly embedded in docs (to improve overall page speed).
3699 plusone = !((cardSize == "6x2") || (cardSize == "6x3") ||
3700 (cardSize == "9x2") || (cardSize == "9x3") ||
3701 (cardSize == "12x2") || (cardSize == "12x3"));
3702
3703 // A stack has a third dimension which is the number of stacked items
3704 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3705 var stackCount = 0;
3706 var $stackDiv = null;
3707
3708 if (isStack) {
3709 // Create a stack container which should have the dimensions defined
3710 // by the product of the items inside.
3711 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
3712 + 'x' + isStack[2] * isStack[3]) .appendTo($widget);
3713 }
3714
3715 // Build each stack item or just a single item
3716 do {
3717 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003718
Robert Lye7eeb402014-06-03 19:35:24 -07003719 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003720
3721 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003722 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003723
Dirk Doughertyc3921652014-05-13 16:55:26 -07003724 if (isStack) {
3725 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3726 if (++stackCount == parseInt(isStack[3])) {
3727 $card.addClass('resource-card-row-stack-last');
3728 stackCount = 0;
3729 }
3730 } else {
3731 stackCount = 0;
3732 }
3733
Robert Lye7eeb402014-06-03 19:35:24 -07003734 $card.appendTo($stackDiv || $widget);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003735
3736 } while (++i < resources.length && stackCount > 0);
3737 }
3738 }
3739
3740 /* Build a site map of resources using a section as a root. */
3741 function buildSectionList(opts) {
3742 if (opts.section && SECTION_BY_ID[opts.section]) {
3743 return SECTION_BY_ID[opts.section].sections || [];
3744 }
3745 return [];
3746 }
3747
3748 function buildResourceList(opts) {
3749 var maxResults = opts.maxResults || 100;
3750
3751 var query = opts.query || '';
3752 var expressions = parseResourceQuery(query);
3753 var addedResourceIndices = {};
3754 var results = [];
3755
3756 for (var i = 0; i < expressions.length; i++) {
3757 var clauses = expressions[i];
3758
3759 // build initial set of resources from first clause
3760 var firstClause = clauses[0];
3761 var resources = [];
3762 switch (firstClause.attr) {
3763 case 'type':
3764 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3765 break;
3766 case 'lang':
3767 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3768 break;
3769 case 'tag':
3770 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3771 break;
3772 case 'collection':
3773 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3774 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3775 break;
3776 case 'section':
3777 var urls = SITE_MAP[firstClause.value].sections || [];
3778 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3779 break;
3780 }
3781 // console.log(firstClause.attr + ':' + firstClause.value);
3782 resources = resources || [];
3783
3784 // use additional clauses to filter corpus
3785 if (clauses.length > 1) {
3786 var otherClauses = clauses.slice(1);
3787 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3788 }
3789
3790 // filter out resources already added
3791 if (i > 1) {
3792 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3793 }
3794
3795 // add to list of already added indices
3796 for (var j = 0; j < resources.length; j++) {
3797 // console.log(resources[j].title);
3798 addedResourceIndices[resources[j].index] = 1;
3799 }
3800
3801 // concat to final results list
3802 results = results.concat(resources);
3803 }
3804
3805 if (opts.sortOrder && results.length) {
3806 var attr = opts.sortOrder;
3807
3808 if (opts.sortOrder == 'random') {
3809 var i = results.length, j, temp;
3810 while (--i) {
3811 j = Math.floor(Math.random() * (i + 1));
3812 temp = results[i];
3813 results[i] = results[j];
3814 results[j] = temp;
3815 }
3816 } else {
3817 var desc = attr.charAt(0) == '-';
3818 if (desc) {
3819 attr = attr.substring(1);
3820 }
3821 results = results.sort(function(x,y) {
3822 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3823 });
3824 }
3825 }
3826
3827 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3828 results = results.slice(0, maxResults);
3829
3830 for (var j = 0; j < results.length; ++j) {
3831 addedPageResources[results[j].index] = 1;
3832 }
3833
3834 return results;
3835 }
3836
3837
3838 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3839 return function(resource) {
3840 return !addedResourceIndices[resource.index];
3841 };
3842 }
3843
3844
3845 function getResourceMatchesClausesFilter(clauses) {
3846 return function(resource) {
3847 return doesResourceMatchClauses(resource, clauses);
3848 };
3849 }
3850
3851
3852 function doesResourceMatchClauses(resource, clauses) {
3853 for (var i = 0; i < clauses.length; i++) {
3854 var map;
3855 switch (clauses[i].attr) {
3856 case 'type':
3857 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3858 break;
3859 case 'lang':
3860 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3861 break;
3862 case 'tag':
3863 map = IS_RESOURCE_TAGGED[clauses[i].value];
3864 break;
3865 }
3866
3867 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3868 return clauses[i].negative;
3869 }
3870 }
3871 return true;
3872 }
smain@google.com95948b82014-06-16 19:24:25 -07003873
Robert Lye7eeb402014-06-03 19:35:24 -07003874 function cleanUrl(url)
3875 {
3876 if (url && url.indexOf('//') === -1) {
3877 url = toRoot + url;
3878 }
smain@google.com95948b82014-06-16 19:24:25 -07003879
Robert Lye7eeb402014-06-03 19:35:24 -07003880 return url;
3881 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003882
3883
3884 function parseResourceQuery(query) {
3885 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3886 var expressions = [];
3887 var expressionStrs = query.split(',') || [];
3888 for (var i = 0; i < expressionStrs.length; i++) {
3889 var expr = expressionStrs[i] || '';
3890
3891 // Break expression into clauses (clause e.g. 'tag:foo')
3892 var clauses = [];
3893 var clauseStrs = expr.split(/(?=[\+\-])/);
3894 for (var j = 0; j < clauseStrs.length; j++) {
3895 var clauseStr = clauseStrs[j] || '';
3896
3897 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3898 var parts = clauseStr.split(':');
3899 var clause = {};
3900
3901 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3902 if (clause.attr) {
3903 if (clause.attr.charAt(0) == '+') {
3904 clause.attr = clause.attr.substring(1);
3905 } else if (clause.attr.charAt(0) == '-') {
3906 clause.negative = true;
3907 clause.attr = clause.attr.substring(1);
3908 }
3909 }
3910
3911 if (parts.length > 1) {
3912 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3913 }
3914
3915 clauses.push(clause);
3916 }
3917
3918 if (!clauses.length) {
3919 continue;
3920 }
3921
3922 expressions.push(clauses);
3923 }
3924
3925 return expressions;
3926 }
3927})();
3928
3929(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07003930
smain@google.com95948b82014-06-16 19:24:25 -07003931 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003932 Utility method for creating dom for the description area of a card.
3933 Used in decorateResourceCard and decorateResource.
3934 */
3935 function buildResourceCardDescription(resource, plusone) {
3936 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07003937
Robert Lye7eeb402014-06-03 19:35:24 -07003938 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07003939
Robert Lye7eeb402014-06-03 19:35:24 -07003940 if (resource.cta) {
3941 $description.append($('<a>').addClass('cta').html(resource.cta));
3942 }
smain@google.com95948b82014-06-16 19:24:25 -07003943
Robert Lye7eeb402014-06-03 19:35:24 -07003944 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07003945 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07003946 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07003947
Robert Lye7eeb402014-06-03 19:35:24 -07003948 $description.append($('<div>').addClass('util')
3949 .append($('<div>').addClass('g-plusone')
3950 .attr('data-size', 'small')
3951 .attr('data-align', 'right')
3952 .attr('data-href', plusurl)));
3953 }
smain@google.com95948b82014-06-16 19:24:25 -07003954
Robert Lye7eeb402014-06-03 19:35:24 -07003955 return $description;
3956 }
smain@google.com95948b82014-06-16 19:24:25 -07003957
3958
Dirk Doughertyc3921652014-05-13 16:55:26 -07003959 /* Simple jquery function to create dom for a standard resource card */
3960 $.fn.decorateResourceCard = function(resource,plusone) {
3961 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07003962 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07003963 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07003964
Robert Lye7eeb402014-06-03 19:35:24 -07003965 if (imgUrl.indexOf('//') === -1) {
3966 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003967 }
Robert Lye7eeb402014-06-03 19:35:24 -07003968
3969 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07003970 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07003971 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07003972 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07003973
Robert Lye7eeb402014-06-03 19:35:24 -07003974 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
3975 .append($('<div>').addClass('section').text(section))
3976 .append($('<div>').addClass('title').html(resource.title))
3977 .append(buildResourceCardDescription(resource, plusone))
3978 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003979
3980 return this;
3981 };
3982
3983 /* Simple jquery function to create dom for a resource section card (menu) */
3984 $.fn.decorateResourceSection = function(section,plusone) {
3985 var resource = section.resource;
3986 //keep url clean for matching and offline mode handling
3987 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
3988 var $base = $('<a>')
3989 .addClass('card-bg')
3990 .attr('href', resource.url)
3991 .append($('<div>').addClass('card-section-icon')
3992 .append($('<div>').addClass('icon'))
3993 .append($('<div>').addClass('section').html(resource.title)))
3994 .appendTo(this);
3995
3996 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
3997
3998 if (section.sections && section.sections.length) {
3999 // Recurse the section sub-tree to find a resource image.
4000 var stack = [section];
4001
4002 while (stack.length) {
4003 if (stack[0].resource.image) {
4004 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
4005 break;
4006 }
4007
4008 if (stack[0].sections) {
4009 stack = stack.concat(stack[0].sections);
4010 }
4011
4012 stack.shift();
4013 }
4014
4015 var $ul = $('<ul>')
4016 .appendTo($cardInfo);
4017
4018 var max = section.sections.length > 3 ? 3 : section.sections.length;
4019
4020 for (var i = 0; i < max; ++i) {
4021
4022 var subResource = section.sections[i];
4023 if (!plusone) {
4024 $('<li>')
4025 .append($('<a>').attr('href', subResource.url)
4026 .append($('<div>').addClass('title').html(subResource.title))
4027 .append($('<div>').addClass('description ellipsis')
4028 .append($('<div>').addClass('text').html(subResource.summary))
4029 .append($('<div>').addClass('util'))))
4030 .appendTo($ul);
4031 } else {
4032 $('<li>')
4033 .append($('<a>').attr('href', subResource.url)
4034 .append($('<div>').addClass('title').html(subResource.title))
4035 .append($('<div>').addClass('description ellipsis')
4036 .append($('<div>').addClass('text').html(subResource.summary))
4037 .append($('<div>').addClass('util')
4038 .append($('<div>').addClass('g-plusone')
4039 .attr('data-size', 'small')
4040 .attr('data-align', 'right')
4041 .attr('data-href', resource.url)))))
4042 .appendTo($ul);
4043 }
4044 }
4045
4046 // Add a more row
4047 if (max < section.sections.length) {
4048 $('<li>')
4049 .append($('<a>').attr('href', resource.url)
4050 .append($('<div>')
4051 .addClass('title')
4052 .text('More')))
4053 .appendTo($ul);
4054 }
4055 } else {
4056 // No sub-resources, just render description?
4057 }
4058
4059 return this;
4060 };
smain@google.com95948b82014-06-16 19:24:25 -07004061
4062
4063
4064
Robert Lye7eeb402014-06-03 19:35:24 -07004065 /* Render other types of resource styles that are not cards. */
4066 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07004067 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004068 'assets/images/resource-card-default-android.jpg';
4069 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004070
Robert Lye7eeb402014-06-03 19:35:24 -07004071 if (imgUrl.indexOf('//') === -1) {
4072 imgUrl = toRoot + imgUrl;
4073 }
smain@google.com95948b82014-06-16 19:24:25 -07004074
Robert Lye7eeb402014-06-03 19:35:24 -07004075 if (linkUrl && linkUrl.indexOf('//') === -1) {
4076 linkUrl = toRoot + linkUrl;
4077 }
4078
4079 $(this).append(
4080 $('<div>').addClass('image')
4081 .css('background-image', 'url(' + imgUrl + ')'),
4082 $('<div>').addClass('info').append(
4083 $('<h4>').addClass('title').html(resource.title),
4084 $('<p>').addClass('summary').html(resource.summary),
4085 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4086 )
4087 );
4088
4089 return this;
4090 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004091})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004092
4093
Dirk Doughertyc3921652014-05-13 16:55:26 -07004094/* Calculate the vertical area remaining */
4095(function($) {
4096 $.fn.ellipsisfade= function(lineHeight) {
4097 this.each(function() {
4098 // get element text
4099 var $this = $(this);
4100 var remainingHeight = $this.parent().parent().height();
4101 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004102 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004103 if ($(this).is(":visible")) {
4104 var h = $(this).height();
4105 remainingHeight = remainingHeight - h;
4106 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004107 });
4108
4109 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4110 $this.parent().css({'height': adjustedRemainingHeight});
4111 $this.css({'height': "auto"});
4112 });
4113
4114 return this;
4115 };
4116}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004117
4118/*
4119 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004120
Robert Lye7eeb402014-06-03 19:35:24 -07004121 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004122 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004123 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004124
Robert Lye7eeb402014-06-03 19:35:24 -07004125 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004126
Robert Lye7eeb402014-06-03 19:35:24 -07004127 <div class="fullscreen-carousel">
4128 <div class="fullscreen-carousel-content">
4129 <!-- content here -->
4130 </div>
4131 <div class="fullscreen-carousel-content">
4132 <!-- content here -->
4133 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004134
Robert Lye7eeb402014-06-03 19:35:24 -07004135 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004136
Robert Lye7eeb402014-06-03 19:35:24 -07004137 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004138
Robert Lye7eeb402014-06-03 19:35:24 -07004139 Control over how the carousel takes over the screen can mostly be defined in
4140 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004141 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004142 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004143 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004144 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004145
Robert Lye7eeb402014-06-03 19:35:24 -07004146 There is limited functionality for having multiple sections since that request
4147 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4148 scroll between multiple content areas.
4149*/
4150
4151(function() {
4152 $(document).ready(function() {
4153 $('.fullscreen-carousel').each(function() {
4154 initWidget(this);
4155 });
4156 });
4157
4158 function initWidget(widget) {
4159 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004160
Robert Lye7eeb402014-06-03 19:35:24 -07004161 var topOffset = $widget.offset().top;
4162 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4163 var maxHeight = 0;
4164 var minHeight = 0;
4165 var $content = $widget.find('.fullscreen-carousel-content');
4166 var $nextArrow = $widget.find('.next-arrow');
4167 var $prevArrow = $widget.find('.prev-arrow');
4168 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004169
Robert Lye7eeb402014-06-03 19:35:24 -07004170 if ($content.length <= 1) {
4171 $nextArrow.hide();
4172 $prevArrow.hide();
4173 } else {
4174 $nextArrow.click(function() {
4175 var index = ($content.index($curSection) + 1);
4176 $curSection.hide();
4177 $curSection = $($content[index >= $content.length ? 0 : index]);
4178 $curSection.show();
4179 });
smain@google.com95948b82014-06-16 19:24:25 -07004180
Robert Lye7eeb402014-06-03 19:35:24 -07004181 $prevArrow.click(function() {
4182 var index = ($content.index($curSection) - 1);
4183 $curSection.hide();
4184 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4185 $curSection.show();
4186 });
4187 }
4188
4189 // Just hide all content sections except first.
4190 $content.each(function(index) {
4191 if ($(this).height() > minHeight) minHeight = $(this).height();
4192 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4193 });
4194
4195 // Register for changes to window size, and trigger.
4196 $(window).resize(resizeWidget);
4197 resizeWidget();
4198
4199 function resizeWidget() {
4200 var height = $(window).height() - topOffset - padBottom;
4201 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004202 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004203 (maxHeight && height > maxHeight ? maxHeight : height));
4204 }
smain@google.com95948b82014-06-16 19:24:25 -07004205 }
Robert Lye7eeb402014-06-03 19:35:24 -07004206})();
4207
4208
4209
4210
4211
4212/*
4213 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004214
Robert Lye7eeb402014-06-03 19:35:24 -07004215 The following allows tab widgets to be installed via the html below. Each
4216 tab content section should have a data-tab attribute matching one of the
4217 nav items'. Also each tab content section should have a width matching the
4218 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004219
Robert Lye7eeb402014-06-03 19:35:24 -07004220 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004221
Robert Lye7eeb402014-06-03 19:35:24 -07004222 <div class="tab-carousel">
4223 <ul class="tab-nav">
4224 <li><a href="#" data-tab="handsets">Handsets</a>
4225 <li><a href="#" data-tab="wearable">Wearable</a>
4226 <li><a href="#" data-tab="tv">TV</a>
4227 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004228
Robert Lye7eeb402014-06-03 19:35:24 -07004229 <div class="tab-carousel-content">
4230 <div data-tab="handsets">
4231 <!--Full width content here-->
4232 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004233
Robert Lye7eeb402014-06-03 19:35:24 -07004234 <div data-tab="wearable">
4235 <!--Full width content here-->
4236 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004237
Robert Lye7eeb402014-06-03 19:35:24 -07004238 <div data-tab="tv">
4239 <!--Full width content here-->
4240 </div>
4241 </div>
4242 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004243
Robert Lye7eeb402014-06-03 19:35:24 -07004244*/
4245(function() {
4246 $(document).ready(function() {
4247 $('.tab-carousel').each(function() {
4248 initWidget(this);
4249 });
4250 });
4251
4252 function initWidget(widget) {
4253 var $widget = $(widget);
4254 var $nav = $widget.find('.tab-nav');
4255 var $anchors = $nav.find('[data-tab]');
4256 var $li = $nav.find('li');
4257 var $contentContainer = $widget.find('.tab-carousel-content');
4258 var $tabs = $contentContainer.find('[data-tab]');
4259 var $curTab = $($tabs[0]); // Current tab is first tab.
4260 var width = $widget.width();
4261
4262 // Setup nav interactivity.
4263 $anchors.click(function(evt) {
4264 evt.preventDefault();
4265 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004266 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004267 });
smain@google.com95948b82014-06-16 19:24:25 -07004268
Robert Lye7eeb402014-06-03 19:35:24 -07004269 // Add highlight for navigation on first item.
4270 var $highlight = $('<div>').addClass('highlight')
4271 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4272 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004273
Robert Lye7eeb402014-06-03 19:35:24 -07004274 // Store height since we will change contents to absolute.
4275 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004276
Robert Lye7eeb402014-06-03 19:35:24 -07004277 // Absolutely position tabs so they're ready for transition.
4278 $tabs.each(function(index) {
4279 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4280 });
smain@google.com95948b82014-06-16 19:24:25 -07004281
Robert Lye7eeb402014-06-03 19:35:24 -07004282 function transitionWidget($toTab) {
4283 if (!$curTab.is($toTab)) {
4284 var curIndex = $tabs.index($curTab[0]);
4285 var toIndex = $tabs.index($toTab[0]);
4286 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004287
Robert Lye7eeb402014-06-03 19:35:24 -07004288 // Animate content sections.
4289 $toTab.css({left:(width * dir) + 'px'});
4290 $curTab.animate({left:(width * -dir) + 'px'});
4291 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004292
Robert Lye7eeb402014-06-03 19:35:24 -07004293 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004294 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004295 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004296
Robert Lye7eeb402014-06-03 19:35:24 -07004297 // Store new current section.
4298 $curTab = $toTab;
4299 }
4300 }
smain@google.com95948b82014-06-16 19:24:25 -07004301 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08004302})();