blob: 701870aa643e37690c2b2e0bd1aafa4416da9859 [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 */
563 var $videoLinks = $("a.video-shadowbox-button");
564 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 });
586
587 }
588
Scott Maine4d8f1b2012-06-21 18:03:05 -0700589});
Scott Main7e447ed2013-02-19 17:22:37 -0800590// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700591
592
smain@google.com698fff02014-11-20 20:39:33 -0800593var youTubePlayer;
594function onYouTubeIframeAPIReady() {
595}
596
597function startYouTubePlayer(videoId) {
598 if (youTubePlayer == null) {
599 youTubePlayer = new YT.Player('youTubePlayer', {
600 height: '529',
601 width: '940',
602 videoId: videoId,
603 events: {
604 'onReady': onPlayerReady
605 }
606 });
607 } else {
608 youTubePlayer.playVideo();
609 }
610 $("#video-container").fadeIn(200, function(){$("#video-frame").show()});
611}
612
613function onPlayerReady(event) {
614 event.target.playVideo();
615}
616
617function closeVideo() {
618 try {
619 youTubePlayer.stopVideo();
620 $("#video-container").fadeOut(200);
621 } catch(e) {
622 console.log('Video not available');
623 $("#video-container").fadeOut(200);
624 }
625}
626
627
628
Scott Mainad08f072013-08-20 16:49:57 -0700629function initExpandableNavItems(rootTag) {
630 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
631 var section = $(this).closest('li.nav-section');
632 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700633 /* hide me and descendants */
634 section.find('ul').slideUp(250, function() {
635 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700636 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700637 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700638 resizeNav();
639 });
640 } else {
641 /* show me */
642 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700643 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700644 $others.removeClass('expanded').children('ul').slideUp(250);
645
646 // now expand me
647 section.closest('li').addClass('expanded');
648 section.children('ul').slideDown(250, function() {
649 resizeNav();
650 });
651 }
652 });
Scott Mainf0093852013-08-22 11:37:11 -0700653
654 // Stop expand/collapse behavior when clicking on nav section links
655 // (since we're navigating away from the page)
656 // This selector captures the first instance of <a>, but not those with "#" as the href.
657 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
658 window.location.href = $(this).attr('href');
659 return false;
660 });
Scott Mainad08f072013-08-20 16:49:57 -0700661}
662
Dirk Doughertyc3921652014-05-13 16:55:26 -0700663
664/** Create the list of breadcrumb links in the sticky header */
665function buildBreadcrumbs() {
666 var $breadcrumbUl = $("#sticky-header ul.breadcrumb");
667 // Add the secondary horizontal nav item, if provided
668 var $selectedSecondNav = $("div#nav-x ul.nav-x a.selected").clone().removeClass("selected");
669 if ($selectedSecondNav.length) {
670 $breadcrumbUl.prepend($("<li>").append($selectedSecondNav))
671 }
672 // Add the primary horizontal nav
673 var $selectedFirstNav = $("div#header-wrap ul.nav-x a.selected").clone().removeClass("selected");
674 // If there's no header nav item, use the logo link and title from alt text
675 if ($selectedFirstNav.length < 1) {
676 $selectedFirstNav = $("<a>")
677 .attr('href', $("div#header .logo a").attr('href'))
678 .text($("div#header .logo img").attr('alt'));
679 }
680 $breadcrumbUl.prepend($("<li>").append($selectedFirstNav));
681}
682
683
684
Scott Maine624b3f2013-09-12 12:56:41 -0700685/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700686function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700687 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
688 if ($("ul#nav li.selected").length) {
689 unHighlightSidenav();
690 }
691 // look for URL in sidenav, including the hash
692 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
693
694 // If the selNavLink is still empty, look for it without the hash
695 if ($selNavLink.length == 0) {
696 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
697 }
698
Scott Mainf6145542013-04-01 16:38:11 -0700699 var $selListItem;
700 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700701 // Find this page's <li> in sidenav and set selected
702 $selListItem = $selNavLink.closest('li');
703 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700704
Scott Mainf6145542013-04-01 16:38:11 -0700705 // Traverse up the tree and expand all parent nav-sections
706 $selNavLink.parents('li.nav-section').each(function() {
707 $(this).addClass('expanded');
708 $(this).children('ul').show();
709 });
710 }
711}
712
Scott Maine624b3f2013-09-12 12:56:41 -0700713function unHighlightSidenav() {
714 $("ul#nav li.selected").removeClass("selected");
715 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
716}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700717
718function toggleFullscreen(enable) {
719 var delay = 20;
720 var enabled = true;
721 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
722 if (enable) {
723 // Currently NOT USING fullscreen; enable fullscreen
724 stylesheet.removeAttr('disabled');
725 $('#nav-swap .fullscreen').removeClass('disabled');
726 $('#devdoc-nav').css({left:''});
727 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
728 enabled = true;
729 } else {
730 // Currently USING fullscreen; disable fullscreen
731 stylesheet.attr('disabled', 'disabled');
732 $('#nav-swap .fullscreen').addClass('disabled');
733 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
734 enabled = false;
735 }
smain@google.com6bdcb982014-11-14 11:53:07 -0800736 writeCookie("fullscreen", enabled, null);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700737 setNavBarLeftPos();
738 resizeNav(delay);
739 updateSideNavPosition();
740 setTimeout(initSidenavHeightResize,delay);
741}
742
743
744function setNavBarLeftPos() {
745 navBarLeftPos = $('#body-content').offset().left;
746}
747
748
749function updateSideNavPosition() {
750 var newLeft = $(window).scrollLeft() - navBarLeftPos;
751 $('#devdoc-nav').css({left: -newLeft});
752 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
753}
Scott Main3b90aff2013-08-01 18:09:35 -0700754
Scott Maine4d8f1b2012-06-21 18:03:05 -0700755// TODO: use $(document).ready instead
756function addLoadEvent(newfun) {
757 var current = window.onload;
758 if (typeof window.onload != 'function') {
759 window.onload = newfun;
760 } else {
761 window.onload = function() {
762 current();
763 newfun();
764 }
765 }
766}
767
768var agent = navigator['userAgent'].toLowerCase();
769// If a mobile phone, set flag and do mobile setup
770if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
771 (agent.indexOf("blackberry") != -1) ||
772 (agent.indexOf("webos") != -1) ||
773 (agent.indexOf("mini") != -1)) { // opera mini browsers
774 isMobile = true;
775}
776
777
Scott Main498d7102013-08-21 15:47:38 -0700778$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700779 $("pre:not(.no-pretty-print)").addClass("prettyprint");
780 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700781});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700782
Scott Maine4d8f1b2012-06-21 18:03:05 -0700783
784
785
786/* ######### RESIZE THE SIDENAV HEIGHT ########## */
787
788function resizeNav(delay) {
789 var $nav = $("#devdoc-nav");
790 var $window = $(window);
791 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700792
Scott Maine4d8f1b2012-06-21 18:03:05 -0700793 // Get the height of entire window and the total header height.
794 // Then figure out based on scroll position whether the header is visible
795 var windowHeight = $window.height();
796 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700797 var headerHeight = $('#header-wrapper').outerHeight();
798 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700799
800 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700801 // Could be either margin or top position, depending on whether the nav is fixed.
Scott Main3b90aff2013-08-01 18:09:35 -0700802 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700803 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700804
Scott Maine4d8f1b2012-06-21 18:03:05 -0700805 // Depending on whether the header is visible, set the side nav's height.
806 if (headerVisible) {
807 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700808 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700809 } else {
810 // Once header is off screen, the nav height is almost full window height
811 navHeight = windowHeight - topMargin;
812 }
Scott Main3b90aff2013-08-01 18:09:35 -0700813
814
815
Scott Maine4d8f1b2012-06-21 18:03:05 -0700816 $scrollPanes = $(".scroll-pane");
817 if ($scrollPanes.length > 1) {
818 // subtract the height of the api level widget and nav swapper from the available nav height
819 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700820
Scott Maine4d8f1b2012-06-21 18:03:05 -0700821 $("#swapper").css({height:navHeight + "px"});
822 if ($("#nav-tree").is(":visible")) {
823 $("#nav-tree").css({height:navHeight});
824 }
Scott Main3b90aff2013-08-01 18:09:35 -0700825
826 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700827 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700828
829 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700830 // then the package panel should begin to shrink
831 if (parseInt(classesHeight) <= 0) {
832 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
833 $("#packages-nav").css({height:navHeight - 10});
834 }
Scott Main3b90aff2013-08-01 18:09:35 -0700835
Scott Maine4d8f1b2012-06-21 18:03:05 -0700836 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
837 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700838
839
Scott Maine4d8f1b2012-06-21 18:03:05 -0700840 } else {
841 $nav.height(navHeight);
842 }
Scott Main3b90aff2013-08-01 18:09:35 -0700843
Scott Maine4d8f1b2012-06-21 18:03:05 -0700844 if (delay) {
845 updateFromResize = true;
846 delayedReInitScrollbars(delay);
847 } else {
848 reInitScrollbars();
849 }
Scott Main3b90aff2013-08-01 18:09:35 -0700850
Scott Maine4d8f1b2012-06-21 18:03:05 -0700851}
852
853var updateScrollbars = false;
854var updateFromResize = false;
855
856/* Re-initialize the scrollbars to account for changed nav size.
857 * This method postpones the actual update by a 1/4 second in order to optimize the
858 * scroll performance while the header is still visible, because re-initializing the
859 * scroll panes is an intensive process.
860 */
861function delayedReInitScrollbars(delay) {
862 // If we're scheduled for an update, but have received another resize request
863 // before the scheduled resize has occured, just ignore the new request
864 // (and wait for the scheduled one).
865 if (updateScrollbars && updateFromResize) {
866 updateFromResize = false;
867 return;
868 }
Scott Main3b90aff2013-08-01 18:09:35 -0700869
Scott Maine4d8f1b2012-06-21 18:03:05 -0700870 // We're scheduled for an update and the update request came from this method's setTimeout
871 if (updateScrollbars && !updateFromResize) {
872 reInitScrollbars();
873 updateScrollbars = false;
874 } else {
875 updateScrollbars = true;
876 updateFromResize = false;
877 setTimeout('delayedReInitScrollbars()',delay);
878 }
879}
880
881/* Re-initialize the scrollbars to account for changed nav size. */
882function reInitScrollbars() {
883 var pane = $(".scroll-pane").each(function(){
884 var api = $(this).data('jsp');
885 if (!api) { setTimeout(reInitScrollbars,300); return;}
886 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700887 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700888 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
889}
890
891
892/* Resize the height of the nav panels in the reference,
893 * and save the new size to a cookie */
894function saveNavPanels() {
895 var basePath = getBaseUri(location.pathname);
896 var section = basePath.substring(1,basePath.indexOf("/",1));
smain@google.com6bdcb982014-11-14 11:53:07 -0800897 writeCookie("height", resizePackagesNav.css("height"), section);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700898}
899
900
901
902function restoreHeight(packageHeight) {
903 $("#resize-packages-nav").height(packageHeight);
904 $("#packages-nav").height(packageHeight);
905 // var classesHeight = navHeight - packageHeight;
906 // $("#classes-nav").css({height:classesHeight});
907 // $("#classes-nav .jspContainer").css({height:classesHeight});
908}
909
910
911
912/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
913
914
915
916
917
Scott Main3b90aff2013-08-01 18:09:35 -0700918/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700919 This is called when the page finished loading. */
920function scrollIntoView(nav) {
921 var $nav = $("#"+nav);
922 var element = $nav.jScrollPane({/* ...settings... */});
923 var api = element.data('jsp');
924
925 if ($nav.is(':visible')) {
926 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700927 if ($selected.length == 0) {
928 // If no selected item found, exit
929 return;
930 }
Scott Main52dd2062013-08-15 12:22:28 -0700931 // get the selected item's offset from its container nav by measuring the item's offset
932 // relative to the document then subtract the container nav's offset relative to the document
933 var selectedOffset = $selected.offset().top - $nav.offset().top;
934 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
935 // if it's more than 80% down the nav
936 // scroll the item up by an amount equal to 80% the container nav's height
937 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700938 }
939 }
940}
941
942
943
944
945
946
947/* Show popup dialogs */
948function showDialog(id) {
949 $dialog = $("#"+id);
950 $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>');
951 $dialog.wrapInner('<div/>');
952 $dialog.removeClass("hide");
953}
954
955
956
957
958
959/* ######### COOKIES! ########## */
960
961function readCookie(cookie) {
962 var myCookie = cookie_namespace+"_"+cookie+"=";
963 if (document.cookie) {
964 var index = document.cookie.indexOf(myCookie);
965 if (index != -1) {
966 var valStart = index + myCookie.length;
967 var valEnd = document.cookie.indexOf(";", valStart);
968 if (valEnd == -1) {
969 valEnd = document.cookie.length;
970 }
971 var val = document.cookie.substring(valStart, valEnd);
972 return val;
973 }
974 }
975 return 0;
976}
977
smain@google.com6bdcb982014-11-14 11:53:07 -0800978function writeCookie(cookie, val, section) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700979 if (val==undefined) return;
980 section = section == null ? "_" : "_"+section+"_";
smain@google.com6bdcb982014-11-14 11:53:07 -0800981 var age = 2*365*24*60*60; // set max-age to 2 years
Scott Main3b90aff2013-08-01 18:09:35 -0700982 var cookieValue = cookie_namespace + section + cookie + "=" + val
smain@google.com80e38f42014-11-03 10:47:12 -0800983 + "; max-age=" + age +"; path=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700984 document.cookie = cookieValue;
985}
986
987/* ######### END COOKIES! ########## */
988
989
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700990var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -0700991var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700992var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -0700993/* Sets the vertical scoll position at which the sticky bar should appear.
994 This method is called to reset the position when search results appear or hide */
995function setStickyTop() {
996 stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight();
997}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700998
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700999/*
Scott Mainb16376f2014-05-21 20:35:47 -07001000 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -07001001 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001002$(window).scroll(function(event) {
1003
1004 setStickyTop();
1005 var hiding = false;
1006 var $stickyEl = $('#sticky-header');
1007 var $menuEl = $('.menu-container');
1008 // Exit if there's no sidenav
1009 if ($('#side-nav').length == 0) return;
1010 // Exit if the mouse target is a DIV, because that means the event is coming
1011 // from a scrollable div and so there's no need to make adjustments to our layout
1012 if ($(event.target).nodeName == "DIV") {
1013 return;
1014 }
1015
1016 var top = $(window).scrollTop();
1017 // we set the navbar fixed when the scroll position is beyond the height of the site header...
1018 var shouldBeSticky = top >= stickyTop;
1019 // ... except if the document content is shorter than the sidenav height.
1020 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
1021 if ($("#doc-col").height() < $("#side-nav").height()) {
1022 shouldBeSticky = false;
1023 }
Scott Mainf5257812014-05-22 17:26:38 -07001024 // Account for horizontal scroll
1025 var scrollLeft = $(window).scrollLeft();
1026 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
1027 if (sticky && (scrollLeft != prevScrollLeft)) {
1028 updateSideNavPosition();
1029 prevScrollLeft = scrollLeft;
1030 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001031
1032 // Don't continue if the header is sufficently far away
1033 // (to avoid intensive resizing that slows scrolling)
1034 if (sticky == shouldBeSticky) {
1035 return;
1036 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001037
1038 // If sticky header visible and position is now near top, hide sticky
1039 if (sticky && !shouldBeSticky) {
1040 sticky = false;
1041 hiding = true;
1042 // make the sidenav static again
1043 $('#devdoc-nav')
1044 .removeClass('fixed')
1045 .css({'width':'auto','margin':''})
1046 .prependTo('#side-nav');
1047 // delay hide the sticky
1048 $menuEl.removeClass('sticky-menu');
1049 $stickyEl.fadeOut(250);
1050 hiding = false;
1051
1052 // update the sidenaav position for side scrolling
1053 updateSideNavPosition();
1054 } else if (!sticky && shouldBeSticky) {
1055 sticky = true;
1056 $stickyEl.fadeIn(10);
1057 $menuEl.addClass('sticky-menu');
1058
1059 // make the sidenav fixed
1060 var width = $('#devdoc-nav').width();
1061 $('#devdoc-nav')
1062 .addClass('fixed')
1063 .css({'width':width+'px'})
1064 .prependTo('#body-content');
1065
1066 // update the sidenaav position for side scrolling
1067 updateSideNavPosition();
1068
1069 } else if (hiding && top < 15) {
1070 $menuEl.removeClass('sticky-menu');
1071 $stickyEl.hide();
1072 hiding = false;
1073 }
1074 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
1075});
1076
1077/*
1078 * Manages secion card states and nav resize to conclude loading
1079 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001080(function() {
1081 $(document).ready(function() {
1082
Dirk Doughertyc3921652014-05-13 16:55:26 -07001083 // Stack hover states
1084 $('.section-card-menu').each(function(index, el) {
1085 var height = $(el).height();
1086 $(el).css({height:height+'px', position:'relative'});
1087 var $cardInfo = $(el).find('.card-info');
1088
1089 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1090 });
1091
Dirk Doughertyc3921652014-05-13 16:55:26 -07001092 });
1093
1094})();
1095
Scott Maine4d8f1b2012-06-21 18:03:05 -07001096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
Scott Maind7026f72013-06-17 15:08:49 -07001109/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001110
1111
1112
1113
1114
1115function toggle(obj, slide) {
1116 var ul = $("ul:first", obj);
1117 var li = ul.parent();
1118 if (li.hasClass("closed")) {
1119 if (slide) {
1120 ul.slideDown("fast");
1121 } else {
1122 ul.show();
1123 }
1124 li.removeClass("closed");
1125 li.addClass("open");
1126 $(".toggle-img", li).attr("title", "hide pages");
1127 } else {
1128 ul.slideUp("fast");
1129 li.removeClass("open");
1130 li.addClass("closed");
1131 $(".toggle-img", li).attr("title", "show pages");
1132 }
1133}
1134
1135
Scott Maine4d8f1b2012-06-21 18:03:05 -07001136function buildToggleLists() {
1137 $(".toggle-list").each(
1138 function(i) {
1139 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1140 $(this).addClass("closed");
1141 });
1142}
1143
1144
1145
Scott Maind7026f72013-06-17 15:08:49 -07001146function hideNestedItems(list, toggle) {
1147 $list = $(list);
1148 // hide nested lists
1149 if($list.hasClass('showing')) {
1150 $("li ol", $list).hide('fast');
1151 $list.removeClass('showing');
1152 // show nested lists
1153 } else {
1154 $("li ol", $list).show('fast');
1155 $list.addClass('showing');
1156 }
1157 $(".more,.less",$(toggle)).toggle();
1158}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001159
1160
smain@google.com95948b82014-06-16 19:24:25 -07001161/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1162function setupIdeDocToggle() {
1163 $( "select.ide" ).change(function() {
1164 var selected = $(this).find("option:selected").attr("value");
1165 $(".select-ide").hide();
1166 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001167
smain@google.com95948b82014-06-16 19:24:25 -07001168 $("select.ide").val(selected);
1169 });
1170}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195/* REFERENCE NAV SWAP */
1196
1197
1198function getNavPref() {
1199 var v = readCookie('reference_nav');
1200 if (v != NAV_PREF_TREE) {
1201 v = NAV_PREF_PANELS;
1202 }
1203 return v;
1204}
1205
1206function chooseDefaultNav() {
1207 nav_pref = getNavPref();
1208 if (nav_pref == NAV_PREF_TREE) {
1209 $("#nav-panels").toggle();
1210 $("#panel-link").toggle();
1211 $("#nav-tree").toggle();
1212 $("#tree-link").toggle();
1213 }
1214}
1215
1216function swapNav() {
1217 if (nav_pref == NAV_PREF_TREE) {
1218 nav_pref = NAV_PREF_PANELS;
1219 } else {
1220 nav_pref = NAV_PREF_TREE;
1221 init_default_navtree(toRoot);
1222 }
smain@google.com6bdcb982014-11-14 11:53:07 -08001223 writeCookie("nav", nav_pref, "reference");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001224
1225 $("#nav-panels").toggle();
1226 $("#panel-link").toggle();
1227 $("#nav-tree").toggle();
1228 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001229
Scott Maine4d8f1b2012-06-21 18:03:05 -07001230 resizeNav();
1231
1232 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1233 $("#nav-tree .jspContainer:visible")
1234 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1235 // Another nasty hack to make the scrollbar appear now that we have height
1236 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001237
Scott Maine4d8f1b2012-06-21 18:03:05 -07001238 if ($("#nav-tree").is(':visible')) {
1239 scrollIntoView("nav-tree");
1240 } else {
1241 scrollIntoView("packages-nav");
1242 scrollIntoView("classes-nav");
1243 }
1244}
1245
1246
1247
Scott Mainf5089842012-08-14 16:31:07 -07001248/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001249/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001250/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001251
1252function getBaseUri(uri) {
1253 var intlUrl = (uri.substring(0,6) == "/intl/");
1254 if (intlUrl) {
1255 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1256 base = base.substring(base.indexOf('/')+1, base.length);
1257 //alert("intl, returning base url: /" + base);
1258 return ("/" + base);
1259 } else {
1260 //alert("not intl, returning uri as found.");
1261 return uri;
1262 }
1263}
1264
1265function requestAppendHL(uri) {
1266//append "?hl=<lang> to an outgoing request (such as to blog)
1267 var lang = getLangPref();
1268 if (lang) {
1269 var q = 'hl=' + lang;
1270 uri += '?' + q;
1271 window.location = uri;
1272 return false;
1273 } else {
1274 return true;
1275 }
1276}
1277
1278
Scott Maine4d8f1b2012-06-21 18:03:05 -07001279function changeNavLang(lang) {
Scott Main6eb95f12012-10-02 17:12:23 -07001280 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1281 $links.each(function(i){ // for each link with a translation
1282 var $link = $(this);
1283 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1284 // put the desired language from the attribute as the text
1285 $link.text($link.attr(lang+"-lang"))
Scott Maine4d8f1b2012-06-21 18:03:05 -07001286 }
Scott Main6eb95f12012-10-02 17:12:23 -07001287 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001288}
1289
Scott Main015d6162013-01-29 09:01:52 -08001290function changeLangPref(lang, submit) {
smain@google.com6bdcb982014-11-14 11:53:07 -08001291 writeCookie("pref_lang", lang, null);
Scott Main015d6162013-01-29 09:01:52 -08001292
1293 // ####### TODO: Remove this condition once we're stable on devsite #######
1294 // This condition is only needed if we still need to support legacy GAE server
1295 if (devsite) {
1296 // Switch language when on Devsite server
1297 if (submit) {
1298 $("#setlang").submit();
1299 }
1300 } else {
1301 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001302 if (submit) {
1303 window.location = getBaseUri(location.pathname);
1304 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001305 }
1306}
1307
1308function loadLangPref() {
1309 var lang = readCookie("pref_lang");
1310 if (lang != 0) {
1311 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1312 }
1313}
1314
1315function getLangPref() {
1316 var lang = $("#language").find(":selected").attr("value");
1317 if (!lang) {
1318 lang = readCookie("pref_lang");
1319 }
1320 return (lang != 0) ? lang : 'en';
1321}
1322
1323/* ########## END LOCALIZATION ############ */
1324
1325
1326
1327
1328
1329
1330/* Used to hide and reveal supplemental content, such as long code samples.
1331 See the companion CSS in android-developer-docs.css */
1332function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001333 var div = $(obj).closest(".toggle-content");
1334 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001335 if (div.hasClass("closed")) { // if it's closed, open it
1336 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001337 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001338 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001339 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001340 + "assets/images/triangle-opened.png");
1341 } else { // if it's open, close it
1342 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001343 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001344 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001345 div.find(".toggle-content").removeClass("open").addClass("closed")
1346 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001347 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001348 + "assets/images/triangle-closed.png");
1349 });
1350 }
1351 return false;
1352}
Scott Mainf5089842012-08-14 16:31:07 -07001353
1354
Scott Maindb3678b2012-10-23 14:13:41 -07001355/* New version of expandable content */
1356function toggleExpandable(link,id) {
1357 if($(id).is(':visible')) {
1358 $(id).slideUp();
1359 $(link).removeClass('expanded');
1360 } else {
1361 $(id).slideDown();
1362 $(link).addClass('expanded');
1363 }
1364}
1365
1366function hideExpandable(ids) {
1367 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001368 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001369}
1370
Scott Mainf5089842012-08-14 16:31:07 -07001371
1372
1373
1374
Scott Main3b90aff2013-08-01 18:09:35 -07001375/*
Scott Mainf5089842012-08-14 16:31:07 -07001376 * Slideshow 1.0
1377 * Used on /index.html and /develop/index.html for carousel
1378 *
1379 * Sample usage:
1380 * HTML -
1381 * <div class="slideshow-container">
1382 * <a href="" class="slideshow-prev">Prev</a>
1383 * <a href="" class="slideshow-next">Next</a>
1384 * <ul>
1385 * <li class="item"><img src="images/marquee1.jpg"></li>
1386 * <li class="item"><img src="images/marquee2.jpg"></li>
1387 * <li class="item"><img src="images/marquee3.jpg"></li>
1388 * <li class="item"><img src="images/marquee4.jpg"></li>
1389 * </ul>
1390 * </div>
1391 *
1392 * <script type="text/javascript">
1393 * $('.slideshow-container').dacSlideshow({
1394 * auto: true,
1395 * btnPrev: '.slideshow-prev',
1396 * btnNext: '.slideshow-next'
1397 * });
1398 * </script>
1399 *
1400 * Options:
1401 * btnPrev: optional identifier for previous button
1402 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001403 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001404 * auto: whether or not to auto-proceed
1405 * speed: animation speed
1406 * autoTime: time between auto-rotation
1407 * easing: easing function for transition
1408 * start: item to select by default
1409 * scroll: direction to scroll in
1410 * pagination: whether or not to include dotted pagination
1411 *
1412 */
1413
1414 (function($) {
1415 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001416
Scott Mainf5089842012-08-14 16:31:07 -07001417 //Options - see above
1418 o = $.extend({
1419 btnPrev: null,
1420 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001421 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001422 auto: true,
1423 speed: 500,
1424 autoTime: 12000,
1425 easing: null,
1426 start: 0,
1427 scroll: 1,
1428 pagination: true
1429
1430 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001431
1432 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001433 return this.each(function() {
1434
1435 var running = false;
1436 var animCss = o.vertical ? "top" : "left";
1437 var sizeCss = o.vertical ? "height" : "width";
1438 var div = $(this);
1439 var ul = $("ul", div);
1440 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001441 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001442 var timer = null;
1443
1444 var li = $("li", ul);
1445 var itemLength = li.size();
1446 var curr = o.start;
1447
1448 li.css({float: o.vertical ? "none" : "left"});
1449 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1450 div.css({position: "relative", "z-index": "2", left: "0px"});
1451
1452 var liSize = o.vertical ? height(li) : width(li);
1453 var ulSize = liSize * itemLength;
1454 var divSize = liSize;
1455
1456 li.css({width: li.width(), height: li.height()});
1457 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1458
1459 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001460
Scott Mainf5089842012-08-14 16:31:07 -07001461 //Pagination
1462 if (o.pagination) {
1463 var pagination = $("<div class='pagination'></div>");
1464 var pag_ul = $("<ul></ul>");
1465 if (tl > 1) {
1466 for (var i=0;i<tl;i++) {
1467 var li = $("<li>"+i+"</li>");
1468 pag_ul.append(li);
1469 if (i==o.start) li.addClass('active');
1470 li.click(function() {
1471 go(parseInt($(this).text()));
1472 })
1473 }
1474 pagination.append(pag_ul);
1475 div.append(pagination);
1476 }
1477 }
Scott Main3b90aff2013-08-01 18:09:35 -07001478
Scott Mainf5089842012-08-14 16:31:07 -07001479 //Previous button
1480 if(o.btnPrev)
1481 $(o.btnPrev).click(function(e) {
1482 e.preventDefault();
1483 return go(curr-o.scroll);
1484 });
1485
1486 //Next button
1487 if(o.btnNext)
1488 $(o.btnNext).click(function(e) {
1489 e.preventDefault();
1490 return go(curr+o.scroll);
1491 });
Scott Maineb410352013-01-14 19:03:40 -08001492
1493 //Pause button
1494 if(o.btnPause)
1495 $(o.btnPause).click(function(e) {
1496 e.preventDefault();
1497 if ($(this).hasClass('paused')) {
1498 startRotateTimer();
1499 } else {
1500 pauseRotateTimer();
1501 }
1502 });
Scott Main3b90aff2013-08-01 18:09:35 -07001503
Scott Mainf5089842012-08-14 16:31:07 -07001504 //Auto rotation
1505 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001506
Scott Mainf5089842012-08-14 16:31:07 -07001507 function startRotateTimer() {
1508 clearInterval(timer);
1509 timer = setInterval(function() {
1510 if (curr == tl-1) {
1511 go(0);
1512 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001513 go(curr+o.scroll);
1514 }
Scott Mainf5089842012-08-14 16:31:07 -07001515 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001516 $(o.btnPause).removeClass('paused');
1517 }
1518
1519 function pauseRotateTimer() {
1520 clearInterval(timer);
1521 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001522 }
1523
1524 //Go to an item
1525 function go(to) {
1526 if(!running) {
1527
1528 if(to<0) {
1529 to = itemLength-1;
1530 } else if (to>itemLength-1) {
1531 to = 0;
1532 }
1533 curr = to;
1534
1535 running = true;
1536
1537 ul.animate(
1538 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1539 function() {
1540 running = false;
1541 }
1542 );
1543
1544 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1545 $( (curr-o.scroll<0 && o.btnPrev)
1546 ||
1547 (curr+o.scroll > itemLength && o.btnNext)
1548 ||
1549 []
1550 ).addClass("disabled");
1551
Scott Main3b90aff2013-08-01 18:09:35 -07001552
Scott Mainf5089842012-08-14 16:31:07 -07001553 var nav_items = $('li', pagination);
1554 nav_items.removeClass('active');
1555 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001556
Scott Mainf5089842012-08-14 16:31:07 -07001557
1558 }
1559 if(o.auto) startRotateTimer();
1560 return false;
1561 };
1562 });
1563 };
1564
1565 function css(el, prop) {
1566 return parseInt($.css(el[0], prop)) || 0;
1567 };
1568 function width(el) {
1569 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1570 };
1571 function height(el) {
1572 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1573 };
1574
1575 })(jQuery);
1576
1577
Scott Main3b90aff2013-08-01 18:09:35 -07001578/*
Scott Mainf5089842012-08-14 16:31:07 -07001579 * dacSlideshow 1.0
1580 * Used on develop/index.html for side-sliding tabs
1581 *
1582 * Sample usage:
1583 * HTML -
1584 * <div class="slideshow-container">
1585 * <a href="" class="slideshow-prev">Prev</a>
1586 * <a href="" class="slideshow-next">Next</a>
1587 * <ul>
1588 * <li class="item"><img src="images/marquee1.jpg"></li>
1589 * <li class="item"><img src="images/marquee2.jpg"></li>
1590 * <li class="item"><img src="images/marquee3.jpg"></li>
1591 * <li class="item"><img src="images/marquee4.jpg"></li>
1592 * </ul>
1593 * </div>
1594 *
1595 * <script type="text/javascript">
1596 * $('.slideshow-container').dacSlideshow({
1597 * auto: true,
1598 * btnPrev: '.slideshow-prev',
1599 * btnNext: '.slideshow-next'
1600 * });
1601 * </script>
1602 *
1603 * Options:
1604 * btnPrev: optional identifier for previous button
1605 * btnNext: optional identifier for next button
1606 * auto: whether or not to auto-proceed
1607 * speed: animation speed
1608 * autoTime: time between auto-rotation
1609 * easing: easing function for transition
1610 * start: item to select by default
1611 * scroll: direction to scroll in
1612 * pagination: whether or not to include dotted pagination
1613 *
1614 */
1615 (function($) {
1616 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001617
Scott Mainf5089842012-08-14 16:31:07 -07001618 //Options - see above
1619 o = $.extend({
1620 speed : 250,
1621 easing: null,
1622 nav_id: null,
1623 frame_id: null
1624 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001625
1626 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001627 return this.each(function() {
1628
1629 var curr = 0;
1630 var running = false;
1631 var animCss = "margin-left";
1632 var sizeCss = "width";
1633 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001634
Scott Mainf5089842012-08-14 16:31:07 -07001635 var nav = $(o.nav_id, div);
1636 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001637 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001638 var frame = div.find(o.frame_id);
1639 var content_width = $(frame).find('ul').width();
1640 //Buttons
1641 $(nav_li).click(function(e) {
1642 go($(nav_li).index($(this)));
1643 })
Scott Main3b90aff2013-08-01 18:09:35 -07001644
Scott Mainf5089842012-08-14 16:31:07 -07001645 //Go to an item
1646 function go(to) {
1647 if(!running) {
1648 curr = to;
1649 running = true;
1650
1651 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1652 function() {
1653 running = false;
1654 }
1655 );
1656
Scott Main3b90aff2013-08-01 18:09:35 -07001657
Scott Mainf5089842012-08-14 16:31:07 -07001658 nav_li.removeClass('active');
1659 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001660
Scott Mainf5089842012-08-14 16:31:07 -07001661
1662 }
1663 return false;
1664 };
1665 });
1666 };
1667
1668 function css(el, prop) {
1669 return parseInt($.css(el[0], prop)) || 0;
1670 };
1671 function width(el) {
1672 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1673 };
1674 function height(el) {
1675 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1676 };
1677
1678 })(jQuery);
1679
1680
1681
1682
1683
1684/* ######################################################## */
1685/* ################ SEARCH SUGGESTIONS ################## */
1686/* ######################################################## */
1687
1688
Scott Main7e447ed2013-02-19 17:22:37 -08001689
Scott Main0e76e7e2013-03-12 10:24:07 -07001690var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1691var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1692
Scott Mainf5089842012-08-14 16:31:07 -07001693var gMatches = new Array();
1694var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001695var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001696var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1697var gListLength = 0;
1698
1699
1700var gGoogleMatches = new Array();
1701var ROW_COUNT_GOOGLE = 15; // max number of results in list
1702var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001703
Scott Main0e76e7e2013-03-12 10:24:07 -07001704var gDocsMatches = new Array();
1705var ROW_COUNT_DOCS = 100; // max number of results in list
1706var gDocsListLength = 0;
1707
Scott Mainde295272013-03-25 15:48:35 -07001708function onSuggestionClick(link) {
1709 // When user clicks a suggested document, track it
smain@google.com633f3222014-10-03 15:49:45 -07001710 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).text(),
1711 'from: ' + $("#search_autocomplete").val());
Scott Mainde295272013-03-25 15:48:35 -07001712}
1713
Scott Mainf5089842012-08-14 16:31:07 -07001714function set_item_selected($li, selected)
1715{
1716 if (selected) {
1717 $li.attr('class','jd-autocomplete jd-selected');
1718 } else {
1719 $li.attr('class','jd-autocomplete');
1720 }
1721}
1722
1723function set_item_values(toroot, $li, match)
1724{
1725 var $link = $('a',$li);
1726 $link.html(match.__hilabel || match.label);
1727 $link.attr('href',toroot + match.link);
1728}
1729
Scott Main719acb42013-12-05 16:05:09 -08001730function set_item_values_jd(toroot, $li, match)
1731{
1732 var $link = $('a',$li);
1733 $link.html(match.title);
1734 $link.attr('href',toroot + match.url);
1735}
1736
Scott Main0e76e7e2013-03-12 10:24:07 -07001737function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001738 var $li = $("<li class='jd-autocomplete'></li>");
1739 $list.append($li);
1740
1741 $li.mousedown(function() {
1742 window.location = this.firstChild.getAttribute("href");
1743 });
1744 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001745 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001746 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001747 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1748 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001749 });
Scott Mainde295272013-03-25 15:48:35 -07001750 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001751 $li.attr('class','show-item');
1752 return $li;
1753}
1754
Scott Mainf5089842012-08-14 16:31:07 -07001755function sync_selection_table(toroot)
1756{
Scott Mainf5089842012-08-14 16:31:07 -07001757 var $li; //list item jquery object
1758 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001759
Scott Main0e76e7e2013-03-12 10:24:07 -07001760 // if there are NO results at all, hide all columns
1761 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1762 $('.suggest-card').hide(300);
1763 return;
1764 }
1765
1766 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001767 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001768 // reveal suggestion list
1769 $('.suggest-card.dummy').show();
1770 $('.suggest-card.reference').show();
1771 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001772
Scott Main0e76e7e2013-03-12 10:24:07 -07001773 // reset the lists
1774 $(".search_filtered_wrapper.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001775
Scott Main0e76e7e2013-03-12 10:24:07 -07001776 // ########### ANDROID RESULTS #############
1777 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001778
Scott Main0e76e7e2013-03-12 10:24:07 -07001779 // determine android results to show
1780 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1781 gMatches.length : ROW_COUNT_FRAMEWORK;
1782 for (i=0; i<gListLength; i++) {
1783 var $li = new_suggestion($(".suggest-card.reference ul"));
1784 set_item_values(toroot, $li, gMatches[i]);
1785 set_item_selected($li, i == gSelectedIndex);
1786 }
1787 }
Scott Main7e447ed2013-02-19 17:22:37 -08001788
Scott Main0e76e7e2013-03-12 10:24:07 -07001789 // ########### GOOGLE RESULTS #############
1790 if (gGoogleMatches.length > 0) {
1791 // show header for list
1792 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001793
Scott Main0e76e7e2013-03-12 10:24:07 -07001794 // determine google results to show
1795 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1796 for (i=0; i<gGoogleListLength; i++) {
1797 var $li = new_suggestion($(".suggest-card.reference ul"));
1798 set_item_values(toroot, $li, gGoogleMatches[i]);
1799 set_item_selected($li, i == gSelectedIndex);
1800 }
1801 }
Scott Mainf5089842012-08-14 16:31:07 -07001802 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001803 $('.suggest-card.reference').hide();
1804 $('.suggest-card.dummy').hide();
1805 }
1806
1807 // ########### JD DOC RESULTS #############
1808 if (gDocsMatches.length > 0) {
1809 // reset the lists
1810 $(".search_filtered_wrapper.docs li").remove();
1811
1812 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001813 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1814 // The order must match the reverse order that each section appears as a card in
1815 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001816 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1817 for (i=0; i<gDocsListLength; i++) {
1818 var sugg = gDocsMatches[i];
1819 var $li;
1820 if (sugg.type == "design") {
1821 $li = new_suggestion($(".suggest-card.design ul"));
1822 } else
1823 if (sugg.type == "distribute") {
1824 $li = new_suggestion($(".suggest-card.distribute ul"));
1825 } else
Scott Main719acb42013-12-05 16:05:09 -08001826 if (sugg.type == "samples") {
1827 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1828 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001829 if (sugg.type == "training") {
1830 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1831 } else
Scott Main719acb42013-12-05 16:05:09 -08001832 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001833 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1834 } else {
1835 continue;
1836 }
1837
Scott Main719acb42013-12-05 16:05:09 -08001838 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001839 set_item_selected($li, i == gSelectedIndex);
1840 }
1841
1842 // add heading and show or hide card
1843 if ($(".suggest-card.design li").length > 0) {
1844 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1845 $(".suggest-card.design").show(300);
1846 } else {
1847 $('.suggest-card.design').hide(300);
1848 }
1849 if ($(".suggest-card.distribute li").length > 0) {
1850 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1851 $(".suggest-card.distribute").show(300);
1852 } else {
1853 $('.suggest-card.distribute').hide(300);
1854 }
1855 if ($(".child-card.guides li").length > 0) {
1856 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1857 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1858 }
1859 if ($(".child-card.training li").length > 0) {
1860 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1861 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1862 }
Scott Main719acb42013-12-05 16:05:09 -08001863 if ($(".child-card.samples li").length > 0) {
1864 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1865 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1866 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001867
1868 if ($(".suggest-card.develop li").length > 0) {
1869 $(".suggest-card.develop").show(300);
1870 } else {
1871 $('.suggest-card.develop').hide(300);
1872 }
1873
1874 } else {
1875 $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001876 }
1877}
1878
Scott Main0e76e7e2013-03-12 10:24:07 -07001879/** Called by the search input's onkeydown and onkeyup events.
1880 * Handles navigation with keyboard arrows, Enter key to invoke search,
1881 * otherwise invokes search suggestions on key-up event.
1882 * @param e The JS event
1883 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001884 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001885 * @returns True if the event should bubble up
1886 */
Scott Mainf5089842012-08-14 16:31:07 -07001887function search_changed(e, kd, toroot)
1888{
Scott Main719acb42013-12-05 16:05:09 -08001889 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001890 var search = document.getElementById("search_autocomplete");
1891 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001892 // get the ul hosting the currently selected item
1893 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1894 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1895 var $selectedUl = $columns[gSelectedColumn];
1896
Scott Mainf5089842012-08-14 16:31:07 -07001897 // show/hide the close button
1898 if (text != '') {
1899 $(".search .close").removeClass("hide");
1900 } else {
1901 $(".search .close").addClass("hide");
1902 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001903 // 27 = esc
1904 if (e.keyCode == 27) {
1905 // close all search results
1906 if (kd) $('.search .close').trigger('click');
1907 return true;
1908 }
Scott Mainf5089842012-08-14 16:31:07 -07001909 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001910 else if (e.keyCode == 13) {
1911 if (gSelectedIndex < 0) {
1912 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001913 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1914 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001915 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001916 return true;
1917 } else {
1918 // otherwise, results are already showing, so allow ajax to auto refresh the results
1919 // and ignore this Enter press to avoid the reload.
1920 return false;
1921 }
1922 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001923 // click the link corresponding to selected item
1924 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001925 return false;
1926 }
1927 }
Scott Mainb16376f2014-05-21 20:35:47 -07001928 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001929 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001930 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001931 if ((sticky ) && (search.value != "")) {
1932 $('body,html').animate({scrollTop:0}, '500', 'swing');
1933 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001934 return true;
1935 }
1936 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001937 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001938 // if the next item is a header, skip it
1939 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001940 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001941 }
1942 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001943 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001944 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001945 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1946 // If user reaches top, reset selected column
1947 if (gSelectedIndex < 0) {
1948 gSelectedColumn = -1;
1949 }
Scott Mainf5089842012-08-14 16:31:07 -07001950 }
1951 return false;
1952 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001953 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001954 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001955 // if the next item is a header, skip it
1956 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001957 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08001958 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001959 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
1960 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
1961 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001962 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07001963 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07001964 }
1965 return false;
1966 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001967 // Consider left/right arrow navigation
1968 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
1969 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
1970 // 37 LEFT ARROW
1971 // go left only if current column is not left-most column (last column)
1972 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
1973 $('li', $selectedUl).removeClass('jd-selected');
1974 gSelectedColumn++;
1975 $selectedUl = $columns[gSelectedColumn];
1976 // keep or reset the selected item to last item as appropriate
1977 gSelectedIndex = gSelectedIndex >
1978 $("li", $selectedUl).length-1 ?
1979 $("li", $selectedUl).length-1 : gSelectedIndex;
1980 // if the corresponding item is a header, move down
1981 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1982 gSelectedIndex++;
1983 }
Scott Main3b90aff2013-08-01 18:09:35 -07001984 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07001985 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1986 return false;
1987 }
1988 // 39 RIGHT ARROW
1989 // go right only if current column is not the right-most column (first column)
1990 else if (e.keyCode == 39 && gSelectedColumn > 0) {
1991 $('li', $selectedUl).removeClass('jd-selected');
1992 gSelectedColumn--;
1993 $selectedUl = $columns[gSelectedColumn];
1994 // keep or reset the selected item to last item as appropriate
1995 gSelectedIndex = gSelectedIndex >
1996 $("li", $selectedUl).length-1 ?
1997 $("li", $selectedUl).length-1 : gSelectedIndex;
1998 // if the corresponding item is a header, move down
1999 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
2000 gSelectedIndex++;
2001 }
Scott Main3b90aff2013-08-01 18:09:35 -07002002 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07002003 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
2004 return false;
2005 }
2006 }
2007
Scott Main719acb42013-12-05 16:05:09 -08002008 // if key-up event and not arrow down/up/left/right,
2009 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07002010 else if (!kd && (e.keyCode != 40)
2011 && (e.keyCode != 38)
2012 && (e.keyCode != 37)
2013 && (e.keyCode != 39)) {
2014 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07002015 gMatches = new Array();
2016 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002017 gGoogleMatches = new Array();
2018 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07002019 gDocsMatches = new Array();
2020 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08002021
2022 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07002023 for (var i=0; i<DATA.length; i++) {
2024 var s = DATA[i];
2025 if (text.length != 0 &&
2026 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2027 gMatches[matchedCount] = s;
2028 matchedCount++;
2029 }
2030 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002031 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07002032 for (var i=0; i<gMatches.length; i++) {
2033 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08002034 }
2035
2036
2037 // Search for Google matches
2038 for (var i=0; i<GOOGLE_DATA.length; i++) {
2039 var s = GOOGLE_DATA[i];
2040 if (text.length != 0 &&
2041 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
2042 gGoogleMatches[matchedCountGoogle] = s;
2043 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07002044 }
2045 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002046 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08002047 for (var i=0; i<gGoogleMatches.length; i++) {
2048 var s = gGoogleMatches[i];
2049 }
2050
Scott Mainf5089842012-08-14 16:31:07 -07002051 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07002052
2053
2054
Scott Main719acb42013-12-05 16:05:09 -08002055 // Search for matching JD docs
Dirk Dougherty9b7f8f22014-11-01 17:08:56 -07002056 if (text.length >= 2) {
Scott Main719acb42013-12-05 16:05:09 -08002057 // Regex to match only the beginning of a word
2058 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
2059
2060
2061 // Search for Training classes
2062 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002063 // current search comparison, with counters for tag and title,
2064 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08002065 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07002066 s.matched_tag = 0;
2067 s.matched_title = 0;
2068 var matched = false;
2069
2070 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08002071 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002072 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08002073 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002074 matched = true;
2075 s.matched_tag = j + 1; // add 1 to index position
2076 }
2077 }
Scott Main719acb42013-12-05 16:05:09 -08002078 // Don't consider doc title for lessons (only for class landing pages),
2079 // unless the lesson has a tag that already matches
2080 if ((s.lang == currentLang) &&
2081 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002082 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08002083 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002084 matched = true;
2085 s.matched_title = 1;
2086 }
2087 }
2088 if (matched) {
2089 gDocsMatches[matchedCountDocs] = s;
2090 matchedCountDocs++;
2091 }
2092 }
Scott Main719acb42013-12-05 16:05:09 -08002093
2094
2095 // Search for API Guides
2096 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2097 // current search comparison, with counters for tag and title,
2098 // used later to improve ranking
2099 var s = GUIDE_RESOURCES[i];
2100 s.matched_tag = 0;
2101 s.matched_title = 0;
2102 var matched = false;
2103
2104 // Check if query matches any tags; work backwards toward 1 to assist ranking
2105 for (var j = s.keywords.length - 1; j >= 0; j--) {
2106 // it matches a tag
2107 if (s.keywords[j].toLowerCase().match(textRegex)) {
2108 matched = true;
2109 s.matched_tag = j + 1; // add 1 to index position
2110 }
2111 }
2112 // Check if query matches the doc title, but only for current language
2113 if (s.lang == currentLang) {
2114 // if query matches the doc title
2115 if (s.title.toLowerCase().match(textRegex)) {
2116 matched = true;
2117 s.matched_title = 1;
2118 }
2119 }
2120 if (matched) {
2121 gDocsMatches[matchedCountDocs] = s;
2122 matchedCountDocs++;
2123 }
2124 }
2125
2126
2127 // Search for Tools Guides
2128 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2129 // current search comparison, with counters for tag and title,
2130 // used later to improve ranking
2131 var s = TOOLS_RESOURCES[i];
2132 s.matched_tag = 0;
2133 s.matched_title = 0;
2134 var matched = false;
2135
2136 // Check if query matches any tags; work backwards toward 1 to assist ranking
2137 for (var j = s.keywords.length - 1; j >= 0; j--) {
2138 // it matches a tag
2139 if (s.keywords[j].toLowerCase().match(textRegex)) {
2140 matched = true;
2141 s.matched_tag = j + 1; // add 1 to index position
2142 }
2143 }
2144 // Check if query matches the doc title, but only for current language
2145 if (s.lang == currentLang) {
2146 // if query matches the doc title
2147 if (s.title.toLowerCase().match(textRegex)) {
2148 matched = true;
2149 s.matched_title = 1;
2150 }
2151 }
2152 if (matched) {
2153 gDocsMatches[matchedCountDocs] = s;
2154 matchedCountDocs++;
2155 }
2156 }
2157
2158
2159 // Search for About docs
2160 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2161 // current search comparison, with counters for tag and title,
2162 // used later to improve ranking
2163 var s = ABOUT_RESOURCES[i];
2164 s.matched_tag = 0;
2165 s.matched_title = 0;
2166 var matched = false;
2167
2168 // Check if query matches any tags; work backwards toward 1 to assist ranking
2169 for (var j = s.keywords.length - 1; j >= 0; j--) {
2170 // it matches a tag
2171 if (s.keywords[j].toLowerCase().match(textRegex)) {
2172 matched = true;
2173 s.matched_tag = j + 1; // add 1 to index position
2174 }
2175 }
2176 // Check if query matches the doc title, but only for current language
2177 if (s.lang == currentLang) {
2178 // if query matches the doc title
2179 if (s.title.toLowerCase().match(textRegex)) {
2180 matched = true;
2181 s.matched_title = 1;
2182 }
2183 }
2184 if (matched) {
2185 gDocsMatches[matchedCountDocs] = s;
2186 matchedCountDocs++;
2187 }
2188 }
2189
2190
2191 // Search for Design guides
2192 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2193 // current search comparison, with counters for tag and title,
2194 // used later to improve ranking
2195 var s = DESIGN_RESOURCES[i];
2196 s.matched_tag = 0;
2197 s.matched_title = 0;
2198 var matched = false;
2199
2200 // Check if query matches any tags; work backwards toward 1 to assist ranking
2201 for (var j = s.keywords.length - 1; j >= 0; j--) {
2202 // it matches a tag
2203 if (s.keywords[j].toLowerCase().match(textRegex)) {
2204 matched = true;
2205 s.matched_tag = j + 1; // add 1 to index position
2206 }
2207 }
2208 // Check if query matches the doc title, but only for current language
2209 if (s.lang == currentLang) {
2210 // if query matches the doc title
2211 if (s.title.toLowerCase().match(textRegex)) {
2212 matched = true;
2213 s.matched_title = 1;
2214 }
2215 }
2216 if (matched) {
2217 gDocsMatches[matchedCountDocs] = s;
2218 matchedCountDocs++;
2219 }
2220 }
2221
2222
2223 // Search for Distribute guides
2224 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2225 // current search comparison, with counters for tag and title,
2226 // used later to improve ranking
2227 var s = DISTRIBUTE_RESOURCES[i];
2228 s.matched_tag = 0;
2229 s.matched_title = 0;
2230 var matched = false;
2231
2232 // Check if query matches any tags; work backwards toward 1 to assist ranking
2233 for (var j = s.keywords.length - 1; j >= 0; j--) {
2234 // it matches a tag
2235 if (s.keywords[j].toLowerCase().match(textRegex)) {
2236 matched = true;
2237 s.matched_tag = j + 1; // add 1 to index position
2238 }
2239 }
2240 // Check if query matches the doc title, but only for current language
2241 if (s.lang == currentLang) {
2242 // if query matches the doc title
2243 if (s.title.toLowerCase().match(textRegex)) {
2244 matched = true;
2245 s.matched_title = 1;
2246 }
2247 }
2248 if (matched) {
2249 gDocsMatches[matchedCountDocs] = s;
2250 matchedCountDocs++;
2251 }
2252 }
2253
2254
2255 // Search for Google guides
2256 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2257 // current search comparison, with counters for tag and title,
2258 // used later to improve ranking
2259 var s = GOOGLE_RESOURCES[i];
2260 s.matched_tag = 0;
2261 s.matched_title = 0;
2262 var matched = false;
2263
2264 // Check if query matches any tags; work backwards toward 1 to assist ranking
2265 for (var j = s.keywords.length - 1; j >= 0; j--) {
2266 // it matches a tag
2267 if (s.keywords[j].toLowerCase().match(textRegex)) {
2268 matched = true;
2269 s.matched_tag = j + 1; // add 1 to index position
2270 }
2271 }
2272 // Check if query matches the doc title, but only for current language
2273 if (s.lang == currentLang) {
2274 // if query matches the doc title
2275 if (s.title.toLowerCase().match(textRegex)) {
2276 matched = true;
2277 s.matched_title = 1;
2278 }
2279 }
2280 if (matched) {
2281 gDocsMatches[matchedCountDocs] = s;
2282 matchedCountDocs++;
2283 }
2284 }
2285
2286
2287 // Search for Samples
2288 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2289 // current search comparison, with counters for tag and title,
2290 // used later to improve ranking
2291 var s = SAMPLES_RESOURCES[i];
2292 s.matched_tag = 0;
2293 s.matched_title = 0;
2294 var matched = false;
2295 // Check if query matches any tags; work backwards toward 1 to assist ranking
2296 for (var j = s.keywords.length - 1; j >= 0; j--) {
2297 // it matches a tag
2298 if (s.keywords[j].toLowerCase().match(textRegex)) {
2299 matched = true;
2300 s.matched_tag = j + 1; // add 1 to index position
2301 }
2302 }
2303 // Check if query matches the doc title, but only for current language
2304 if (s.lang == currentLang) {
2305 // if query matches the doc title.t
2306 if (s.title.toLowerCase().match(textRegex)) {
2307 matched = true;
2308 s.matched_title = 1;
2309 }
2310 }
2311 if (matched) {
2312 gDocsMatches[matchedCountDocs] = s;
2313 matchedCountDocs++;
2314 }
2315 }
2316
2317 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002318 rank_autocomplete_doc_results(text, gDocsMatches);
2319 }
2320
2321 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002322 sync_selection_table(toroot);
2323 return true; // allow the event to bubble up to the search api
2324 }
2325}
2326
Scott Main0e76e7e2013-03-12 10:24:07 -07002327/* Order the jd doc result list based on match quality */
2328function rank_autocomplete_doc_results(query, matches) {
2329 query = query || '';
2330 if (!matches || !matches.length)
2331 return;
2332
2333 var _resultScoreFn = function(match) {
2334 var score = 1.0;
2335
2336 // if the query matched a tag
2337 if (match.matched_tag > 0) {
2338 // multiply score by factor relative to position in tags list (max of 3)
2339 score *= 3 / match.matched_tag;
2340
2341 // if it also matched the title
2342 if (match.matched_title > 0) {
2343 score *= 2;
2344 }
2345 } else if (match.matched_title > 0) {
2346 score *= 3;
2347 }
2348
2349 return score;
2350 };
2351
2352 for (var i=0; i<matches.length; i++) {
2353 matches[i].__resultScore = _resultScoreFn(matches[i]);
2354 }
2355
2356 matches.sort(function(a,b){
2357 var n = b.__resultScore - a.__resultScore;
2358 if (n == 0) // lexicographical sort if scores are the same
2359 n = (a.label < b.label) ? -1 : 1;
2360 return n;
2361 });
2362}
2363
Scott Main7e447ed2013-02-19 17:22:37 -08002364/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002365function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002366 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002367 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002368 return;
2369
2370 // helper function that gets the last occurence index of the given regex
2371 // in the given string, or -1 if not found
2372 var _lastSearch = function(s, re) {
2373 if (s == '')
2374 return -1;
2375 var l = -1;
2376 var tmp;
2377 while ((tmp = s.search(re)) >= 0) {
2378 if (l < 0) l = 0;
2379 l += tmp;
2380 s = s.substr(tmp + 1);
2381 }
2382 return l;
2383 };
2384
2385 // helper function that counts the occurrences of a given character in
2386 // a given string
2387 var _countChar = function(s, c) {
2388 var n = 0;
2389 for (var i=0; i<s.length; i++)
2390 if (s.charAt(i) == c) ++n;
2391 return n;
2392 };
2393
2394 var queryLower = query.toLowerCase();
2395 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2396 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2397 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2398
2399 var _resultScoreFn = function(result) {
2400 // scores are calculated based on exact and prefix matches,
2401 // and then number of path separators (dots) from the last
2402 // match (i.e. favoring classes and deep package names)
2403 var score = 1.0;
2404 var labelLower = result.label.toLowerCase();
2405 var t;
2406 t = _lastSearch(labelLower, partExactAlnumRE);
2407 if (t >= 0) {
2408 // exact part match
2409 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2410 score *= 200 / (partsAfter + 1);
2411 } else {
2412 t = _lastSearch(labelLower, partPrefixAlnumRE);
2413 if (t >= 0) {
2414 // part prefix match
2415 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2416 score *= 20 / (partsAfter + 1);
2417 }
2418 }
2419
2420 return score;
2421 };
2422
Scott Main7e447ed2013-02-19 17:22:37 -08002423 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002424 // if the API is deprecated, default score is 0; otherwise, perform scoring
2425 if (matches[i].deprecated == "true") {
2426 matches[i].__resultScore = 0;
2427 } else {
2428 matches[i].__resultScore = _resultScoreFn(matches[i]);
2429 }
Scott Mainf5089842012-08-14 16:31:07 -07002430 }
2431
Scott Main7e447ed2013-02-19 17:22:37 -08002432 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002433 var n = b.__resultScore - a.__resultScore;
2434 if (n == 0) // lexicographical sort if scores are the same
2435 n = (a.label < b.label) ? -1 : 1;
2436 return n;
2437 });
2438}
2439
Scott Main7e447ed2013-02-19 17:22:37 -08002440/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002441function highlight_autocomplete_result_labels(query) {
2442 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002443 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002444 return;
2445
2446 var queryLower = query.toLowerCase();
2447 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2448 var queryRE = new RegExp(
2449 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2450 for (var i=0; i<gMatches.length; i++) {
2451 gMatches[i].__hilabel = gMatches[i].label.replace(
2452 queryRE, '<b>$1</b>');
2453 }
Scott Main7e447ed2013-02-19 17:22:37 -08002454 for (var i=0; i<gGoogleMatches.length; i++) {
2455 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2456 queryRE, '<b>$1</b>');
2457 }
Scott Mainf5089842012-08-14 16:31:07 -07002458}
2459
2460function search_focus_changed(obj, focused)
2461{
Scott Main3b90aff2013-08-01 18:09:35 -07002462 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002463 if(obj.value == ""){
2464 $(".search .close").addClass("hide");
2465 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002466 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002467 }
2468}
2469
2470function submit_search() {
2471 var query = document.getElementById('search_autocomplete').value;
2472 location.hash = 'q=' + query;
2473 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002474 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002475 return false;
2476}
2477
2478
2479function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002480 $("#searchResults").slideUp('fast', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002481 $(".search .close").addClass("hide");
2482 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002483
Scott Mainf5089842012-08-14 16:31:07 -07002484 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002485
Scott Mainf5089842012-08-14 16:31:07 -07002486 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2487 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002488
2489 // forcefully regain key-up event control (previously jacked by search api)
2490 $("#search_autocomplete").keyup(function(event) {
2491 return search_changed(event, false, toRoot);
2492 });
2493
Scott Mainf5089842012-08-14 16:31:07 -07002494 return false;
2495}
2496
2497
2498
2499/* ########################################################## */
2500/* ################ CUSTOM SEARCH ENGINE ################## */
2501/* ########################################################## */
2502
Scott Mainf5089842012-08-14 16:31:07 -07002503var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002504google.load('search', '1', {"callback" : function() {
2505 searchControl = new google.search.SearchControl();
2506 } });
Scott Mainf5089842012-08-14 16:31:07 -07002507
2508function loadSearchResults() {
2509 document.getElementById("search_autocomplete").style.color = "#000";
2510
Scott Mainf5089842012-08-14 16:31:07 -07002511 searchControl = new google.search.SearchControl();
2512
2513 // use our existing search form and use tabs when multiple searchers are used
2514 drawOptions = new google.search.DrawOptions();
2515 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2516 drawOptions.setInput(document.getElementById("search_autocomplete"));
2517
2518 // configure search result options
2519 searchOptions = new google.search.SearcherOptions();
2520 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2521
2522 // configure each of the searchers, for each tab
2523 devSiteSearcher = new google.search.WebSearch();
2524 devSiteSearcher.setUserDefinedLabel("All");
2525 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2526
2527 designSearcher = new google.search.WebSearch();
2528 designSearcher.setUserDefinedLabel("Design");
2529 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2530
2531 trainingSearcher = new google.search.WebSearch();
2532 trainingSearcher.setUserDefinedLabel("Training");
2533 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2534
2535 guidesSearcher = new google.search.WebSearch();
2536 guidesSearcher.setUserDefinedLabel("Guides");
2537 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2538
2539 referenceSearcher = new google.search.WebSearch();
2540 referenceSearcher.setUserDefinedLabel("Reference");
2541 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2542
Scott Maindf08ada2012-12-03 08:54:37 -08002543 googleSearcher = new google.search.WebSearch();
2544 googleSearcher.setUserDefinedLabel("Google Services");
2545 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2546
Scott Mainf5089842012-08-14 16:31:07 -07002547 blogSearcher = new google.search.WebSearch();
2548 blogSearcher.setUserDefinedLabel("Blog");
2549 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2550
2551 // add each searcher to the search control
2552 searchControl.addSearcher(devSiteSearcher, searchOptions);
2553 searchControl.addSearcher(designSearcher, searchOptions);
2554 searchControl.addSearcher(trainingSearcher, searchOptions);
2555 searchControl.addSearcher(guidesSearcher, searchOptions);
2556 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002557 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002558 searchControl.addSearcher(blogSearcher, searchOptions);
2559
2560 // configure result options
2561 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2562 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2563 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2564 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2565
2566 // upon ajax search, refresh the url and search title
2567 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2568 updateResultTitle(query);
2569 var query = document.getElementById('search_autocomplete').value;
2570 location.hash = 'q=' + query;
2571 });
2572
Scott Mainde295272013-03-25 15:48:35 -07002573 // once search results load, set up click listeners
2574 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2575 addResultClickListeners();
2576 });
2577
Scott Mainf5089842012-08-14 16:31:07 -07002578 // draw the search results box
2579 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2580
2581 // get query and execute the search
2582 searchControl.execute(decodeURI(getQuery(location.hash)));
2583
2584 document.getElementById("search_autocomplete").focus();
2585 addTabListeners();
2586}
2587// End of loadSearchResults
2588
2589
2590google.setOnLoadCallback(function(){
2591 if (location.hash.indexOf("q=") == -1) {
2592 // if there's no query in the url, don't search and make sure results are hidden
2593 $('#searchResults').hide();
2594 return;
2595 } else {
2596 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002597 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002598 $(".search .close").removeClass("hide");
2599 loadSearchResults();
2600 }
2601}, true);
2602
smain@google.com9a818f52014-10-03 09:25:59 -07002603/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2604 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002605function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002606 // Ignore if there's no search bar (some special pages have no header)
2607 if ($("#search-container").length < 1) return;
2608
smain@google.com3b77ab52014-06-17 11:57:27 -07002609 var hash = escape(location.hash.substr(1));
2610 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002611 // Sanity check that there's an element with that ID on the page
2612 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002613 // If the position of the target element is near the top of the page (<20px, where we expect it
2614 // to be because we need to move it down 60px to become in view), then move it down 60px
2615 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2616 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002617 }
2618 }
2619}
2620
Scott Mainf5089842012-08-14 16:31:07 -07002621// when an event on the browser history occurs (back, forward, load) requery hash and do search
2622$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002623 // Ignore if there's no search bar (some special pages have no header)
2624 if ($("#search-container").length < 1) return;
2625
Dirk Doughertyc3921652014-05-13 16:55:26 -07002626 // If the hash isn't a search query or there's an error in the query,
2627 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002628 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2629 // If the results pane is open, close it.
2630 if (!$("#searchResults").is(":hidden")) {
2631 hideResults();
2632 }
Scott Mainb16376f2014-05-21 20:35:47 -07002633 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002634 return;
2635 }
2636
2637 // Otherwise, we have a search to do
2638 var query = decodeURI(getQuery(location.hash));
2639 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002640 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002641 $("#search_autocomplete").focus();
2642 $(".search .close").removeClass("hide");
2643
2644 updateResultTitle(query);
2645});
2646
2647function updateResultTitle(query) {
2648 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2649}
2650
2651// forcefully regain key-up event control (previously jacked by search api)
2652$("#search_autocomplete").keyup(function(event) {
2653 return search_changed(event, false, toRoot);
2654});
2655
2656// add event listeners to each tab so we can track the browser history
2657function addTabListeners() {
2658 var tabHeaders = $(".gsc-tabHeader");
2659 for (var i = 0; i < tabHeaders.length; i++) {
2660 $(tabHeaders[i]).attr("id",i).click(function() {
2661 /*
2662 // make a copy of the page numbers for the search left pane
2663 setTimeout(function() {
2664 // remove any residual page numbers
2665 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002666 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002667 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002668 // and because we're going to remove it (previous line),
2669 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002670 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2671 .clone().appendTo('#searchResults .gsc-tabsArea');
2672 }, 200);
2673 */
2674 });
2675 }
2676 setTimeout(function(){$(tabHeaders[0]).click()},200);
2677}
2678
Scott Mainde295272013-03-25 15:48:35 -07002679// add analytics tracking events to each result link
2680function addResultClickListeners() {
2681 $("#searchResults a.gs-title").each(function(index, link) {
2682 // When user clicks enter for Google search results, track it
2683 $(link).click(function() {
smain@google.com633f3222014-10-03 15:49:45 -07002684 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).text(),
2685 'from: ' + $("#search_autocomplete").val());
Scott Mainde295272013-03-25 15:48:35 -07002686 });
2687 });
2688}
2689
Scott Mainf5089842012-08-14 16:31:07 -07002690
2691function getQuery(hash) {
2692 var queryParts = hash.split('=');
2693 return queryParts[1];
2694}
2695
2696/* returns the given string with all HTML brackets converted to entities
2697 TODO: move this to the site's JS library */
2698function escapeHTML(string) {
2699 return string.replace(/</g,"&lt;")
2700 .replace(/>/g,"&gt;");
2701}
2702
2703
2704
2705
2706
2707
2708
2709/* ######################################################## */
2710/* ################# JAVADOC REFERENCE ################### */
2711/* ######################################################## */
2712
Scott Main65511c02012-09-07 15:51:32 -07002713/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002714if (location.pathname.indexOf("/reference") == 0) {
2715 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2716 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2717 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002718 $(document).ready(function() {
2719 // init available apis based on user pref
2720 changeApiLevel();
2721 initSidenavHeightResize()
2722 });
2723 }
Scott Main65511c02012-09-07 15:51:32 -07002724}
Scott Mainf5089842012-08-14 16:31:07 -07002725
2726var API_LEVEL_COOKIE = "api_level";
2727var minLevel = 1;
2728var maxLevel = 1;
2729
2730/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002731
Scott Mainf5089842012-08-14 16:31:07 -07002732 function initSidenavHeightResize() {
2733 // Change the drag bar size to nicely fit the scrollbar positions
2734 var $dragBar = $(".ui-resizable-s");
2735 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002736
2737 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002738 containment: "#nav-panels",
2739 handles: "s",
2740 alsoResize: "#packages-nav",
2741 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2742 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2743 });
Scott Main3b90aff2013-08-01 18:09:35 -07002744
Scott Mainf5089842012-08-14 16:31:07 -07002745 }
Scott Main3b90aff2013-08-01 18:09:35 -07002746
Scott Mainf5089842012-08-14 16:31:07 -07002747function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002748 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002749 $('#devdoc-nav').css({
2750 'width' : $('#side-nav').css('width'),
2751 'margin' : $('#side-nav').css('margin')
2752 });
2753 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002754
Scott Mainf5089842012-08-14 16:31:07 -07002755 initSidenavHeightResize();
2756}
2757
2758function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002759 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002760 $('#devdoc-nav').css({
2761 'width' : $('#side-nav').css('width'),
2762 'margin' : $('#side-nav').css('margin')
2763 });
2764 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002765
Scott Mainf5089842012-08-14 16:31:07 -07002766 initSidenavHeightResize();
2767}
2768
2769function buildApiLevelSelector() {
2770 maxLevel = SINCE_DATA.length;
2771 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2772 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2773
2774 minLevel = parseInt($("#doc-api-level").attr("class"));
2775 // Handle provisional api levels; the provisional level will always be the highest possible level
2776 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2777 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2778 if (isNaN(minLevel) && minLevel.length) {
2779 minLevel = maxLevel;
2780 }
2781 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2782 for (var i = maxLevel-1; i >= 0; i--) {
2783 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2784 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2785 select.append(option);
2786 }
2787
2788 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2789 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2790 selectedLevelItem.setAttribute('selected',true);
2791}
2792
2793function changeApiLevel() {
2794 maxLevel = SINCE_DATA.length;
2795 var selectedLevel = maxLevel;
2796
2797 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2798 toggleVisisbleApis(selectedLevel, "body");
2799
smain@google.com6bdcb982014-11-14 11:53:07 -08002800 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002801
2802 if (selectedLevel < minLevel) {
2803 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002804 $("#naMessage").show().html("<div><p><strong>This " + thing
2805 + " requires API level " + minLevel + " or higher.</strong></p>"
2806 + "<p>This document is hidden because your selected API level for the documentation is "
2807 + selectedLevel + ". You can change the documentation API level with the selector "
2808 + "above the left navigation.</p>"
2809 + "<p>For more information about specifying the API level your app requires, "
2810 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2811 + ">Supporting Different Platform Versions</a>.</p>"
2812 + "<input type='button' value='OK, make this page visible' "
2813 + "title='Change the API level to " + minLevel + "' "
2814 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2815 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002816 } else {
2817 $("#naMessage").hide();
2818 }
2819}
2820
2821function toggleVisisbleApis(selectedLevel, context) {
2822 var apis = $(".api",context);
2823 apis.each(function(i) {
2824 var obj = $(this);
2825 var className = obj.attr("class");
2826 var apiLevelIndex = className.lastIndexOf("-")+1;
2827 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2828 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2829 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2830 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2831 return;
2832 }
2833 apiLevel = parseInt(apiLevel);
2834
2835 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2836 var selectedLevelNum = parseInt(selectedLevel)
2837 var apiLevelNum = parseInt(apiLevel);
2838 if (isNaN(apiLevelNum)) {
2839 apiLevelNum = maxLevel;
2840 }
2841
2842 // Grey things out that aren't available and give a tooltip title
2843 if (apiLevelNum > selectedLevelNum) {
2844 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002845 + apiLevel + "\" or higher. To reveal, change the target API level "
2846 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002847 }
Scott Mainf5089842012-08-14 16:31:07 -07002848 else obj.removeClass("absent").removeAttr("title");
2849 });
2850}
2851
2852
2853
2854
2855/* ################# SIDENAV TREE VIEW ################### */
2856
2857function new_node(me, mom, text, link, children_data, api_level)
2858{
2859 var node = new Object();
2860 node.children = Array();
2861 node.children_data = children_data;
2862 node.depth = mom.depth + 1;
2863
2864 node.li = document.createElement("li");
2865 mom.get_children_ul().appendChild(node.li);
2866
2867 node.label_div = document.createElement("div");
2868 node.label_div.className = "label";
2869 if (api_level != null) {
2870 $(node.label_div).addClass("api");
2871 $(node.label_div).addClass("api-level-"+api_level);
2872 }
2873 node.li.appendChild(node.label_div);
2874
2875 if (children_data != null) {
2876 node.expand_toggle = document.createElement("a");
2877 node.expand_toggle.href = "javascript:void(0)";
2878 node.expand_toggle.onclick = function() {
2879 if (node.expanded) {
2880 $(node.get_children_ul()).slideUp("fast");
2881 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2882 node.expanded = false;
2883 } else {
2884 expand_node(me, node);
2885 }
2886 };
2887 node.label_div.appendChild(node.expand_toggle);
2888
2889 node.plus_img = document.createElement("img");
2890 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2891 node.plus_img.className = "plus";
2892 node.plus_img.width = "8";
2893 node.plus_img.border = "0";
2894 node.expand_toggle.appendChild(node.plus_img);
2895
2896 node.expanded = false;
2897 }
2898
2899 var a = document.createElement("a");
2900 node.label_div.appendChild(a);
2901 node.label = document.createTextNode(text);
2902 a.appendChild(node.label);
2903 if (link) {
2904 a.href = me.toroot + link;
2905 } else {
2906 if (children_data != null) {
2907 a.className = "nolink";
2908 a.href = "javascript:void(0)";
2909 a.onclick = node.expand_toggle.onclick;
2910 // This next line shouldn't be necessary. I'll buy a beer for the first
2911 // person who figures out how to remove this line and have the link
2912 // toggle shut on the first try. --joeo@android.com
2913 node.expanded = false;
2914 }
2915 }
Scott Main3b90aff2013-08-01 18:09:35 -07002916
Scott Mainf5089842012-08-14 16:31:07 -07002917
2918 node.children_ul = null;
2919 node.get_children_ul = function() {
2920 if (!node.children_ul) {
2921 node.children_ul = document.createElement("ul");
2922 node.children_ul.className = "children_ul";
2923 node.children_ul.style.display = "none";
2924 node.li.appendChild(node.children_ul);
2925 }
2926 return node.children_ul;
2927 };
2928
2929 return node;
2930}
2931
Robert Lyd2dd6e52012-11-29 21:28:48 -08002932
2933
2934
Scott Mainf5089842012-08-14 16:31:07 -07002935function expand_node(me, node)
2936{
2937 if (node.children_data && !node.expanded) {
2938 if (node.children_visited) {
2939 $(node.get_children_ul()).slideDown("fast");
2940 } else {
2941 get_node(me, node);
2942 if ($(node.label_div).hasClass("absent")) {
2943 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07002944 }
Scott Mainf5089842012-08-14 16:31:07 -07002945 $(node.get_children_ul()).slideDown("fast");
2946 }
2947 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2948 node.expanded = true;
2949
2950 // perform api level toggling because new nodes are new to the DOM
2951 var selectedLevel = $("#apiLevelSelector option:selected").val();
2952 toggleVisisbleApis(selectedLevel, "#side-nav");
2953 }
2954}
2955
2956function get_node(me, mom)
2957{
2958 mom.children_visited = true;
2959 for (var i in mom.children_data) {
2960 var node_data = mom.children_data[i];
2961 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2962 node_data[2], node_data[3]);
2963 }
2964}
2965
2966function this_page_relative(toroot)
2967{
2968 var full = document.location.pathname;
2969 var file = "";
2970 if (toroot.substr(0, 1) == "/") {
2971 if (full.substr(0, toroot.length) == toroot) {
2972 return full.substr(toroot.length);
2973 } else {
2974 // the file isn't under toroot. Fail.
2975 return null;
2976 }
2977 } else {
2978 if (toroot != "./") {
2979 toroot = "./" + toroot;
2980 }
2981 do {
2982 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
2983 var pos = full.lastIndexOf("/");
2984 file = full.substr(pos) + file;
2985 full = full.substr(0, pos);
2986 toroot = toroot.substr(0, toroot.length-3);
2987 }
2988 } while (toroot != "" && toroot != "/");
2989 return file.substr(1);
2990 }
2991}
2992
2993function find_page(url, data)
2994{
2995 var nodes = data;
2996 var result = null;
2997 for (var i in nodes) {
2998 var d = nodes[i];
2999 if (d[1] == url) {
3000 return new Array(i);
3001 }
3002 else if (d[2] != null) {
3003 result = find_page(url, d[2]);
3004 if (result != null) {
3005 return (new Array(i).concat(result));
3006 }
3007 }
3008 }
3009 return null;
3010}
3011
Scott Mainf5089842012-08-14 16:31:07 -07003012function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07003013 // load json file for navtree data
3014 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
3015 // when the file is loaded, initialize the tree
3016 if(jqxhr.status === 200) {
3017 init_navtree("tree-list", toroot, NAVTREE_DATA);
3018 }
3019 });
Scott Main3b90aff2013-08-01 18:09:35 -07003020
Scott Mainf5089842012-08-14 16:31:07 -07003021 // perform api level toggling because because the whole tree is new to the DOM
3022 var selectedLevel = $("#apiLevelSelector option:selected").val();
3023 toggleVisisbleApis(selectedLevel, "#side-nav");
3024}
3025
3026function init_navtree(navtree_id, toroot, root_nodes)
3027{
3028 var me = new Object();
3029 me.toroot = toroot;
3030 me.node = new Object();
3031
3032 me.node.li = document.getElementById(navtree_id);
3033 me.node.children_data = root_nodes;
3034 me.node.children = new Array();
3035 me.node.children_ul = document.createElement("ul");
3036 me.node.get_children_ul = function() { return me.node.children_ul; };
3037 //me.node.children_ul.className = "children_ul";
3038 me.node.li.appendChild(me.node.children_ul);
3039 me.node.depth = 0;
3040
3041 get_node(me, me.node);
3042
3043 me.this_page = this_page_relative(toroot);
3044 me.breadcrumbs = find_page(me.this_page, root_nodes);
3045 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
3046 var mom = me.node;
3047 for (var i in me.breadcrumbs) {
3048 var j = me.breadcrumbs[i];
3049 mom = mom.children[j];
3050 expand_node(me, mom);
3051 }
3052 mom.label_div.className = mom.label_div.className + " selected";
3053 addLoadEvent(function() {
3054 scrollIntoView("nav-tree");
3055 });
3056 }
3057}
3058
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003059
3060
3061
3062
3063
3064
3065
Robert Lyd2dd6e52012-11-29 21:28:48 -08003066/* TODO: eliminate redundancy with non-google functions */
3067function init_google_navtree(navtree_id, toroot, root_nodes)
3068{
3069 var me = new Object();
3070 me.toroot = toroot;
3071 me.node = new Object();
3072
3073 me.node.li = document.getElementById(navtree_id);
3074 me.node.children_data = root_nodes;
3075 me.node.children = new Array();
3076 me.node.children_ul = document.createElement("ul");
3077 me.node.get_children_ul = function() { return me.node.children_ul; };
3078 //me.node.children_ul.className = "children_ul";
3079 me.node.li.appendChild(me.node.children_ul);
3080 me.node.depth = 0;
3081
3082 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003083}
3084
3085function new_google_node(me, mom, text, link, children_data, api_level)
3086{
3087 var node = new Object();
3088 var child;
3089 node.children = Array();
3090 node.children_data = children_data;
3091 node.depth = mom.depth + 1;
3092 node.get_children_ul = function() {
3093 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003094 node.children_ul = document.createElement("ul");
3095 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003096 node.li.appendChild(node.children_ul);
3097 }
3098 return node.children_ul;
3099 };
3100 node.li = document.createElement("li");
3101
3102 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003103
3104
Robert Lyd2dd6e52012-11-29 21:28:48 -08003105 if(link) {
3106 child = document.createElement("a");
3107
3108 }
3109 else {
3110 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003111 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003112
3113 }
3114 if (children_data != null) {
3115 node.li.className="nav-section";
3116 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003117 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003118 node.li.appendChild(node.label_div);
3119 get_google_node(me, node);
3120 node.label_div.appendChild(child);
3121 }
3122 else {
3123 node.li.appendChild(child);
3124 }
3125 if(link) {
3126 child.href = me.toroot + link;
3127 }
3128 node.label = document.createTextNode(text);
3129 child.appendChild(node.label);
3130
3131 node.children_ul = null;
3132
3133 return node;
3134}
3135
3136function get_google_node(me, mom)
3137{
3138 mom.children_visited = true;
3139 var linkText;
3140 for (var i in mom.children_data) {
3141 var node_data = mom.children_data[i];
3142 linkText = node_data[0];
3143
3144 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3145 linkText = linkText.substr(19, linkText.length);
3146 }
3147 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3148 node_data[2], node_data[3]);
3149 }
3150}
Scott Mainad08f072013-08-20 16:49:57 -07003151
3152
3153
3154
3155
3156
3157/****** NEW version of script to build google and sample navs dynamically ******/
3158// TODO: update Google reference docs to tolerate this new implementation
3159
Scott Maine624b3f2013-09-12 12:56:41 -07003160var NODE_NAME = 0;
3161var NODE_HREF = 1;
3162var NODE_GROUP = 2;
3163var NODE_TAGS = 3;
3164var NODE_CHILDREN = 4;
3165
Scott Mainad08f072013-08-20 16:49:57 -07003166function init_google_navtree2(navtree_id, data)
3167{
3168 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003169 for (var i in data) {
3170 var node_data = data[i];
3171 $containerUl.append(new_google_node2(node_data));
3172 }
3173
Scott Main70557ee2013-10-30 14:47:40 -07003174 // Make all third-generation list items 'sticky' to prevent them from collapsing
3175 $containerUl.find('li li li.nav-section').addClass('sticky');
3176
Scott Mainad08f072013-08-20 16:49:57 -07003177 initExpandableNavItems("#"+navtree_id);
3178}
3179
3180function new_google_node2(node_data)
3181{
Scott Maine624b3f2013-09-12 12:56:41 -07003182 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003183 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3184 linkText = linkText.substr(19, linkText.length);
3185 }
3186 var $li = $('<li>');
3187 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003188 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003189 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3190 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003191 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003192 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3193 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003194 }
3195 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003196 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003197 $li.addClass("nav-section");
3198 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003199 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003200
Scott Maine624b3f2013-09-12 12:56:41 -07003201 for (var i in node_data[NODE_CHILDREN]) {
3202 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003203 $childUl.append(new_google_node2(child_node_data));
3204 }
3205 $li.append($childUl);
3206 }
3207 $li.prepend($a);
3208
3209 return $li;
3210}
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
Robert Lyd2dd6e52012-11-29 21:28:48 -08003222function showGoogleRefTree() {
3223 init_default_google_navtree(toRoot);
3224 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003225}
3226
3227function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003228 // load json file for navtree data
3229 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3230 // when the file is loaded, initialize the tree
3231 if(jqxhr.status === 200) {
3232 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3233 highlightSidenav();
3234 resizeNav();
3235 }
3236 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003237}
3238
3239function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003240 // load json file for navtree data
3241 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3242 // when the file is loaded, initialize the tree
3243 if(jqxhr.status === 200) {
3244 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3245 highlightSidenav();
3246 resizeNav();
3247 }
3248 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003249}
3250
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003251function showSamplesRefTree() {
3252 init_default_samples_navtree(toRoot);
3253}
3254
3255function init_default_samples_navtree(toroot) {
3256 // load json file for navtree data
3257 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3258 // when the file is loaded, initialize the tree
3259 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003260 // hack to remove the "about the samples" link then put it back in
3261 // after we nuke the list to remove the dummy static list of samples
3262 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3263 $("#nav.samples-nav").empty();
3264 $("#nav.samples-nav").append($firstLi);
3265
Scott Mainad08f072013-08-20 16:49:57 -07003266 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003267 highlightSidenav();
3268 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003269 if ($("#jd-content #samples").length) {
3270 showSamples();
3271 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003272 }
3273 });
3274}
3275
Scott Mainf5089842012-08-14 16:31:07 -07003276/* TOGGLE INHERITED MEMBERS */
3277
3278/* Toggle an inherited class (arrow toggle)
3279 * @param linkObj The link that was clicked.
3280 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3281 * 'null' to simply toggle.
3282 */
3283function toggleInherited(linkObj, expand) {
3284 var base = linkObj.getAttribute("id");
3285 var list = document.getElementById(base + "-list");
3286 var summary = document.getElementById(base + "-summary");
3287 var trigger = document.getElementById(base + "-trigger");
3288 var a = $(linkObj);
3289 if ( (expand == null && a.hasClass("closed")) || expand ) {
3290 list.style.display = "none";
3291 summary.style.display = "block";
3292 trigger.src = toRoot + "assets/images/triangle-opened.png";
3293 a.removeClass("closed");
3294 a.addClass("opened");
3295 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3296 list.style.display = "block";
3297 summary.style.display = "none";
3298 trigger.src = toRoot + "assets/images/triangle-closed.png";
3299 a.removeClass("opened");
3300 a.addClass("closed");
3301 }
3302 return false;
3303}
3304
3305/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3306 * @param linkObj The link that was clicked.
3307 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3308 * 'null' to simply toggle.
3309 */
3310function toggleAllInherited(linkObj, expand) {
3311 var a = $(linkObj);
3312 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3313 var expandos = $(".jd-expando-trigger", table);
3314 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3315 expandos.each(function(i) {
3316 toggleInherited(this, true);
3317 });
3318 a.text("[Collapse]");
3319 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3320 expandos.each(function(i) {
3321 toggleInherited(this, false);
3322 });
3323 a.text("[Expand]");
3324 }
3325 return false;
3326}
3327
3328/* Toggle all inherited members in the class (link in the class title)
3329 */
3330function toggleAllClassInherited() {
3331 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3332 var toggles = $(".toggle-all", $("#body-content"));
3333 if (a.text() == "[Expand All]") {
3334 toggles.each(function(i) {
3335 toggleAllInherited(this, true);
3336 });
3337 a.text("[Collapse All]");
3338 } else {
3339 toggles.each(function(i) {
3340 toggleAllInherited(this, false);
3341 });
3342 a.text("[Expand All]");
3343 }
3344 return false;
3345}
3346
3347/* Expand all inherited members in the class. Used when initiating page search */
3348function ensureAllInheritedExpanded() {
3349 var toggles = $(".toggle-all", $("#body-content"));
3350 toggles.each(function(i) {
3351 toggleAllInherited(this, true);
3352 });
3353 $("#toggleAllClassInherited").text("[Collapse All]");
3354}
3355
3356
3357/* HANDLE KEY EVENTS
3358 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3359 */
3360var agent = navigator['userAgent'].toLowerCase();
3361var mac = agent.indexOf("macintosh") != -1;
3362
3363$(document).keydown( function(e) {
3364var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3365 if (control && e.which == 70) { // 70 is "F"
3366 ensureAllInheritedExpanded();
3367 }
3368});
Scott Main498d7102013-08-21 15:47:38 -07003369
3370
3371
3372
3373
3374
3375/* On-demand functions */
3376
3377/** Move sample code line numbers out of PRE block and into non-copyable column */
3378function initCodeLineNumbers() {
3379 var numbers = $("#codesample-block a.number");
3380 if (numbers.length) {
3381 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3382 }
3383
3384 $(document).ready(function() {
3385 // select entire line when clicked
3386 $("span.code-line").click(function() {
3387 if (!shifted) {
3388 selectText(this);
3389 }
3390 });
3391 // invoke line link on double click
3392 $(".code-line").dblclick(function() {
3393 document.location.hash = $(this).attr('id');
3394 });
3395 // highlight the line when hovering on the number
3396 $("#codesample-line-numbers a.number").mouseover(function() {
3397 var id = $(this).attr('href');
3398 $(id).css('background','#e7e7e7');
3399 });
3400 $("#codesample-line-numbers a.number").mouseout(function() {
3401 var id = $(this).attr('href');
3402 $(id).css('background','none');
3403 });
3404 });
3405}
3406
3407// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3408var shifted = false;
3409$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3410
3411// courtesy of jasonedelman.com
3412function selectText(element) {
3413 var doc = document
3414 , range, selection
3415 ;
3416 if (doc.body.createTextRange) { //ms
3417 range = doc.body.createTextRange();
3418 range.moveToElementText(element);
3419 range.select();
3420 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003421 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003422 range = doc.createRange();
3423 range.selectNodeContents(element);
3424 selection.removeAllRanges();
3425 selection.addRange(range);
3426 }
Scott Main285f0772013-08-22 23:22:09 +00003427}
Scott Main03aca9a2013-10-31 07:20:55 -07003428
3429
3430
3431
3432/** Display links and other information about samples that match the
3433 group specified by the URL */
3434function showSamples() {
3435 var group = $("#samples").attr('class');
3436 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3437
3438 var $ul = $("<ul>");
3439 $selectedLi = $("#nav li.selected");
3440
3441 $selectedLi.children("ul").children("li").each(function() {
3442 var $li = $("<li>").append($(this).find("a").first().clone());
3443 $ul.append($li);
3444 });
3445
3446 $("#samples").append($ul);
3447
3448}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003449
3450
3451
3452/* ########################################################## */
3453/* ################### RESOURCE CARDS ##################### */
3454/* ########################################################## */
3455
3456/** Handle resource queries, collections, and grids (sections). Requires
3457 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3458
3459(function() {
3460 // Prevent the same resource from being loaded more than once per page.
3461 var addedPageResources = {};
3462
3463 $(document).ready(function() {
3464 $('.resource-widget').each(function() {
3465 initResourceWidget(this);
3466 });
3467
3468 /* Pass the line height to ellipsisfade() to adjust the height of the
3469 text container to show the max number of lines possible, without
3470 showing lines that are cut off. This works with the css ellipsis
3471 classes to fade last text line and apply an ellipsis char. */
3472
Scott Mainb16376f2014-05-21 20:35:47 -07003473 //card text currently uses 15px line height.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003474 var lineHeight = 15;
3475 $('.card-info .text').ellipsisfade(lineHeight);
3476 });
3477
3478 /*
3479 Three types of resource layouts:
3480 Flow - Uses a fixed row-height flow using float left style.
3481 Carousel - Single card slideshow all same dimension absolute.
3482 Stack - Uses fixed columns and flexible element height.
3483 */
3484 function initResourceWidget(widget) {
3485 var $widget = $(widget);
3486 var isFlow = $widget.hasClass('resource-flow-layout'),
3487 isCarousel = $widget.hasClass('resource-carousel-layout'),
3488 isStack = $widget.hasClass('resource-stack-layout');
3489
3490 // find size of widget by pulling out its class name
3491 var sizeCols = 1;
3492 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
3493 if (m) {
3494 sizeCols = parseInt(m[1], 10);
3495 }
3496
3497 var opts = {
3498 cardSizes: ($widget.data('cardsizes') || '').split(','),
3499 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3500 itemsPerPage: $widget.data('itemsperpage'),
3501 sortOrder: $widget.data('sortorder'),
3502 query: $widget.data('query'),
3503 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003504 sizeCols: sizeCols,
3505 /* Added by LFL 6/6/14 */
3506 resourceStyle: $widget.data('resourcestyle') || 'card',
3507 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003508 };
3509
3510 // run the search for the set of resources to show
3511
3512 var resources = buildResourceList(opts);
3513
3514 if (isFlow) {
3515 drawResourcesFlowWidget($widget, opts, resources);
3516 } else if (isCarousel) {
3517 drawResourcesCarouselWidget($widget, opts, resources);
3518 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003519 /* Looks like this got removed and is not used, so repurposing for the
3520 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003521 Modified by LFL 6/6/14
3522 */
3523 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003524 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003525 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003526 }
3527 }
3528
3529 /* Initializes a Resource Carousel Widget */
3530 function drawResourcesCarouselWidget($widget, opts, resources) {
3531 $widget.empty();
3532 var plusone = true; //always show plusone on carousel
3533
3534 $widget.addClass('resource-card slideshow-container')
3535 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3536 .append($('<a>').addClass('slideshow-next').text('Next'));
3537
3538 var css = { 'width': $widget.width() + 'px',
3539 'height': $widget.height() + 'px' };
3540
3541 var $ul = $('<ul>');
3542
3543 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003544 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003545 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003546 .decorateResourceCard(resources[i],plusone);
3547
3548 $('<li>').css(css)
3549 .append($card)
3550 .appendTo($ul);
3551 }
3552
3553 $('<div>').addClass('frame')
3554 .append($ul)
3555 .appendTo($widget);
3556
3557 $widget.dacSlideshow({
3558 auto: true,
3559 btnPrev: '.slideshow-prev',
3560 btnNext: '.slideshow-next'
3561 });
3562 };
3563
Robert Lye7eeb402014-06-03 19:35:24 -07003564 /* Initializes a Resource Card Stack Widget (column-based layout)
3565 Modified by LFL 6/6/14
3566 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003567 function drawResourcesStackWidget($widget, opts, resources, sections) {
3568 // Don't empty widget, grab all items inside since they will be the first
3569 // items stacked, followed by the resource query
3570 var plusone = true; //by default show plusone on section cards
3571 var cards = $widget.find('.resource-card').detach().toArray();
3572 var numStacks = opts.numStacks || 1;
3573 var $stacks = [];
3574 var urlString;
3575
3576 for (var i = 0; i < numStacks; ++i) {
3577 $stacks[i] = $('<div>').addClass('resource-card-stack')
3578 .appendTo($widget);
3579 }
3580
3581 var sectionResources = [];
3582
3583 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003584 if (sections) {
3585 for (var i = 0; i < sections.length; ++i) {
3586 if (!sections[i].sections || !sections[i].sections.length) {
3587 // Render it as a resource card
3588 sectionResources.push(
3589 $('<a>')
3590 .addClass('resource-card section-card')
3591 .attr('href', cleanUrl(sections[i].resource.url))
3592 .decorateResourceCard(sections[i].resource,plusone)[0]
3593 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003594
Robert Lye7eeb402014-06-03 19:35:24 -07003595 } else {
3596 cards.push(
3597 $('<div>')
3598 .addClass('resource-card section-card-menu')
3599 .decorateResourceSection(sections[i],plusone)[0]
3600 );
3601 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003602 }
3603 }
3604
3605 cards = cards.concat(sectionResources);
3606
3607 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003608 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003609
Robert Lye7eeb402014-06-03 19:35:24 -07003610 if (opts.resourceStyle.indexOf('related') > -1) {
3611 $card.addClass('related-card');
3612 }
smain@google.com95948b82014-06-16 19:24:25 -07003613
Dirk Doughertyc3921652014-05-13 16:55:26 -07003614 cards.push($card[0]);
3615 }
3616
Robert Lye7eeb402014-06-03 19:35:24 -07003617 if (opts.stackSort != 'false') {
3618 for (var i = 0; i < cards.length; ++i) {
3619 // Find the stack with the shortest height, but give preference to
3620 // left to right order.
3621 var minHeight = $stacks[0].height();
3622 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003623
Robert Lye7eeb402014-06-03 19:35:24 -07003624 for (var j = 1; j < numStacks; ++j) {
3625 var height = $stacks[j].height();
3626 if (height < minHeight - 45) {
3627 minHeight = height;
3628 minIndex = j;
3629 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003630 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003631
Robert Lye7eeb402014-06-03 19:35:24 -07003632 $stacks[minIndex].append($(cards[i]));
3633 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003634 }
3635
3636 };
smain@google.com95948b82014-06-16 19:24:25 -07003637
3638 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003639 Create a resource card using the given resource object and a list of html
3640 configured options. Returns a jquery object containing the element.
3641 */
smain@google.com95948b82014-06-16 19:24:25 -07003642 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003643 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003644
Robert Lye7eeb402014-06-03 19:35:24 -07003645 // The difference here is that generic cards are not entirely clickable
3646 // so its a div instead of an a tag, also the generic one is not given
3647 // the resource-card class so it appears with a transparent background
3648 // and can be styled in whatever way the css setup.
3649 if (opts.resourceStyle == 'generic') {
3650 $el = $('<div>')
3651 .addClass('resource')
3652 .attr('href', cleanUrl(resource.url))
3653 .decorateResource(resource, opts);
3654 } else {
3655 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003656
Robert Lye7eeb402014-06-03 19:35:24 -07003657 $el = $('<a>')
3658 .addClass(cls)
3659 .attr('href', cleanUrl(resource.url))
3660 .decorateResourceCard(resource, plusone);
3661 }
smain@google.com95948b82014-06-16 19:24:25 -07003662
Robert Lye7eeb402014-06-03 19:35:24 -07003663 return $el;
3664 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003665
3666 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3667 function drawResourcesFlowWidget($widget, opts, resources) {
3668 $widget.empty();
3669 var cardSizes = opts.cardSizes || ['6x6'];
3670 var i = 0, j = 0;
3671 var plusone = true; // by default show plusone on resource cards
3672
3673 while (i < resources.length) {
3674 var cardSize = cardSizes[j++ % cardSizes.length];
3675 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003676 // Some card sizes do not get a plusone button, such as where space is constrained
3677 // or for cards commonly embedded in docs (to improve overall page speed).
3678 plusone = !((cardSize == "6x2") || (cardSize == "6x3") ||
3679 (cardSize == "9x2") || (cardSize == "9x3") ||
3680 (cardSize == "12x2") || (cardSize == "12x3"));
3681
3682 // A stack has a third dimension which is the number of stacked items
3683 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3684 var stackCount = 0;
3685 var $stackDiv = null;
3686
3687 if (isStack) {
3688 // Create a stack container which should have the dimensions defined
3689 // by the product of the items inside.
3690 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
3691 + 'x' + isStack[2] * isStack[3]) .appendTo($widget);
3692 }
3693
3694 // Build each stack item or just a single item
3695 do {
3696 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003697
Robert Lye7eeb402014-06-03 19:35:24 -07003698 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003699
3700 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003701 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003702
Dirk Doughertyc3921652014-05-13 16:55:26 -07003703 if (isStack) {
3704 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3705 if (++stackCount == parseInt(isStack[3])) {
3706 $card.addClass('resource-card-row-stack-last');
3707 stackCount = 0;
3708 }
3709 } else {
3710 stackCount = 0;
3711 }
3712
Robert Lye7eeb402014-06-03 19:35:24 -07003713 $card.appendTo($stackDiv || $widget);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003714
3715 } while (++i < resources.length && stackCount > 0);
3716 }
3717 }
3718
3719 /* Build a site map of resources using a section as a root. */
3720 function buildSectionList(opts) {
3721 if (opts.section && SECTION_BY_ID[opts.section]) {
3722 return SECTION_BY_ID[opts.section].sections || [];
3723 }
3724 return [];
3725 }
3726
3727 function buildResourceList(opts) {
3728 var maxResults = opts.maxResults || 100;
3729
3730 var query = opts.query || '';
3731 var expressions = parseResourceQuery(query);
3732 var addedResourceIndices = {};
3733 var results = [];
3734
3735 for (var i = 0; i < expressions.length; i++) {
3736 var clauses = expressions[i];
3737
3738 // build initial set of resources from first clause
3739 var firstClause = clauses[0];
3740 var resources = [];
3741 switch (firstClause.attr) {
3742 case 'type':
3743 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3744 break;
3745 case 'lang':
3746 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3747 break;
3748 case 'tag':
3749 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3750 break;
3751 case 'collection':
3752 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3753 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3754 break;
3755 case 'section':
3756 var urls = SITE_MAP[firstClause.value].sections || [];
3757 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3758 break;
3759 }
3760 // console.log(firstClause.attr + ':' + firstClause.value);
3761 resources = resources || [];
3762
3763 // use additional clauses to filter corpus
3764 if (clauses.length > 1) {
3765 var otherClauses = clauses.slice(1);
3766 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3767 }
3768
3769 // filter out resources already added
3770 if (i > 1) {
3771 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3772 }
3773
3774 // add to list of already added indices
3775 for (var j = 0; j < resources.length; j++) {
3776 // console.log(resources[j].title);
3777 addedResourceIndices[resources[j].index] = 1;
3778 }
3779
3780 // concat to final results list
3781 results = results.concat(resources);
3782 }
3783
3784 if (opts.sortOrder && results.length) {
3785 var attr = opts.sortOrder;
3786
3787 if (opts.sortOrder == 'random') {
3788 var i = results.length, j, temp;
3789 while (--i) {
3790 j = Math.floor(Math.random() * (i + 1));
3791 temp = results[i];
3792 results[i] = results[j];
3793 results[j] = temp;
3794 }
3795 } else {
3796 var desc = attr.charAt(0) == '-';
3797 if (desc) {
3798 attr = attr.substring(1);
3799 }
3800 results = results.sort(function(x,y) {
3801 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3802 });
3803 }
3804 }
3805
3806 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3807 results = results.slice(0, maxResults);
3808
3809 for (var j = 0; j < results.length; ++j) {
3810 addedPageResources[results[j].index] = 1;
3811 }
3812
3813 return results;
3814 }
3815
3816
3817 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3818 return function(resource) {
3819 return !addedResourceIndices[resource.index];
3820 };
3821 }
3822
3823
3824 function getResourceMatchesClausesFilter(clauses) {
3825 return function(resource) {
3826 return doesResourceMatchClauses(resource, clauses);
3827 };
3828 }
3829
3830
3831 function doesResourceMatchClauses(resource, clauses) {
3832 for (var i = 0; i < clauses.length; i++) {
3833 var map;
3834 switch (clauses[i].attr) {
3835 case 'type':
3836 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3837 break;
3838 case 'lang':
3839 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3840 break;
3841 case 'tag':
3842 map = IS_RESOURCE_TAGGED[clauses[i].value];
3843 break;
3844 }
3845
3846 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3847 return clauses[i].negative;
3848 }
3849 }
3850 return true;
3851 }
smain@google.com95948b82014-06-16 19:24:25 -07003852
Robert Lye7eeb402014-06-03 19:35:24 -07003853 function cleanUrl(url)
3854 {
3855 if (url && url.indexOf('//') === -1) {
3856 url = toRoot + url;
3857 }
smain@google.com95948b82014-06-16 19:24:25 -07003858
Robert Lye7eeb402014-06-03 19:35:24 -07003859 return url;
3860 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003861
3862
3863 function parseResourceQuery(query) {
3864 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3865 var expressions = [];
3866 var expressionStrs = query.split(',') || [];
3867 for (var i = 0; i < expressionStrs.length; i++) {
3868 var expr = expressionStrs[i] || '';
3869
3870 // Break expression into clauses (clause e.g. 'tag:foo')
3871 var clauses = [];
3872 var clauseStrs = expr.split(/(?=[\+\-])/);
3873 for (var j = 0; j < clauseStrs.length; j++) {
3874 var clauseStr = clauseStrs[j] || '';
3875
3876 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3877 var parts = clauseStr.split(':');
3878 var clause = {};
3879
3880 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3881 if (clause.attr) {
3882 if (clause.attr.charAt(0) == '+') {
3883 clause.attr = clause.attr.substring(1);
3884 } else if (clause.attr.charAt(0) == '-') {
3885 clause.negative = true;
3886 clause.attr = clause.attr.substring(1);
3887 }
3888 }
3889
3890 if (parts.length > 1) {
3891 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3892 }
3893
3894 clauses.push(clause);
3895 }
3896
3897 if (!clauses.length) {
3898 continue;
3899 }
3900
3901 expressions.push(clauses);
3902 }
3903
3904 return expressions;
3905 }
3906})();
3907
3908(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07003909
smain@google.com95948b82014-06-16 19:24:25 -07003910 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003911 Utility method for creating dom for the description area of a card.
3912 Used in decorateResourceCard and decorateResource.
3913 */
3914 function buildResourceCardDescription(resource, plusone) {
3915 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07003916
Robert Lye7eeb402014-06-03 19:35:24 -07003917 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07003918
Robert Lye7eeb402014-06-03 19:35:24 -07003919 if (resource.cta) {
3920 $description.append($('<a>').addClass('cta').html(resource.cta));
3921 }
smain@google.com95948b82014-06-16 19:24:25 -07003922
Robert Lye7eeb402014-06-03 19:35:24 -07003923 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07003924 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07003925 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07003926
Robert Lye7eeb402014-06-03 19:35:24 -07003927 $description.append($('<div>').addClass('util')
3928 .append($('<div>').addClass('g-plusone')
3929 .attr('data-size', 'small')
3930 .attr('data-align', 'right')
3931 .attr('data-href', plusurl)));
3932 }
smain@google.com95948b82014-06-16 19:24:25 -07003933
Robert Lye7eeb402014-06-03 19:35:24 -07003934 return $description;
3935 }
smain@google.com95948b82014-06-16 19:24:25 -07003936
3937
Dirk Doughertyc3921652014-05-13 16:55:26 -07003938 /* Simple jquery function to create dom for a standard resource card */
3939 $.fn.decorateResourceCard = function(resource,plusone) {
3940 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07003941 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07003942 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07003943
Robert Lye7eeb402014-06-03 19:35:24 -07003944 if (imgUrl.indexOf('//') === -1) {
3945 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003946 }
Robert Lye7eeb402014-06-03 19:35:24 -07003947
3948 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07003949 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07003950 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07003951 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07003952
Robert Lye7eeb402014-06-03 19:35:24 -07003953 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
3954 .append($('<div>').addClass('section').text(section))
3955 .append($('<div>').addClass('title').html(resource.title))
3956 .append(buildResourceCardDescription(resource, plusone))
3957 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003958
3959 return this;
3960 };
3961
3962 /* Simple jquery function to create dom for a resource section card (menu) */
3963 $.fn.decorateResourceSection = function(section,plusone) {
3964 var resource = section.resource;
3965 //keep url clean for matching and offline mode handling
3966 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
3967 var $base = $('<a>')
3968 .addClass('card-bg')
3969 .attr('href', resource.url)
3970 .append($('<div>').addClass('card-section-icon')
3971 .append($('<div>').addClass('icon'))
3972 .append($('<div>').addClass('section').html(resource.title)))
3973 .appendTo(this);
3974
3975 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
3976
3977 if (section.sections && section.sections.length) {
3978 // Recurse the section sub-tree to find a resource image.
3979 var stack = [section];
3980
3981 while (stack.length) {
3982 if (stack[0].resource.image) {
3983 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
3984 break;
3985 }
3986
3987 if (stack[0].sections) {
3988 stack = stack.concat(stack[0].sections);
3989 }
3990
3991 stack.shift();
3992 }
3993
3994 var $ul = $('<ul>')
3995 .appendTo($cardInfo);
3996
3997 var max = section.sections.length > 3 ? 3 : section.sections.length;
3998
3999 for (var i = 0; i < max; ++i) {
4000
4001 var subResource = section.sections[i];
4002 if (!plusone) {
4003 $('<li>')
4004 .append($('<a>').attr('href', subResource.url)
4005 .append($('<div>').addClass('title').html(subResource.title))
4006 .append($('<div>').addClass('description ellipsis')
4007 .append($('<div>').addClass('text').html(subResource.summary))
4008 .append($('<div>').addClass('util'))))
4009 .appendTo($ul);
4010 } else {
4011 $('<li>')
4012 .append($('<a>').attr('href', subResource.url)
4013 .append($('<div>').addClass('title').html(subResource.title))
4014 .append($('<div>').addClass('description ellipsis')
4015 .append($('<div>').addClass('text').html(subResource.summary))
4016 .append($('<div>').addClass('util')
4017 .append($('<div>').addClass('g-plusone')
4018 .attr('data-size', 'small')
4019 .attr('data-align', 'right')
4020 .attr('data-href', resource.url)))))
4021 .appendTo($ul);
4022 }
4023 }
4024
4025 // Add a more row
4026 if (max < section.sections.length) {
4027 $('<li>')
4028 .append($('<a>').attr('href', resource.url)
4029 .append($('<div>')
4030 .addClass('title')
4031 .text('More')))
4032 .appendTo($ul);
4033 }
4034 } else {
4035 // No sub-resources, just render description?
4036 }
4037
4038 return this;
4039 };
smain@google.com95948b82014-06-16 19:24:25 -07004040
4041
4042
4043
Robert Lye7eeb402014-06-03 19:35:24 -07004044 /* Render other types of resource styles that are not cards. */
4045 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07004046 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07004047 'assets/images/resource-card-default-android.jpg';
4048 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07004049
Robert Lye7eeb402014-06-03 19:35:24 -07004050 if (imgUrl.indexOf('//') === -1) {
4051 imgUrl = toRoot + imgUrl;
4052 }
smain@google.com95948b82014-06-16 19:24:25 -07004053
Robert Lye7eeb402014-06-03 19:35:24 -07004054 if (linkUrl && linkUrl.indexOf('//') === -1) {
4055 linkUrl = toRoot + linkUrl;
4056 }
4057
4058 $(this).append(
4059 $('<div>').addClass('image')
4060 .css('background-image', 'url(' + imgUrl + ')'),
4061 $('<div>').addClass('info').append(
4062 $('<h4>').addClass('title').html(resource.title),
4063 $('<p>').addClass('summary').html(resource.summary),
4064 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4065 )
4066 );
4067
4068 return this;
4069 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004070})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004071
4072
Dirk Doughertyc3921652014-05-13 16:55:26 -07004073/* Calculate the vertical area remaining */
4074(function($) {
4075 $.fn.ellipsisfade= function(lineHeight) {
4076 this.each(function() {
4077 // get element text
4078 var $this = $(this);
4079 var remainingHeight = $this.parent().parent().height();
4080 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004081 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004082 if ($(this).is(":visible")) {
4083 var h = $(this).height();
4084 remainingHeight = remainingHeight - h;
4085 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004086 });
4087
4088 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4089 $this.parent().css({'height': adjustedRemainingHeight});
4090 $this.css({'height': "auto"});
4091 });
4092
4093 return this;
4094 };
4095}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004096
4097/*
4098 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004099
Robert Lye7eeb402014-06-03 19:35:24 -07004100 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004101 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004102 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004103
Robert Lye7eeb402014-06-03 19:35:24 -07004104 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004105
Robert Lye7eeb402014-06-03 19:35:24 -07004106 <div class="fullscreen-carousel">
4107 <div class="fullscreen-carousel-content">
4108 <!-- content here -->
4109 </div>
4110 <div class="fullscreen-carousel-content">
4111 <!-- content here -->
4112 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004113
Robert Lye7eeb402014-06-03 19:35:24 -07004114 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004115
Robert Lye7eeb402014-06-03 19:35:24 -07004116 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004117
Robert Lye7eeb402014-06-03 19:35:24 -07004118 Control over how the carousel takes over the screen can mostly be defined in
4119 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004120 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004121 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004122 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004123 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004124
Robert Lye7eeb402014-06-03 19:35:24 -07004125 There is limited functionality for having multiple sections since that request
4126 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4127 scroll between multiple content areas.
4128*/
4129
4130(function() {
4131 $(document).ready(function() {
4132 $('.fullscreen-carousel').each(function() {
4133 initWidget(this);
4134 });
4135 });
4136
4137 function initWidget(widget) {
4138 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004139
Robert Lye7eeb402014-06-03 19:35:24 -07004140 var topOffset = $widget.offset().top;
4141 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4142 var maxHeight = 0;
4143 var minHeight = 0;
4144 var $content = $widget.find('.fullscreen-carousel-content');
4145 var $nextArrow = $widget.find('.next-arrow');
4146 var $prevArrow = $widget.find('.prev-arrow');
4147 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004148
Robert Lye7eeb402014-06-03 19:35:24 -07004149 if ($content.length <= 1) {
4150 $nextArrow.hide();
4151 $prevArrow.hide();
4152 } else {
4153 $nextArrow.click(function() {
4154 var index = ($content.index($curSection) + 1);
4155 $curSection.hide();
4156 $curSection = $($content[index >= $content.length ? 0 : index]);
4157 $curSection.show();
4158 });
smain@google.com95948b82014-06-16 19:24:25 -07004159
Robert Lye7eeb402014-06-03 19:35:24 -07004160 $prevArrow.click(function() {
4161 var index = ($content.index($curSection) - 1);
4162 $curSection.hide();
4163 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4164 $curSection.show();
4165 });
4166 }
4167
4168 // Just hide all content sections except first.
4169 $content.each(function(index) {
4170 if ($(this).height() > minHeight) minHeight = $(this).height();
4171 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4172 });
4173
4174 // Register for changes to window size, and trigger.
4175 $(window).resize(resizeWidget);
4176 resizeWidget();
4177
4178 function resizeWidget() {
4179 var height = $(window).height() - topOffset - padBottom;
4180 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004181 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004182 (maxHeight && height > maxHeight ? maxHeight : height));
4183 }
smain@google.com95948b82014-06-16 19:24:25 -07004184 }
Robert Lye7eeb402014-06-03 19:35:24 -07004185})();
4186
4187
4188
4189
4190
4191/*
4192 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004193
Robert Lye7eeb402014-06-03 19:35:24 -07004194 The following allows tab widgets to be installed via the html below. Each
4195 tab content section should have a data-tab attribute matching one of the
4196 nav items'. Also each tab content section should have a width matching the
4197 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004198
Robert Lye7eeb402014-06-03 19:35:24 -07004199 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004200
Robert Lye7eeb402014-06-03 19:35:24 -07004201 <div class="tab-carousel">
4202 <ul class="tab-nav">
4203 <li><a href="#" data-tab="handsets">Handsets</a>
4204 <li><a href="#" data-tab="wearable">Wearable</a>
4205 <li><a href="#" data-tab="tv">TV</a>
4206 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004207
Robert Lye7eeb402014-06-03 19:35:24 -07004208 <div class="tab-carousel-content">
4209 <div data-tab="handsets">
4210 <!--Full width content here-->
4211 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004212
Robert Lye7eeb402014-06-03 19:35:24 -07004213 <div data-tab="wearable">
4214 <!--Full width content here-->
4215 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004216
Robert Lye7eeb402014-06-03 19:35:24 -07004217 <div data-tab="tv">
4218 <!--Full width content here-->
4219 </div>
4220 </div>
4221 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004222
Robert Lye7eeb402014-06-03 19:35:24 -07004223*/
4224(function() {
4225 $(document).ready(function() {
4226 $('.tab-carousel').each(function() {
4227 initWidget(this);
4228 });
4229 });
4230
4231 function initWidget(widget) {
4232 var $widget = $(widget);
4233 var $nav = $widget.find('.tab-nav');
4234 var $anchors = $nav.find('[data-tab]');
4235 var $li = $nav.find('li');
4236 var $contentContainer = $widget.find('.tab-carousel-content');
4237 var $tabs = $contentContainer.find('[data-tab]');
4238 var $curTab = $($tabs[0]); // Current tab is first tab.
4239 var width = $widget.width();
4240
4241 // Setup nav interactivity.
4242 $anchors.click(function(evt) {
4243 evt.preventDefault();
4244 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004245 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004246 });
smain@google.com95948b82014-06-16 19:24:25 -07004247
Robert Lye7eeb402014-06-03 19:35:24 -07004248 // Add highlight for navigation on first item.
4249 var $highlight = $('<div>').addClass('highlight')
4250 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4251 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004252
Robert Lye7eeb402014-06-03 19:35:24 -07004253 // Store height since we will change contents to absolute.
4254 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004255
Robert Lye7eeb402014-06-03 19:35:24 -07004256 // Absolutely position tabs so they're ready for transition.
4257 $tabs.each(function(index) {
4258 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4259 });
smain@google.com95948b82014-06-16 19:24:25 -07004260
Robert Lye7eeb402014-06-03 19:35:24 -07004261 function transitionWidget($toTab) {
4262 if (!$curTab.is($toTab)) {
4263 var curIndex = $tabs.index($curTab[0]);
4264 var toIndex = $tabs.index($toTab[0]);
4265 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004266
Robert Lye7eeb402014-06-03 19:35:24 -07004267 // Animate content sections.
4268 $toTab.css({left:(width * dir) + 'px'});
4269 $curTab.animate({left:(width * -dir) + 'px'});
4270 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004271
Robert Lye7eeb402014-06-03 19:35:24 -07004272 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004273 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004274 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004275
Robert Lye7eeb402014-06-03 19:35:24 -07004276 // Store new current section.
4277 $curTab = $toTab;
4278 }
4279 }
smain@google.com95948b82014-06-16 19:24:25 -07004280 }
Dirk Doughertyb87e3002014-11-18 19:34:34 -08004281})();