blob: 08a02223c3596316b6d0386476ab60b9eaea2f98 [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() {
Scott Main7e447ed2013-02-19 17:22:37 -080023
Scott Main0e76e7e2013-03-12 10:24:07 -070024 // load json file for JD doc search suggestions
Scott Main719acb42013-12-05 16:05:09 -080025 $.getScript(toRoot + 'jd_lists_unified.js');
Scott Main7e447ed2013-02-19 17:22:37 -080026 // load json file for Android API search suggestions
27 $.getScript(toRoot + 'reference/lists.js');
28 // load json files for Google services API suggestions
Scott Main9f2971d2013-02-26 13:07:41 -080029 $.getScript(toRoot + 'reference/gcm_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080030 // once the GCM json (GCM_DATA) is loaded, load the GMS json (GMS_DATA) and merge the data
31 if(jqxhr.status === 200) {
Scott Main9f2971d2013-02-26 13:07:41 -080032 $.getScript(toRoot + 'reference/gms_lists.js', function(data, textStatus, jqxhr) {
Scott Main7e447ed2013-02-19 17:22:37 -080033 if(jqxhr.status === 200) {
34 // combine GCM and GMS data
35 GOOGLE_DATA = GMS_DATA;
36 var start = GOOGLE_DATA.length;
37 for (var i=0; i<GCM_DATA.length; i++) {
38 GOOGLE_DATA.push({id:start+i, label:GCM_DATA[i].label,
39 link:GCM_DATA[i].link, type:GCM_DATA[i].type});
40 }
41 }
42 });
43 }
44 });
45
Scott Main0e76e7e2013-03-12 10:24:07 -070046 // setup keyboard listener for search shortcut
47 $('body').keyup(function(event) {
48 if (event.which == 191) {
49 $('#search_autocomplete').focus();
50 }
51 });
Scott Main015d6162013-01-29 09:01:52 -080052
Scott Maine4d8f1b2012-06-21 18:03:05 -070053 // init the fullscreen toggle click event
54 $('#nav-swap .fullscreen').click(function(){
55 if ($(this).hasClass('disabled')) {
56 toggleFullscreen(true);
57 } else {
58 toggleFullscreen(false);
59 }
60 });
Scott Main3b90aff2013-08-01 18:09:35 -070061
Scott Maine4d8f1b2012-06-21 18:03:05 -070062 // initialize the divs with custom scrollbars
63 $('.scroll-pane').jScrollPane( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -070064
Scott Maine4d8f1b2012-06-21 18:03:05 -070065 // add HRs below all H2s (except for a few other h2 variants)
Scott Mainc29b3f52014-05-30 21:18:30 -070066 $('h2').not('#qv h2')
67 .not('#tb h2')
68 .not('.sidebox h2')
69 .not('#devdoc-nav h2')
70 .not('h2.norule').css({marginBottom:0})
71 .after('<hr/>');
Scott Maine4d8f1b2012-06-21 18:03:05 -070072
73 // set up the search close button
74 $('.search .close').click(function() {
75 $searchInput = $('#search_autocomplete');
76 $searchInput.attr('value', '');
77 $(this).addClass("hide");
78 $("#search-container").removeClass('active');
79 $("#search_autocomplete").blur();
Scott Main0e76e7e2013-03-12 10:24:07 -070080 search_focus_changed($searchInput.get(), false);
81 hideResults();
Scott Maine4d8f1b2012-06-21 18:03:05 -070082 });
83
84 // Set up quicknav
Scott Main3b90aff2013-08-01 18:09:35 -070085 var quicknav_open = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -070086 $("#btn-quicknav").click(function() {
87 if (quicknav_open) {
88 $(this).removeClass('active');
89 quicknav_open = false;
90 collapse();
91 } else {
92 $(this).addClass('active');
93 quicknav_open = true;
94 expand();
95 }
96 })
Scott Main3b90aff2013-08-01 18:09:35 -070097
Scott Maine4d8f1b2012-06-21 18:03:05 -070098 var expand = function() {
99 $('#header-wrap').addClass('quicknav');
100 $('#quicknav').stop().show().animate({opacity:'1'});
101 }
Scott Main3b90aff2013-08-01 18:09:35 -0700102
Scott Maine4d8f1b2012-06-21 18:03:05 -0700103 var collapse = function() {
104 $('#quicknav').stop().animate({opacity:'0'}, 100, function() {
105 $(this).hide();
106 $('#header-wrap').removeClass('quicknav');
107 });
108 }
Scott Main3b90aff2013-08-01 18:09:35 -0700109
110
Scott Maine4d8f1b2012-06-21 18:03:05 -0700111 //Set up search
112 $("#search_autocomplete").focus(function() {
113 $("#search-container").addClass('active');
114 })
115 $("#search-container").mouseover(function() {
116 $("#search-container").addClass('active');
117 $("#search_autocomplete").focus();
118 })
119 $("#search-container").mouseout(function() {
120 if ($("#search_autocomplete").is(":focus")) return;
121 if ($("#search_autocomplete").val() == '') {
122 setTimeout(function(){
123 $("#search-container").removeClass('active');
124 $("#search_autocomplete").blur();
125 },250);
126 }
127 })
128 $("#search_autocomplete").blur(function() {
129 if ($("#search_autocomplete").val() == '') {
130 $("#search-container").removeClass('active');
131 }
132 })
133
Scott Main3b90aff2013-08-01 18:09:35 -0700134
Scott Maine4d8f1b2012-06-21 18:03:05 -0700135 // prep nav expandos
136 var pagePath = document.location.pathname;
137 // account for intl docs by removing the intl/*/ path
138 if (pagePath.indexOf("/intl/") == 0) {
139 pagePath = pagePath.substr(pagePath.indexOf("/",6)); // start after intl/ to get last /
140 }
Scott Mainac2aef52013-02-12 14:15:23 -0800141
Scott Maine4d8f1b2012-06-21 18:03:05 -0700142 if (pagePath.indexOf(SITE_ROOT) == 0) {
143 if (pagePath == '' || pagePath.charAt(pagePath.length - 1) == '/') {
144 pagePath += 'index.html';
145 }
146 }
147
Scott Main01a25452013-02-12 17:32:27 -0800148 // Need a copy of the pagePath before it gets changed in the next block;
149 // it's needed to perform proper tab highlighting in offline docs (see rootDir below)
150 var pagePathOriginal = pagePath;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700151 if (SITE_ROOT.match(/\.\.\//) || SITE_ROOT == '') {
152 // If running locally, SITE_ROOT will be a relative path, so account for that by
153 // finding the relative URL to this page. This will allow us to find links on the page
154 // leading back to this page.
155 var pathParts = pagePath.split('/');
156 var relativePagePathParts = [];
157 var upDirs = (SITE_ROOT.match(/(\.\.\/)+/) || [''])[0].length / 3;
158 for (var i = 0; i < upDirs; i++) {
159 relativePagePathParts.push('..');
160 }
161 for (var i = 0; i < upDirs; i++) {
162 relativePagePathParts.push(pathParts[pathParts.length - (upDirs - i) - 1]);
163 }
164 relativePagePathParts.push(pathParts[pathParts.length - 1]);
165 pagePath = relativePagePathParts.join('/');
166 } else {
167 // Otherwise the page path is already an absolute URL
168 }
169
Scott Mainac2aef52013-02-12 14:15:23 -0800170 // Highlight the header tabs...
171 // highlight Design tab
172 if ($("body").hasClass("design")) {
173 $("#header li.design a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700174 $("#sticky-header").addClass("design");
Scott Mainac2aef52013-02-12 14:15:23 -0800175
smain@google.com6040ffa2014-06-13 15:06:23 -0700176 // highlight About tabs
177 } else if ($("body").hasClass("about")) {
178 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
179 if (rootDir == "about") {
180 $("#nav-x li.about a").addClass("selected");
181 } else if (rootDir == "wear") {
182 $("#nav-x li.wear a").addClass("selected");
183 } else if (rootDir == "tv") {
184 $("#nav-x li.tv a").addClass("selected");
185 } else if (rootDir == "auto") {
186 $("#nav-x li.auto a").addClass("selected");
187 }
Scott Mainac2aef52013-02-12 14:15:23 -0800188 // highlight Develop tab
189 } else if ($("body").hasClass("develop") || $("body").hasClass("google")) {
190 $("#header li.develop a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700191 $("#sticky-header").addClass("develop");
Scott Mainac2aef52013-02-12 14:15:23 -0800192 // In Develop docs, also highlight appropriate sub-tab
Scott Main01a25452013-02-12 17:32:27 -0800193 var rootDir = pagePathOriginal.substring(1,pagePathOriginal.indexOf('/', 1));
Scott Mainac2aef52013-02-12 14:15:23 -0800194 if (rootDir == "training") {
195 $("#nav-x li.training a").addClass("selected");
196 } else if (rootDir == "guide") {
197 $("#nav-x li.guide a").addClass("selected");
198 } else if (rootDir == "reference") {
199 // If the root is reference, but page is also part of Google Services, select Google
200 if ($("body").hasClass("google")) {
201 $("#nav-x li.google a").addClass("selected");
202 } else {
203 $("#nav-x li.reference a").addClass("selected");
204 }
205 } else if ((rootDir == "tools") || (rootDir == "sdk")) {
206 $("#nav-x li.tools a").addClass("selected");
207 } else if ($("body").hasClass("google")) {
208 $("#nav-x li.google a").addClass("selected");
Dirk Dougherty4f7e5152010-09-16 10:43:40 -0700209 } else if ($("body").hasClass("samples")) {
210 $("#nav-x li.samples a").addClass("selected");
Scott Mainac2aef52013-02-12 14:15:23 -0800211 }
212
213 // highlight Distribute tab
214 } else if ($("body").hasClass("distribute")) {
215 $("#header li.distribute a").addClass("selected");
Dirk Doughertyc3921652014-05-13 16:55:26 -0700216 $("#sticky-header").addClass("distribute");
217
218 var baseFrag = pagePathOriginal.indexOf('/', 1) + 1;
219 var secondFrag = pagePathOriginal.substring(baseFrag, pagePathOriginal.indexOf('/', baseFrag));
220 if (secondFrag == "users") {
221 $("#nav-x li.users a").addClass("selected");
222 } else if (secondFrag == "engage") {
223 $("#nav-x li.engage a").addClass("selected");
224 } else if (secondFrag == "monetize") {
225 $("#nav-x li.monetize a").addClass("selected");
226 } else if (secondFrag == "tools") {
227 $("#nav-x li.disttools a").addClass("selected");
228 } else if (secondFrag == "stories") {
229 $("#nav-x li.stories a").addClass("selected");
230 } else if (secondFrag == "essentials") {
231 $("#nav-x li.essentials a").addClass("selected");
232 } else if (secondFrag == "googleplay") {
233 $("#nav-x li.googleplay a").addClass("selected");
234 }
235 } else if ($("body").hasClass("about")) {
236 $("#sticky-header").addClass("about");
Scott Mainb16376f2014-05-21 20:35:47 -0700237 }
Scott Mainac2aef52013-02-12 14:15:23 -0800238
Scott Mainf6145542013-04-01 16:38:11 -0700239 // set global variable so we can highlight the sidenav a bit later (such as for google reference)
240 // and highlight the sidenav
241 mPagePath = pagePath;
242 highlightSidenav();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700243 buildBreadcrumbs();
Scott Mainac2aef52013-02-12 14:15:23 -0800244
Scott Mainf6145542013-04-01 16:38:11 -0700245 // set up prev/next links if they exist
Scott Maine4d8f1b2012-06-21 18:03:05 -0700246 var $selNavLink = $('#nav').find('a[href="' + pagePath + '"]');
Scott Main5a1123e2012-09-26 12:51:28 -0700247 var $selListItem;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700248 if ($selNavLink.length) {
Scott Mainac2aef52013-02-12 14:15:23 -0800249 $selListItem = $selNavLink.closest('li');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700250
251 // set up prev links
252 var $prevLink = [];
253 var $prevListItem = $selListItem.prev('li');
Scott Main3b90aff2013-08-01 18:09:35 -0700254
Scott Maine4d8f1b2012-06-21 18:03:05 -0700255 var crossBoundaries = ($("body.design").length > 0) || ($("body.guide").length > 0) ? true :
256false; // navigate across topic boundaries only in design docs
257 if ($prevListItem.length) {
smain@google.comc91ecb72014-06-23 10:22:23 -0700258 if ($prevListItem.hasClass('nav-section') || crossBoundaries) {
Scott Main5a1123e2012-09-26 12:51:28 -0700259 // jump to last topic of previous section
260 $prevLink = $prevListItem.find('a:last');
261 } else if (!$selListItem.hasClass('nav-section')) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700262 // jump to previous topic in this section
263 $prevLink = $prevListItem.find('a:eq(0)');
264 }
265 } else {
266 // jump to this section's index page (if it exists)
267 var $parentListItem = $selListItem.parents('li');
268 $prevLink = $selListItem.parents('li').find('a');
Scott Main3b90aff2013-08-01 18:09:35 -0700269
Scott Maine4d8f1b2012-06-21 18:03:05 -0700270 // except if cross boundaries aren't allowed, and we're at the top of a section already
271 // (and there's another parent)
Scott Main3b90aff2013-08-01 18:09:35 -0700272 if (!crossBoundaries && $parentListItem.hasClass('nav-section')
Scott Maine4d8f1b2012-06-21 18:03:05 -0700273 && $selListItem.hasClass('nav-section')) {
274 $prevLink = [];
275 }
276 }
277
Scott Maine4d8f1b2012-06-21 18:03:05 -0700278 // set up next links
279 var $nextLink = [];
Scott Maine4d8f1b2012-06-21 18:03:05 -0700280 var startClass = false;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700281 var isCrossingBoundary = false;
Scott Main3b90aff2013-08-01 18:09:35 -0700282
Scott Main1a00f7f2013-10-29 11:11:19 -0700283 if ($selListItem.hasClass('nav-section') && $selListItem.children('div.empty').length == 0) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700284 // we're on an index page, jump to the first topic
Scott Mainb505ca62012-07-26 18:00:14 -0700285 $nextLink = $selListItem.find('ul:eq(0)').find('a:eq(0)');
Scott Maine4d8f1b2012-06-21 18:03:05 -0700286
287 // if there aren't any children, go to the next section (required for About pages)
288 if($nextLink.length == 0) {
289 $nextLink = $selListItem.next('li').find('a');
Scott Mainb505ca62012-07-26 18:00:14 -0700290 } else if ($('.topic-start-link').length) {
291 // as long as there's a child link and there is a "topic start link" (we're on a landing)
292 // then set the landing page "start link" text to be the first doc title
293 $('.topic-start-link').text($nextLink.text().toUpperCase());
Scott Maine4d8f1b2012-06-21 18:03:05 -0700294 }
Scott Main3b90aff2013-08-01 18:09:35 -0700295
Scott Main5a1123e2012-09-26 12:51:28 -0700296 // If the selected page has a description, then it's a class or article homepage
297 if ($selListItem.find('a[description]').length) {
298 // this means we're on a class landing page
Scott Maine4d8f1b2012-06-21 18:03:05 -0700299 startClass = true;
300 }
301 } else {
302 // jump to the next topic in this section (if it exists)
303 $nextLink = $selListItem.next('li').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700304 if ($nextLink.length == 0) {
Scott Main5a1123e2012-09-26 12:51:28 -0700305 isCrossingBoundary = true;
306 // no more topics in this section, jump to the first topic in the next section
smain@google.comabf34112014-06-23 11:39:02 -0700307 $nextLink = $selListItem.parents('li:eq(0)').next('li').find('a:eq(0)');
Scott Main5a1123e2012-09-26 12:51:28 -0700308 if (!$nextLink.length) { // Go up another layer to look for next page (lesson > class > course)
309 $nextLink = $selListItem.parents('li:eq(1)').next('li.nav-section').find('a:eq(0)');
Scott Main1a00f7f2013-10-29 11:11:19 -0700310 if ($nextLink.length == 0) {
311 // if that doesn't work, we're at the end of the list, so disable NEXT link
312 $('.next-page-link').attr('href','').addClass("disabled")
313 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700314 // and completely hide the one in the footer
315 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700316 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700317 }
318 }
319 }
Scott Main5a1123e2012-09-26 12:51:28 -0700320
321 if (startClass) {
322 $('.start-class-link').attr('href', $nextLink.attr('href')).removeClass("hide");
323
Scott Main3b90aff2013-08-01 18:09:35 -0700324 // if there's no training bar (below the start button),
Scott Main5a1123e2012-09-26 12:51:28 -0700325 // then we need to add a bottom border to button
326 if (!$("#tb").length) {
327 $('.start-class-link').css({'border-bottom':'1px solid #DADADA'});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700328 }
Scott Main5a1123e2012-09-26 12:51:28 -0700329 } else if (isCrossingBoundary && !$('body.design').length) { // Design always crosses boundaries
330 $('.content-footer.next-class').show();
331 $('.next-page-link').attr('href','')
332 .removeClass("hide").addClass("disabled")
333 .click(function() { return false; });
smain@google.comc91ecb72014-06-23 10:22:23 -0700334 // and completely hide the one in the footer
335 $('.content-footer .next-page-link').hide();
Scott Main1a00f7f2013-10-29 11:11:19 -0700336 if ($nextLink.length) {
337 $('.next-class-link').attr('href',$nextLink.attr('href'))
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700338 .removeClass("hide")
339 .append(": " + $nextLink.html());
Scott Main1a00f7f2013-10-29 11:11:19 -0700340 $('.next-class-link').find('.new').empty();
341 }
Scott Main5a1123e2012-09-26 12:51:28 -0700342 } else {
smain@google.com5bc3a1a2014-06-17 20:02:53 -0700343 $('.next-page-link').attr('href', $nextLink.attr('href'))
344 .removeClass("hide");
345 // for the footer link, also add the next page title
346 $('.content-footer .next-page-link').append(": " + $nextLink.html());
Scott Main5a1123e2012-09-26 12:51:28 -0700347 }
348
349 if (!startClass && $prevLink.length) {
350 var prevHref = $prevLink.attr('href');
351 if (prevHref == SITE_ROOT + 'index.html') {
352 // Don't show Previous when it leads to the homepage
353 } else {
354 $('.prev-page-link').attr('href', $prevLink.attr('href')).removeClass("hide");
355 }
Scott Main3b90aff2013-08-01 18:09:35 -0700356 }
Scott Main5a1123e2012-09-26 12:51:28 -0700357
Scott Maine4d8f1b2012-06-21 18:03:05 -0700358 }
Scott Main3b90aff2013-08-01 18:09:35 -0700359
360
361
Scott Main5a1123e2012-09-26 12:51:28 -0700362 // Set up the course landing pages for Training with class names and descriptions
363 if ($('body.trainingcourse').length) {
364 var $classLinks = $selListItem.find('ul li a').not('#nav .nav-section .nav-section ul a');
Scott Maine7d75352014-05-22 15:50:56 -0700365
366 // create an array for all the class descriptions
367 var $classDescriptions = new Array($classLinks.length);
368 var lang = getLangPref();
369 $classLinks.each(function(index) {
370 var langDescr = $(this).attr(lang + "-description");
371 if (typeof langDescr !== 'undefined' && langDescr !== false) {
372 // if there's a class description in the selected language, use that
373 $classDescriptions[index] = langDescr;
374 } else {
375 // otherwise, use the default english description
376 $classDescriptions[index] = $(this).attr("description");
377 }
378 });
Scott Main3b90aff2013-08-01 18:09:35 -0700379
Scott Main5a1123e2012-09-26 12:51:28 -0700380 var $olClasses = $('<ol class="class-list"></ol>');
381 var $liClass;
382 var $imgIcon;
383 var $h2Title;
384 var $pSummary;
385 var $olLessons;
386 var $liLesson;
387 $classLinks.each(function(index) {
388 $liClass = $('<li></li>');
389 $h2Title = $('<a class="title" href="'+$(this).attr('href')+'"><h2>' + $(this).html()+'</h2><span></span></a>');
Scott Maine7d75352014-05-22 15:50:56 -0700390 $pSummary = $('<p class="description">' + $classDescriptions[index] + '</p>');
Scott Main3b90aff2013-08-01 18:09:35 -0700391
Scott Main5a1123e2012-09-26 12:51:28 -0700392 $olLessons = $('<ol class="lesson-list"></ol>');
Scott Main3b90aff2013-08-01 18:09:35 -0700393
Scott Main5a1123e2012-09-26 12:51:28 -0700394 $lessons = $(this).closest('li').find('ul li a');
Scott Main3b90aff2013-08-01 18:09:35 -0700395
Scott Main5a1123e2012-09-26 12:51:28 -0700396 if ($lessons.length) {
Scott Main3b90aff2013-08-01 18:09:35 -0700397 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-tutorial.png" '
398 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700399 $lessons.each(function(index) {
400 $olLessons.append('<li><a href="'+$(this).attr('href')+'">' + $(this).html()+'</a></li>');
401 });
402 } else {
Scott Main3b90aff2013-08-01 18:09:35 -0700403 $imgIcon = $('<img src="'+toRoot+'assets/images/resource-article.png" '
404 + ' width="64" height="64" alt=""/>');
Scott Main5a1123e2012-09-26 12:51:28 -0700405 $pSummary.addClass('article');
406 }
407
408 $liClass.append($h2Title).append($imgIcon).append($pSummary).append($olLessons);
409 $olClasses.append($liClass);
410 });
411 $('.jd-descr').append($olClasses);
412 }
413
Scott Maine4d8f1b2012-06-21 18:03:05 -0700414 // Set up expand/collapse behavior
Scott Mainad08f072013-08-20 16:49:57 -0700415 initExpandableNavItems("#nav");
Scott Main3b90aff2013-08-01 18:09:35 -0700416
Scott Main3b90aff2013-08-01 18:09:35 -0700417
Scott Maine4d8f1b2012-06-21 18:03:05 -0700418 $(".scroll-pane").scroll(function(event) {
419 event.preventDefault();
420 return false;
421 });
422
423 /* Resize nav height when window height changes */
424 $(window).resize(function() {
425 if ($('#side-nav').length == 0) return;
426 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
427 setNavBarLeftPos(); // do this even if sidenav isn't fixed because it could become fixed
428 // make sidenav behave when resizing the window and side-scolling is a concern
Scott Mainf5257812014-05-22 17:26:38 -0700429 if (sticky) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700430 if ((stylesheet.attr("disabled") == "disabled") || stylesheet.length == 0) {
431 updateSideNavPosition();
432 } else {
433 updateSidenavFullscreenWidth();
434 }
435 }
436 resizeNav();
437 });
438
439
Scott Maine4d8f1b2012-06-21 18:03:05 -0700440 var navBarLeftPos;
441 if ($('#devdoc-nav').length) {
442 setNavBarLeftPos();
443 }
444
445
Scott Maine4d8f1b2012-06-21 18:03:05 -0700446 // Set up play-on-hover <video> tags.
447 $('video.play-on-hover').bind('click', function(){
448 $(this).get(0).load(); // in case the video isn't seekable
449 $(this).get(0).play();
450 });
451
452 // Set up tooltips
453 var TOOLTIP_MARGIN = 10;
Scott Maindb3678b2012-10-23 14:13:41 -0700454 $('acronym,.tooltip-link').each(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700455 var $target = $(this);
456 var $tooltip = $('<div>')
457 .addClass('tooltip-box')
Scott Maindb3678b2012-10-23 14:13:41 -0700458 .append($target.attr('title'))
Scott Maine4d8f1b2012-06-21 18:03:05 -0700459 .hide()
460 .appendTo('body');
461 $target.removeAttr('title');
462
463 $target.hover(function() {
464 // in
465 var targetRect = $target.offset();
466 targetRect.width = $target.width();
467 targetRect.height = $target.height();
468
469 $tooltip.css({
470 left: targetRect.left,
471 top: targetRect.top + targetRect.height + TOOLTIP_MARGIN
472 });
473 $tooltip.addClass('below');
474 $tooltip.show();
475 }, function() {
476 // out
477 $tooltip.hide();
478 });
479 });
480
481 // Set up <h2> deeplinks
482 $('h2').click(function() {
483 var id = $(this).attr('id');
484 if (id) {
485 document.location.hash = id;
486 }
487 });
488
489 //Loads the +1 button
490 var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
491 po.src = 'https://apis.google.com/js/plusone.js';
492 var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
493
494
Scott Main3b90aff2013-08-01 18:09:35 -0700495 // Revise the sidenav widths to make room for the scrollbar
Scott Maine4d8f1b2012-06-21 18:03:05 -0700496 // which avoids the visible width from changing each time the bar appears
497 var $sidenav = $("#side-nav");
498 var sidenav_width = parseInt($sidenav.innerWidth());
Scott Main3b90aff2013-08-01 18:09:35 -0700499
Scott Maine4d8f1b2012-06-21 18:03:05 -0700500 $("#devdoc-nav #nav").css("width", sidenav_width - 4 + "px"); // 4px is scrollbar width
501
502
503 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
Scott Main3b90aff2013-08-01 18:09:35 -0700504
Scott Maine4d8f1b2012-06-21 18:03:05 -0700505 if ($(".scroll-pane").length > 1) {
506 // Check if there's a user preference for the panel heights
507 var cookieHeight = readCookie("reference_height");
508 if (cookieHeight) {
509 restoreHeight(cookieHeight);
510 }
511 }
Scott Main3b90aff2013-08-01 18:09:35 -0700512
Scott Main06f3f2c2014-05-30 11:23:00 -0700513 // Resize once loading is finished
Scott Maine4d8f1b2012-06-21 18:03:05 -0700514 resizeNav();
Scott Main06f3f2c2014-05-30 11:23:00 -0700515 // Check if there's an anchor that we need to scroll into view.
516 // A delay is needed, because some browsers do not immediately scroll down to the anchor
517 window.setTimeout(offsetScrollForSticky, 100);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700518
Scott Main015d6162013-01-29 09:01:52 -0800519 /* init the language selector based on user cookie for lang */
520 loadLangPref();
521 changeNavLang(getLangPref());
522
523 /* setup event handlers to ensure the overflow menu is visible while picking lang */
524 $("#language select")
525 .mousedown(function() {
526 $("div.morehover").addClass("hover"); })
527 .blur(function() {
528 $("div.morehover").removeClass("hover"); });
529
530 /* some global variable setup */
531 resizePackagesNav = $("#resize-packages-nav");
532 classesNav = $("#classes-nav");
533 devdocNav = $("#devdoc-nav");
534
535 var cookiePath = "";
536 if (location.href.indexOf("/reference/") != -1) {
537 cookiePath = "reference_";
538 } else if (location.href.indexOf("/guide/") != -1) {
539 cookiePath = "guide_";
540 } else if (location.href.indexOf("/tools/") != -1) {
541 cookiePath = "tools_";
542 } else if (location.href.indexOf("/training/") != -1) {
543 cookiePath = "training_";
544 } else if (location.href.indexOf("/design/") != -1) {
545 cookiePath = "design_";
546 } else if (location.href.indexOf("/distribute/") != -1) {
547 cookiePath = "distribute_";
548 }
Scott Maine4d8f1b2012-06-21 18:03:05 -0700549
550});
Scott Main7e447ed2013-02-19 17:22:37 -0800551// END of the onload event
Scott Maine4d8f1b2012-06-21 18:03:05 -0700552
553
Scott Mainad08f072013-08-20 16:49:57 -0700554function initExpandableNavItems(rootTag) {
555 $(rootTag + ' li.nav-section .nav-section-header').click(function() {
556 var section = $(this).closest('li.nav-section');
557 if (section.hasClass('expanded')) {
Scott Mainf0093852013-08-22 11:37:11 -0700558 /* hide me and descendants */
559 section.find('ul').slideUp(250, function() {
560 // remove 'expanded' class from my section and any children
Scott Mainad08f072013-08-20 16:49:57 -0700561 section.closest('li').removeClass('expanded');
Scott Mainf0093852013-08-22 11:37:11 -0700562 $('li.nav-section', section).removeClass('expanded');
Scott Mainad08f072013-08-20 16:49:57 -0700563 resizeNav();
564 });
565 } else {
566 /* show me */
567 // first hide all other siblings
Scott Main70557ee2013-10-30 14:47:40 -0700568 var $others = $('li.nav-section.expanded', $(this).closest('ul')).not('.sticky');
Scott Mainad08f072013-08-20 16:49:57 -0700569 $others.removeClass('expanded').children('ul').slideUp(250);
570
571 // now expand me
572 section.closest('li').addClass('expanded');
573 section.children('ul').slideDown(250, function() {
574 resizeNav();
575 });
576 }
577 });
Scott Mainf0093852013-08-22 11:37:11 -0700578
579 // Stop expand/collapse behavior when clicking on nav section links
580 // (since we're navigating away from the page)
581 // This selector captures the first instance of <a>, but not those with "#" as the href.
582 $('.nav-section-header').find('a:eq(0)').not('a[href="#"]').click(function(evt) {
583 window.location.href = $(this).attr('href');
584 return false;
585 });
Scott Mainad08f072013-08-20 16:49:57 -0700586}
587
Dirk Doughertyc3921652014-05-13 16:55:26 -0700588
589/** Create the list of breadcrumb links in the sticky header */
590function buildBreadcrumbs() {
591 var $breadcrumbUl = $("#sticky-header ul.breadcrumb");
592 // Add the secondary horizontal nav item, if provided
593 var $selectedSecondNav = $("div#nav-x ul.nav-x a.selected").clone().removeClass("selected");
594 if ($selectedSecondNav.length) {
595 $breadcrumbUl.prepend($("<li>").append($selectedSecondNav))
596 }
597 // Add the primary horizontal nav
598 var $selectedFirstNav = $("div#header-wrap ul.nav-x a.selected").clone().removeClass("selected");
599 // If there's no header nav item, use the logo link and title from alt text
600 if ($selectedFirstNav.length < 1) {
601 $selectedFirstNav = $("<a>")
602 .attr('href', $("div#header .logo a").attr('href'))
603 .text($("div#header .logo img").attr('alt'));
604 }
605 $breadcrumbUl.prepend($("<li>").append($selectedFirstNav));
606}
607
608
609
Scott Maine624b3f2013-09-12 12:56:41 -0700610/** Highlight the current page in sidenav, expanding children as appropriate */
Scott Mainf6145542013-04-01 16:38:11 -0700611function highlightSidenav() {
Scott Maine624b3f2013-09-12 12:56:41 -0700612 // if something is already highlighted, undo it. This is for dynamic navigation (Samples index)
613 if ($("ul#nav li.selected").length) {
614 unHighlightSidenav();
615 }
616 // look for URL in sidenav, including the hash
617 var $selNavLink = $('#nav').find('a[href="' + mPagePath + location.hash + '"]');
618
619 // If the selNavLink is still empty, look for it without the hash
620 if ($selNavLink.length == 0) {
621 $selNavLink = $('#nav').find('a[href="' + mPagePath + '"]');
622 }
623
Scott Mainf6145542013-04-01 16:38:11 -0700624 var $selListItem;
625 if ($selNavLink.length) {
Scott Mainf6145542013-04-01 16:38:11 -0700626 // Find this page's <li> in sidenav and set selected
627 $selListItem = $selNavLink.closest('li');
628 $selListItem.addClass('selected');
Scott Main3b90aff2013-08-01 18:09:35 -0700629
Scott Mainf6145542013-04-01 16:38:11 -0700630 // Traverse up the tree and expand all parent nav-sections
631 $selNavLink.parents('li.nav-section').each(function() {
632 $(this).addClass('expanded');
633 $(this).children('ul').show();
634 });
635 }
636}
637
Scott Maine624b3f2013-09-12 12:56:41 -0700638function unHighlightSidenav() {
639 $("ul#nav li.selected").removeClass("selected");
640 $('ul#nav li.nav-section.expanded').removeClass('expanded').children('ul').hide();
641}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700642
643function toggleFullscreen(enable) {
644 var delay = 20;
645 var enabled = true;
646 var stylesheet = $('link[rel="stylesheet"][class="fullscreen"]');
647 if (enable) {
648 // Currently NOT USING fullscreen; enable fullscreen
649 stylesheet.removeAttr('disabled');
650 $('#nav-swap .fullscreen').removeClass('disabled');
651 $('#devdoc-nav').css({left:''});
652 setTimeout(updateSidenavFullscreenWidth,delay); // need to wait a moment for css to switch
653 enabled = true;
654 } else {
655 // Currently USING fullscreen; disable fullscreen
656 stylesheet.attr('disabled', 'disabled');
657 $('#nav-swap .fullscreen').addClass('disabled');
658 setTimeout(updateSidenavFixedWidth,delay); // need to wait a moment for css to switch
659 enabled = false;
660 }
smain@google.com6bdcb982014-11-14 11:53:07 -0800661 writeCookie("fullscreen", enabled, null);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700662 setNavBarLeftPos();
663 resizeNav(delay);
664 updateSideNavPosition();
665 setTimeout(initSidenavHeightResize,delay);
666}
667
668
669function setNavBarLeftPos() {
670 navBarLeftPos = $('#body-content').offset().left;
671}
672
673
674function updateSideNavPosition() {
675 var newLeft = $(window).scrollLeft() - navBarLeftPos;
676 $('#devdoc-nav').css({left: -newLeft});
677 $('#devdoc-nav .totop').css({left: -(newLeft - parseInt($('#side-nav').css('margin-left')))});
678}
Scott Main3b90aff2013-08-01 18:09:35 -0700679
Scott Maine4d8f1b2012-06-21 18:03:05 -0700680// TODO: use $(document).ready instead
681function addLoadEvent(newfun) {
682 var current = window.onload;
683 if (typeof window.onload != 'function') {
684 window.onload = newfun;
685 } else {
686 window.onload = function() {
687 current();
688 newfun();
689 }
690 }
691}
692
693var agent = navigator['userAgent'].toLowerCase();
694// If a mobile phone, set flag and do mobile setup
695if ((agent.indexOf("mobile") != -1) || // android, iphone, ipod
696 (agent.indexOf("blackberry") != -1) ||
697 (agent.indexOf("webos") != -1) ||
698 (agent.indexOf("mini") != -1)) { // opera mini browsers
699 isMobile = true;
700}
701
702
Scott Main498d7102013-08-21 15:47:38 -0700703$(document).ready(function() {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700704 $("pre:not(.no-pretty-print)").addClass("prettyprint");
705 prettyPrint();
Scott Main498d7102013-08-21 15:47:38 -0700706});
Scott Maine4d8f1b2012-06-21 18:03:05 -0700707
Scott Maine4d8f1b2012-06-21 18:03:05 -0700708
709
710
711/* ######### RESIZE THE SIDENAV HEIGHT ########## */
712
713function resizeNav(delay) {
714 var $nav = $("#devdoc-nav");
715 var $window = $(window);
716 var navHeight;
Scott Main3b90aff2013-08-01 18:09:35 -0700717
Scott Maine4d8f1b2012-06-21 18:03:05 -0700718 // Get the height of entire window and the total header height.
719 // Then figure out based on scroll position whether the header is visible
720 var windowHeight = $window.height();
721 var scrollTop = $window.scrollTop();
Dirk Doughertyc3921652014-05-13 16:55:26 -0700722 var headerHeight = $('#header-wrapper').outerHeight();
723 var headerVisible = scrollTop < stickyTop;
Scott Main3b90aff2013-08-01 18:09:35 -0700724
725 // get the height of space between nav and top of window.
Scott Maine4d8f1b2012-06-21 18:03:05 -0700726 // Could be either margin or top position, depending on whether the nav is fixed.
Scott Main3b90aff2013-08-01 18:09:35 -0700727 var topMargin = (parseInt($nav.css('margin-top')) || parseInt($nav.css('top'))) + 1;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700728 // add 1 for the #side-nav bottom margin
Scott Main3b90aff2013-08-01 18:09:35 -0700729
Scott Maine4d8f1b2012-06-21 18:03:05 -0700730 // Depending on whether the header is visible, set the side nav's height.
731 if (headerVisible) {
732 // The sidenav height grows as the header goes off screen
Dirk Doughertyc3921652014-05-13 16:55:26 -0700733 navHeight = windowHeight - (headerHeight - scrollTop) - topMargin;
Scott Maine4d8f1b2012-06-21 18:03:05 -0700734 } else {
735 // Once header is off screen, the nav height is almost full window height
736 navHeight = windowHeight - topMargin;
737 }
Scott Main3b90aff2013-08-01 18:09:35 -0700738
739
740
Scott Maine4d8f1b2012-06-21 18:03:05 -0700741 $scrollPanes = $(".scroll-pane");
742 if ($scrollPanes.length > 1) {
743 // subtract the height of the api level widget and nav swapper from the available nav height
744 navHeight -= ($('#api-nav-header').outerHeight(true) + $('#nav-swap').outerHeight(true));
Scott Main3b90aff2013-08-01 18:09:35 -0700745
Scott Maine4d8f1b2012-06-21 18:03:05 -0700746 $("#swapper").css({height:navHeight + "px"});
747 if ($("#nav-tree").is(":visible")) {
748 $("#nav-tree").css({height:navHeight});
749 }
Scott Main3b90aff2013-08-01 18:09:35 -0700750
751 var classesHeight = navHeight - parseInt($("#resize-packages-nav").css("height")) - 10 + "px";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700752 //subtract 10px to account for drag bar
Scott Main3b90aff2013-08-01 18:09:35 -0700753
754 // if the window becomes small enough to make the class panel height 0,
Scott Maine4d8f1b2012-06-21 18:03:05 -0700755 // then the package panel should begin to shrink
756 if (parseInt(classesHeight) <= 0) {
757 $("#resize-packages-nav").css({height:navHeight - 10}); //subtract 10px for drag bar
758 $("#packages-nav").css({height:navHeight - 10});
759 }
Scott Main3b90aff2013-08-01 18:09:35 -0700760
Scott Maine4d8f1b2012-06-21 18:03:05 -0700761 $("#classes-nav").css({'height':classesHeight, 'margin-top':'10px'});
762 $("#classes-nav .jspContainer").css({height:classesHeight});
Scott Main3b90aff2013-08-01 18:09:35 -0700763
764
Scott Maine4d8f1b2012-06-21 18:03:05 -0700765 } else {
766 $nav.height(navHeight);
767 }
Scott Main3b90aff2013-08-01 18:09:35 -0700768
Scott Maine4d8f1b2012-06-21 18:03:05 -0700769 if (delay) {
770 updateFromResize = true;
771 delayedReInitScrollbars(delay);
772 } else {
773 reInitScrollbars();
774 }
Scott Main3b90aff2013-08-01 18:09:35 -0700775
Scott Maine4d8f1b2012-06-21 18:03:05 -0700776}
777
778var updateScrollbars = false;
779var updateFromResize = false;
780
781/* Re-initialize the scrollbars to account for changed nav size.
782 * This method postpones the actual update by a 1/4 second in order to optimize the
783 * scroll performance while the header is still visible, because re-initializing the
784 * scroll panes is an intensive process.
785 */
786function delayedReInitScrollbars(delay) {
787 // If we're scheduled for an update, but have received another resize request
788 // before the scheduled resize has occured, just ignore the new request
789 // (and wait for the scheduled one).
790 if (updateScrollbars && updateFromResize) {
791 updateFromResize = false;
792 return;
793 }
Scott Main3b90aff2013-08-01 18:09:35 -0700794
Scott Maine4d8f1b2012-06-21 18:03:05 -0700795 // We're scheduled for an update and the update request came from this method's setTimeout
796 if (updateScrollbars && !updateFromResize) {
797 reInitScrollbars();
798 updateScrollbars = false;
799 } else {
800 updateScrollbars = true;
801 updateFromResize = false;
802 setTimeout('delayedReInitScrollbars()',delay);
803 }
804}
805
806/* Re-initialize the scrollbars to account for changed nav size. */
807function reInitScrollbars() {
808 var pane = $(".scroll-pane").each(function(){
809 var api = $(this).data('jsp');
810 if (!api) { setTimeout(reInitScrollbars,300); return;}
811 api.reinitialise( {verticalGutter:0} );
Scott Main3b90aff2013-08-01 18:09:35 -0700812 });
Scott Maine4d8f1b2012-06-21 18:03:05 -0700813 $(".scroll-pane").removeAttr("tabindex"); // get rid of tabindex added by jscroller
814}
815
816
817/* Resize the height of the nav panels in the reference,
818 * and save the new size to a cookie */
819function saveNavPanels() {
820 var basePath = getBaseUri(location.pathname);
821 var section = basePath.substring(1,basePath.indexOf("/",1));
smain@google.com6bdcb982014-11-14 11:53:07 -0800822 writeCookie("height", resizePackagesNav.css("height"), section);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700823}
824
825
826
827function restoreHeight(packageHeight) {
828 $("#resize-packages-nav").height(packageHeight);
829 $("#packages-nav").height(packageHeight);
830 // var classesHeight = navHeight - packageHeight;
831 // $("#classes-nav").css({height:classesHeight});
832 // $("#classes-nav .jspContainer").css({height:classesHeight});
833}
834
835
836
837/* ######### END RESIZE THE SIDENAV HEIGHT ########## */
838
839
840
841
842
Scott Main3b90aff2013-08-01 18:09:35 -0700843/** Scroll the jScrollPane to make the currently selected item visible
Scott Maine4d8f1b2012-06-21 18:03:05 -0700844 This is called when the page finished loading. */
845function scrollIntoView(nav) {
846 var $nav = $("#"+nav);
847 var element = $nav.jScrollPane({/* ...settings... */});
848 var api = element.data('jsp');
849
850 if ($nav.is(':visible')) {
851 var $selected = $(".selected", $nav);
Scott Mainbc729572013-07-30 18:00:51 -0700852 if ($selected.length == 0) {
853 // If no selected item found, exit
854 return;
855 }
Scott Main52dd2062013-08-15 12:22:28 -0700856 // get the selected item's offset from its container nav by measuring the item's offset
857 // relative to the document then subtract the container nav's offset relative to the document
858 var selectedOffset = $selected.offset().top - $nav.offset().top;
859 if (selectedOffset > $nav.height() * .8) { // multiply nav height by .8 so we move up the item
860 // if it's more than 80% down the nav
861 // scroll the item up by an amount equal to 80% the container nav's height
862 api.scrollTo(0, selectedOffset - ($nav.height() * .8), false);
Scott Maine4d8f1b2012-06-21 18:03:05 -0700863 }
864 }
865}
866
867
868
869
870
871
872/* Show popup dialogs */
873function showDialog(id) {
874 $dialog = $("#"+id);
875 $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>');
876 $dialog.wrapInner('<div/>');
877 $dialog.removeClass("hide");
878}
879
880
881
882
883
884/* ######### COOKIES! ########## */
885
886function readCookie(cookie) {
887 var myCookie = cookie_namespace+"_"+cookie+"=";
888 if (document.cookie) {
889 var index = document.cookie.indexOf(myCookie);
890 if (index != -1) {
891 var valStart = index + myCookie.length;
892 var valEnd = document.cookie.indexOf(";", valStart);
893 if (valEnd == -1) {
894 valEnd = document.cookie.length;
895 }
896 var val = document.cookie.substring(valStart, valEnd);
897 return val;
898 }
899 }
900 return 0;
901}
902
smain@google.com6bdcb982014-11-14 11:53:07 -0800903function writeCookie(cookie, val, section) {
Scott Maine4d8f1b2012-06-21 18:03:05 -0700904 if (val==undefined) return;
905 section = section == null ? "_" : "_"+section+"_";
smain@google.com6bdcb982014-11-14 11:53:07 -0800906 var age = 2*365*24*60*60; // set max-age to 2 years
Scott Main3b90aff2013-08-01 18:09:35 -0700907 var cookieValue = cookie_namespace + section + cookie + "=" + val
smain@google.com80e38f42014-11-03 10:47:12 -0800908 + "; max-age=" + age +"; path=/";
Scott Maine4d8f1b2012-06-21 18:03:05 -0700909 document.cookie = cookieValue;
910}
911
912/* ######### END COOKIES! ########## */
913
914
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700915var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -0700916var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700917var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -0700918/* Sets the vertical scoll position at which the sticky bar should appear.
919 This method is called to reset the position when search results appear or hide */
920function setStickyTop() {
921 stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight();
922}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700923
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700924/*
Scott Mainb16376f2014-05-21 20:35:47 -0700925 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -0700926 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700927$(window).scroll(function(event) {
928
929 setStickyTop();
930 var hiding = false;
931 var $stickyEl = $('#sticky-header');
932 var $menuEl = $('.menu-container');
933 // Exit if there's no sidenav
934 if ($('#side-nav').length == 0) return;
935 // Exit if the mouse target is a DIV, because that means the event is coming
936 // from a scrollable div and so there's no need to make adjustments to our layout
937 if ($(event.target).nodeName == "DIV") {
938 return;
939 }
940
941 var top = $(window).scrollTop();
942 // we set the navbar fixed when the scroll position is beyond the height of the site header...
943 var shouldBeSticky = top >= stickyTop;
944 // ... except if the document content is shorter than the sidenav height.
945 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
946 if ($("#doc-col").height() < $("#side-nav").height()) {
947 shouldBeSticky = false;
948 }
Scott Mainf5257812014-05-22 17:26:38 -0700949 // Account for horizontal scroll
950 var scrollLeft = $(window).scrollLeft();
951 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
952 if (sticky && (scrollLeft != prevScrollLeft)) {
953 updateSideNavPosition();
954 prevScrollLeft = scrollLeft;
955 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700956
957 // Don't continue if the header is sufficently far away
958 // (to avoid intensive resizing that slows scrolling)
959 if (sticky == shouldBeSticky) {
960 return;
961 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700962
963 // If sticky header visible and position is now near top, hide sticky
964 if (sticky && !shouldBeSticky) {
965 sticky = false;
966 hiding = true;
967 // make the sidenav static again
968 $('#devdoc-nav')
969 .removeClass('fixed')
970 .css({'width':'auto','margin':''})
971 .prependTo('#side-nav');
972 // delay hide the sticky
973 $menuEl.removeClass('sticky-menu');
974 $stickyEl.fadeOut(250);
975 hiding = false;
976
977 // update the sidenaav position for side scrolling
978 updateSideNavPosition();
979 } else if (!sticky && shouldBeSticky) {
980 sticky = true;
981 $stickyEl.fadeIn(10);
982 $menuEl.addClass('sticky-menu');
983
984 // make the sidenav fixed
985 var width = $('#devdoc-nav').width();
986 $('#devdoc-nav')
987 .addClass('fixed')
988 .css({'width':width+'px'})
989 .prependTo('#body-content');
990
991 // update the sidenaav position for side scrolling
992 updateSideNavPosition();
993
994 } else if (hiding && top < 15) {
995 $menuEl.removeClass('sticky-menu');
996 $stickyEl.hide();
997 hiding = false;
998 }
999 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
1000});
1001
1002/*
1003 * Manages secion card states and nav resize to conclude loading
1004 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001005(function() {
1006 $(document).ready(function() {
1007
Dirk Doughertyc3921652014-05-13 16:55:26 -07001008 // Stack hover states
1009 $('.section-card-menu').each(function(index, el) {
1010 var height = $(el).height();
1011 $(el).css({height:height+'px', position:'relative'});
1012 var $cardInfo = $(el).find('.card-info');
1013
1014 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1015 });
1016
Dirk Doughertyc3921652014-05-13 16:55:26 -07001017 });
1018
1019})();
1020
Scott Maine4d8f1b2012-06-21 18:03:05 -07001021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
Scott Maind7026f72013-06-17 15:08:49 -07001034/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001035
1036
1037
1038
1039
1040function toggle(obj, slide) {
1041 var ul = $("ul:first", obj);
1042 var li = ul.parent();
1043 if (li.hasClass("closed")) {
1044 if (slide) {
1045 ul.slideDown("fast");
1046 } else {
1047 ul.show();
1048 }
1049 li.removeClass("closed");
1050 li.addClass("open");
1051 $(".toggle-img", li).attr("title", "hide pages");
1052 } else {
1053 ul.slideUp("fast");
1054 li.removeClass("open");
1055 li.addClass("closed");
1056 $(".toggle-img", li).attr("title", "show pages");
1057 }
1058}
1059
1060
Scott Maine4d8f1b2012-06-21 18:03:05 -07001061function buildToggleLists() {
1062 $(".toggle-list").each(
1063 function(i) {
1064 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1065 $(this).addClass("closed");
1066 });
1067}
1068
1069
1070
Scott Maind7026f72013-06-17 15:08:49 -07001071function hideNestedItems(list, toggle) {
1072 $list = $(list);
1073 // hide nested lists
1074 if($list.hasClass('showing')) {
1075 $("li ol", $list).hide('fast');
1076 $list.removeClass('showing');
1077 // show nested lists
1078 } else {
1079 $("li ol", $list).show('fast');
1080 $list.addClass('showing');
1081 }
1082 $(".more,.less",$(toggle)).toggle();
1083}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001084
1085
smain@google.com95948b82014-06-16 19:24:25 -07001086/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1087function setupIdeDocToggle() {
1088 $( "select.ide" ).change(function() {
1089 var selected = $(this).find("option:selected").attr("value");
1090 $(".select-ide").hide();
1091 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001092
smain@google.com95948b82014-06-16 19:24:25 -07001093 $("select.ide").val(selected);
1094 });
1095}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120/* REFERENCE NAV SWAP */
1121
1122
1123function getNavPref() {
1124 var v = readCookie('reference_nav');
1125 if (v != NAV_PREF_TREE) {
1126 v = NAV_PREF_PANELS;
1127 }
1128 return v;
1129}
1130
1131function chooseDefaultNav() {
1132 nav_pref = getNavPref();
1133 if (nav_pref == NAV_PREF_TREE) {
1134 $("#nav-panels").toggle();
1135 $("#panel-link").toggle();
1136 $("#nav-tree").toggle();
1137 $("#tree-link").toggle();
1138 }
1139}
1140
1141function swapNav() {
1142 if (nav_pref == NAV_PREF_TREE) {
1143 nav_pref = NAV_PREF_PANELS;
1144 } else {
1145 nav_pref = NAV_PREF_TREE;
1146 init_default_navtree(toRoot);
1147 }
smain@google.com6bdcb982014-11-14 11:53:07 -08001148 writeCookie("nav", nav_pref, "reference");
Scott Maine4d8f1b2012-06-21 18:03:05 -07001149
1150 $("#nav-panels").toggle();
1151 $("#panel-link").toggle();
1152 $("#nav-tree").toggle();
1153 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001154
Scott Maine4d8f1b2012-06-21 18:03:05 -07001155 resizeNav();
1156
1157 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1158 $("#nav-tree .jspContainer:visible")
1159 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1160 // Another nasty hack to make the scrollbar appear now that we have height
1161 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001162
Scott Maine4d8f1b2012-06-21 18:03:05 -07001163 if ($("#nav-tree").is(':visible')) {
1164 scrollIntoView("nav-tree");
1165 } else {
1166 scrollIntoView("packages-nav");
1167 scrollIntoView("classes-nav");
1168 }
1169}
1170
1171
1172
Scott Mainf5089842012-08-14 16:31:07 -07001173/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001174/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001175/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001176
1177function getBaseUri(uri) {
1178 var intlUrl = (uri.substring(0,6) == "/intl/");
1179 if (intlUrl) {
1180 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1181 base = base.substring(base.indexOf('/')+1, base.length);
1182 //alert("intl, returning base url: /" + base);
1183 return ("/" + base);
1184 } else {
1185 //alert("not intl, returning uri as found.");
1186 return uri;
1187 }
1188}
1189
1190function requestAppendHL(uri) {
1191//append "?hl=<lang> to an outgoing request (such as to blog)
1192 var lang = getLangPref();
1193 if (lang) {
1194 var q = 'hl=' + lang;
1195 uri += '?' + q;
1196 window.location = uri;
1197 return false;
1198 } else {
1199 return true;
1200 }
1201}
1202
1203
Scott Maine4d8f1b2012-06-21 18:03:05 -07001204function changeNavLang(lang) {
Scott Main6eb95f12012-10-02 17:12:23 -07001205 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1206 $links.each(function(i){ // for each link with a translation
1207 var $link = $(this);
1208 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1209 // put the desired language from the attribute as the text
1210 $link.text($link.attr(lang+"-lang"))
Scott Maine4d8f1b2012-06-21 18:03:05 -07001211 }
Scott Main6eb95f12012-10-02 17:12:23 -07001212 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001213}
1214
Scott Main015d6162013-01-29 09:01:52 -08001215function changeLangPref(lang, submit) {
smain@google.com6bdcb982014-11-14 11:53:07 -08001216 writeCookie("pref_lang", lang, null);
Scott Main015d6162013-01-29 09:01:52 -08001217
1218 // ####### TODO: Remove this condition once we're stable on devsite #######
1219 // This condition is only needed if we still need to support legacy GAE server
1220 if (devsite) {
1221 // Switch language when on Devsite server
1222 if (submit) {
1223 $("#setlang").submit();
1224 }
1225 } else {
1226 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001227 if (submit) {
1228 window.location = getBaseUri(location.pathname);
1229 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001230 }
1231}
1232
1233function loadLangPref() {
1234 var lang = readCookie("pref_lang");
1235 if (lang != 0) {
1236 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1237 }
1238}
1239
1240function getLangPref() {
1241 var lang = $("#language").find(":selected").attr("value");
1242 if (!lang) {
1243 lang = readCookie("pref_lang");
1244 }
1245 return (lang != 0) ? lang : 'en';
1246}
1247
1248/* ########## END LOCALIZATION ############ */
1249
1250
1251
1252
1253
1254
1255/* Used to hide and reveal supplemental content, such as long code samples.
1256 See the companion CSS in android-developer-docs.css */
1257function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001258 var div = $(obj).closest(".toggle-content");
1259 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001260 if (div.hasClass("closed")) { // if it's closed, open it
1261 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001262 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001263 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001264 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001265 + "assets/images/triangle-opened.png");
1266 } else { // if it's open, close it
1267 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001268 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001269 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001270 div.find(".toggle-content").removeClass("open").addClass("closed")
1271 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001272 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001273 + "assets/images/triangle-closed.png");
1274 });
1275 }
1276 return false;
1277}
Scott Mainf5089842012-08-14 16:31:07 -07001278
1279
Scott Maindb3678b2012-10-23 14:13:41 -07001280/* New version of expandable content */
1281function toggleExpandable(link,id) {
1282 if($(id).is(':visible')) {
1283 $(id).slideUp();
1284 $(link).removeClass('expanded');
1285 } else {
1286 $(id).slideDown();
1287 $(link).addClass('expanded');
1288 }
1289}
1290
1291function hideExpandable(ids) {
1292 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001293 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001294}
1295
Scott Mainf5089842012-08-14 16:31:07 -07001296
1297
1298
1299
Scott Main3b90aff2013-08-01 18:09:35 -07001300/*
Scott Mainf5089842012-08-14 16:31:07 -07001301 * Slideshow 1.0
1302 * Used on /index.html and /develop/index.html for carousel
1303 *
1304 * Sample usage:
1305 * HTML -
1306 * <div class="slideshow-container">
1307 * <a href="" class="slideshow-prev">Prev</a>
1308 * <a href="" class="slideshow-next">Next</a>
1309 * <ul>
1310 * <li class="item"><img src="images/marquee1.jpg"></li>
1311 * <li class="item"><img src="images/marquee2.jpg"></li>
1312 * <li class="item"><img src="images/marquee3.jpg"></li>
1313 * <li class="item"><img src="images/marquee4.jpg"></li>
1314 * </ul>
1315 * </div>
1316 *
1317 * <script type="text/javascript">
1318 * $('.slideshow-container').dacSlideshow({
1319 * auto: true,
1320 * btnPrev: '.slideshow-prev',
1321 * btnNext: '.slideshow-next'
1322 * });
1323 * </script>
1324 *
1325 * Options:
1326 * btnPrev: optional identifier for previous button
1327 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001328 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001329 * auto: whether or not to auto-proceed
1330 * speed: animation speed
1331 * autoTime: time between auto-rotation
1332 * easing: easing function for transition
1333 * start: item to select by default
1334 * scroll: direction to scroll in
1335 * pagination: whether or not to include dotted pagination
1336 *
1337 */
1338
1339 (function($) {
1340 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001341
Scott Mainf5089842012-08-14 16:31:07 -07001342 //Options - see above
1343 o = $.extend({
1344 btnPrev: null,
1345 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001346 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001347 auto: true,
1348 speed: 500,
1349 autoTime: 12000,
1350 easing: null,
1351 start: 0,
1352 scroll: 1,
1353 pagination: true
1354
1355 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001356
1357 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001358 return this.each(function() {
1359
1360 var running = false;
1361 var animCss = o.vertical ? "top" : "left";
1362 var sizeCss = o.vertical ? "height" : "width";
1363 var div = $(this);
1364 var ul = $("ul", div);
1365 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001366 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001367 var timer = null;
1368
1369 var li = $("li", ul);
1370 var itemLength = li.size();
1371 var curr = o.start;
1372
1373 li.css({float: o.vertical ? "none" : "left"});
1374 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1375 div.css({position: "relative", "z-index": "2", left: "0px"});
1376
1377 var liSize = o.vertical ? height(li) : width(li);
1378 var ulSize = liSize * itemLength;
1379 var divSize = liSize;
1380
1381 li.css({width: li.width(), height: li.height()});
1382 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1383
1384 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001385
Scott Mainf5089842012-08-14 16:31:07 -07001386 //Pagination
1387 if (o.pagination) {
1388 var pagination = $("<div class='pagination'></div>");
1389 var pag_ul = $("<ul></ul>");
1390 if (tl > 1) {
1391 for (var i=0;i<tl;i++) {
1392 var li = $("<li>"+i+"</li>");
1393 pag_ul.append(li);
1394 if (i==o.start) li.addClass('active');
1395 li.click(function() {
1396 go(parseInt($(this).text()));
1397 })
1398 }
1399 pagination.append(pag_ul);
1400 div.append(pagination);
1401 }
1402 }
Scott Main3b90aff2013-08-01 18:09:35 -07001403
Scott Mainf5089842012-08-14 16:31:07 -07001404 //Previous button
1405 if(o.btnPrev)
1406 $(o.btnPrev).click(function(e) {
1407 e.preventDefault();
1408 return go(curr-o.scroll);
1409 });
1410
1411 //Next button
1412 if(o.btnNext)
1413 $(o.btnNext).click(function(e) {
1414 e.preventDefault();
1415 return go(curr+o.scroll);
1416 });
Scott Maineb410352013-01-14 19:03:40 -08001417
1418 //Pause button
1419 if(o.btnPause)
1420 $(o.btnPause).click(function(e) {
1421 e.preventDefault();
1422 if ($(this).hasClass('paused')) {
1423 startRotateTimer();
1424 } else {
1425 pauseRotateTimer();
1426 }
1427 });
Scott Main3b90aff2013-08-01 18:09:35 -07001428
Scott Mainf5089842012-08-14 16:31:07 -07001429 //Auto rotation
1430 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001431
Scott Mainf5089842012-08-14 16:31:07 -07001432 function startRotateTimer() {
1433 clearInterval(timer);
1434 timer = setInterval(function() {
1435 if (curr == tl-1) {
1436 go(0);
1437 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001438 go(curr+o.scroll);
1439 }
Scott Mainf5089842012-08-14 16:31:07 -07001440 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001441 $(o.btnPause).removeClass('paused');
1442 }
1443
1444 function pauseRotateTimer() {
1445 clearInterval(timer);
1446 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001447 }
1448
1449 //Go to an item
1450 function go(to) {
1451 if(!running) {
1452
1453 if(to<0) {
1454 to = itemLength-1;
1455 } else if (to>itemLength-1) {
1456 to = 0;
1457 }
1458 curr = to;
1459
1460 running = true;
1461
1462 ul.animate(
1463 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1464 function() {
1465 running = false;
1466 }
1467 );
1468
1469 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1470 $( (curr-o.scroll<0 && o.btnPrev)
1471 ||
1472 (curr+o.scroll > itemLength && o.btnNext)
1473 ||
1474 []
1475 ).addClass("disabled");
1476
Scott Main3b90aff2013-08-01 18:09:35 -07001477
Scott Mainf5089842012-08-14 16:31:07 -07001478 var nav_items = $('li', pagination);
1479 nav_items.removeClass('active');
1480 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001481
Scott Mainf5089842012-08-14 16:31:07 -07001482
1483 }
1484 if(o.auto) startRotateTimer();
1485 return false;
1486 };
1487 });
1488 };
1489
1490 function css(el, prop) {
1491 return parseInt($.css(el[0], prop)) || 0;
1492 };
1493 function width(el) {
1494 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1495 };
1496 function height(el) {
1497 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1498 };
1499
1500 })(jQuery);
1501
1502
Scott Main3b90aff2013-08-01 18:09:35 -07001503/*
Scott Mainf5089842012-08-14 16:31:07 -07001504 * dacSlideshow 1.0
1505 * Used on develop/index.html for side-sliding tabs
1506 *
1507 * Sample usage:
1508 * HTML -
1509 * <div class="slideshow-container">
1510 * <a href="" class="slideshow-prev">Prev</a>
1511 * <a href="" class="slideshow-next">Next</a>
1512 * <ul>
1513 * <li class="item"><img src="images/marquee1.jpg"></li>
1514 * <li class="item"><img src="images/marquee2.jpg"></li>
1515 * <li class="item"><img src="images/marquee3.jpg"></li>
1516 * <li class="item"><img src="images/marquee4.jpg"></li>
1517 * </ul>
1518 * </div>
1519 *
1520 * <script type="text/javascript">
1521 * $('.slideshow-container').dacSlideshow({
1522 * auto: true,
1523 * btnPrev: '.slideshow-prev',
1524 * btnNext: '.slideshow-next'
1525 * });
1526 * </script>
1527 *
1528 * Options:
1529 * btnPrev: optional identifier for previous button
1530 * btnNext: optional identifier for next button
1531 * auto: whether or not to auto-proceed
1532 * speed: animation speed
1533 * autoTime: time between auto-rotation
1534 * easing: easing function for transition
1535 * start: item to select by default
1536 * scroll: direction to scroll in
1537 * pagination: whether or not to include dotted pagination
1538 *
1539 */
1540 (function($) {
1541 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001542
Scott Mainf5089842012-08-14 16:31:07 -07001543 //Options - see above
1544 o = $.extend({
1545 speed : 250,
1546 easing: null,
1547 nav_id: null,
1548 frame_id: null
1549 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001550
1551 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001552 return this.each(function() {
1553
1554 var curr = 0;
1555 var running = false;
1556 var animCss = "margin-left";
1557 var sizeCss = "width";
1558 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001559
Scott Mainf5089842012-08-14 16:31:07 -07001560 var nav = $(o.nav_id, div);
1561 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001562 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001563 var frame = div.find(o.frame_id);
1564 var content_width = $(frame).find('ul').width();
1565 //Buttons
1566 $(nav_li).click(function(e) {
1567 go($(nav_li).index($(this)));
1568 })
Scott Main3b90aff2013-08-01 18:09:35 -07001569
Scott Mainf5089842012-08-14 16:31:07 -07001570 //Go to an item
1571 function go(to) {
1572 if(!running) {
1573 curr = to;
1574 running = true;
1575
1576 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1577 function() {
1578 running = false;
1579 }
1580 );
1581
Scott Main3b90aff2013-08-01 18:09:35 -07001582
Scott Mainf5089842012-08-14 16:31:07 -07001583 nav_li.removeClass('active');
1584 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001585
Scott Mainf5089842012-08-14 16:31:07 -07001586
1587 }
1588 return false;
1589 };
1590 });
1591 };
1592
1593 function css(el, prop) {
1594 return parseInt($.css(el[0], prop)) || 0;
1595 };
1596 function width(el) {
1597 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1598 };
1599 function height(el) {
1600 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1601 };
1602
1603 })(jQuery);
1604
1605
1606
1607
1608
1609/* ######################################################## */
1610/* ################ SEARCH SUGGESTIONS ################## */
1611/* ######################################################## */
1612
1613
Scott Main7e447ed2013-02-19 17:22:37 -08001614
Scott Main0e76e7e2013-03-12 10:24:07 -07001615var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1616var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1617
Scott Mainf5089842012-08-14 16:31:07 -07001618var gMatches = new Array();
1619var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001620var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001621var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1622var gListLength = 0;
1623
1624
1625var gGoogleMatches = new Array();
1626var ROW_COUNT_GOOGLE = 15; // max number of results in list
1627var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001628
Scott Main0e76e7e2013-03-12 10:24:07 -07001629var gDocsMatches = new Array();
1630var ROW_COUNT_DOCS = 100; // max number of results in list
1631var gDocsListLength = 0;
1632
Scott Mainde295272013-03-25 15:48:35 -07001633function onSuggestionClick(link) {
1634 // When user clicks a suggested document, track it
smain@google.com633f3222014-10-03 15:49:45 -07001635 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).text(),
1636 'from: ' + $("#search_autocomplete").val());
Scott Mainde295272013-03-25 15:48:35 -07001637}
1638
Scott Mainf5089842012-08-14 16:31:07 -07001639function set_item_selected($li, selected)
1640{
1641 if (selected) {
1642 $li.attr('class','jd-autocomplete jd-selected');
1643 } else {
1644 $li.attr('class','jd-autocomplete');
1645 }
1646}
1647
1648function set_item_values(toroot, $li, match)
1649{
1650 var $link = $('a',$li);
1651 $link.html(match.__hilabel || match.label);
1652 $link.attr('href',toroot + match.link);
1653}
1654
Scott Main719acb42013-12-05 16:05:09 -08001655function set_item_values_jd(toroot, $li, match)
1656{
1657 var $link = $('a',$li);
1658 $link.html(match.title);
1659 $link.attr('href',toroot + match.url);
1660}
1661
Scott Main0e76e7e2013-03-12 10:24:07 -07001662function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001663 var $li = $("<li class='jd-autocomplete'></li>");
1664 $list.append($li);
1665
1666 $li.mousedown(function() {
1667 window.location = this.firstChild.getAttribute("href");
1668 });
1669 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001670 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001671 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001672 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1673 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001674 });
Scott Mainde295272013-03-25 15:48:35 -07001675 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001676 $li.attr('class','show-item');
1677 return $li;
1678}
1679
Scott Mainf5089842012-08-14 16:31:07 -07001680function sync_selection_table(toroot)
1681{
Scott Mainf5089842012-08-14 16:31:07 -07001682 var $li; //list item jquery object
1683 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001684
Scott Main0e76e7e2013-03-12 10:24:07 -07001685 // if there are NO results at all, hide all columns
1686 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1687 $('.suggest-card').hide(300);
1688 return;
1689 }
1690
1691 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001692 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001693 // reveal suggestion list
1694 $('.suggest-card.dummy').show();
1695 $('.suggest-card.reference').show();
1696 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001697
Scott Main0e76e7e2013-03-12 10:24:07 -07001698 // reset the lists
1699 $(".search_filtered_wrapper.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001700
Scott Main0e76e7e2013-03-12 10:24:07 -07001701 // ########### ANDROID RESULTS #############
1702 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001703
Scott Main0e76e7e2013-03-12 10:24:07 -07001704 // determine android results to show
1705 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1706 gMatches.length : ROW_COUNT_FRAMEWORK;
1707 for (i=0; i<gListLength; i++) {
1708 var $li = new_suggestion($(".suggest-card.reference ul"));
1709 set_item_values(toroot, $li, gMatches[i]);
1710 set_item_selected($li, i == gSelectedIndex);
1711 }
1712 }
Scott Main7e447ed2013-02-19 17:22:37 -08001713
Scott Main0e76e7e2013-03-12 10:24:07 -07001714 // ########### GOOGLE RESULTS #############
1715 if (gGoogleMatches.length > 0) {
1716 // show header for list
1717 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001718
Scott Main0e76e7e2013-03-12 10:24:07 -07001719 // determine google results to show
1720 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1721 for (i=0; i<gGoogleListLength; i++) {
1722 var $li = new_suggestion($(".suggest-card.reference ul"));
1723 set_item_values(toroot, $li, gGoogleMatches[i]);
1724 set_item_selected($li, i == gSelectedIndex);
1725 }
1726 }
Scott Mainf5089842012-08-14 16:31:07 -07001727 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001728 $('.suggest-card.reference').hide();
1729 $('.suggest-card.dummy').hide();
1730 }
1731
1732 // ########### JD DOC RESULTS #############
1733 if (gDocsMatches.length > 0) {
1734 // reset the lists
1735 $(".search_filtered_wrapper.docs li").remove();
1736
1737 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001738 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1739 // The order must match the reverse order that each section appears as a card in
1740 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001741 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1742 for (i=0; i<gDocsListLength; i++) {
1743 var sugg = gDocsMatches[i];
1744 var $li;
1745 if (sugg.type == "design") {
1746 $li = new_suggestion($(".suggest-card.design ul"));
1747 } else
1748 if (sugg.type == "distribute") {
1749 $li = new_suggestion($(".suggest-card.distribute ul"));
1750 } else
Scott Main719acb42013-12-05 16:05:09 -08001751 if (sugg.type == "samples") {
1752 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1753 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001754 if (sugg.type == "training") {
1755 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1756 } else
Scott Main719acb42013-12-05 16:05:09 -08001757 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001758 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1759 } else {
1760 continue;
1761 }
1762
Scott Main719acb42013-12-05 16:05:09 -08001763 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001764 set_item_selected($li, i == gSelectedIndex);
1765 }
1766
1767 // add heading and show or hide card
1768 if ($(".suggest-card.design li").length > 0) {
1769 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1770 $(".suggest-card.design").show(300);
1771 } else {
1772 $('.suggest-card.design').hide(300);
1773 }
1774 if ($(".suggest-card.distribute li").length > 0) {
1775 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1776 $(".suggest-card.distribute").show(300);
1777 } else {
1778 $('.suggest-card.distribute').hide(300);
1779 }
1780 if ($(".child-card.guides li").length > 0) {
1781 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1782 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1783 }
1784 if ($(".child-card.training li").length > 0) {
1785 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1786 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1787 }
Scott Main719acb42013-12-05 16:05:09 -08001788 if ($(".child-card.samples li").length > 0) {
1789 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1790 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1791 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001792
1793 if ($(".suggest-card.develop li").length > 0) {
1794 $(".suggest-card.develop").show(300);
1795 } else {
1796 $('.suggest-card.develop').hide(300);
1797 }
1798
1799 } else {
1800 $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001801 }
1802}
1803
Scott Main0e76e7e2013-03-12 10:24:07 -07001804/** Called by the search input's onkeydown and onkeyup events.
1805 * Handles navigation with keyboard arrows, Enter key to invoke search,
1806 * otherwise invokes search suggestions on key-up event.
1807 * @param e The JS event
1808 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001809 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001810 * @returns True if the event should bubble up
1811 */
Scott Mainf5089842012-08-14 16:31:07 -07001812function search_changed(e, kd, toroot)
1813{
Scott Main719acb42013-12-05 16:05:09 -08001814 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001815 var search = document.getElementById("search_autocomplete");
1816 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001817 // get the ul hosting the currently selected item
1818 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1819 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1820 var $selectedUl = $columns[gSelectedColumn];
1821
Scott Mainf5089842012-08-14 16:31:07 -07001822 // show/hide the close button
1823 if (text != '') {
1824 $(".search .close").removeClass("hide");
1825 } else {
1826 $(".search .close").addClass("hide");
1827 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001828 // 27 = esc
1829 if (e.keyCode == 27) {
1830 // close all search results
1831 if (kd) $('.search .close').trigger('click');
1832 return true;
1833 }
Scott Mainf5089842012-08-14 16:31:07 -07001834 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001835 else if (e.keyCode == 13) {
1836 if (gSelectedIndex < 0) {
1837 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001838 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1839 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001840 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001841 return true;
1842 } else {
1843 // otherwise, results are already showing, so allow ajax to auto refresh the results
1844 // and ignore this Enter press to avoid the reload.
1845 return false;
1846 }
1847 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001848 // click the link corresponding to selected item
1849 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001850 return false;
1851 }
1852 }
Scott Mainb16376f2014-05-21 20:35:47 -07001853 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001854 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001855 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001856 if ((sticky ) && (search.value != "")) {
1857 $('body,html').animate({scrollTop:0}, '500', 'swing');
1858 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001859 return true;
1860 }
1861 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001862 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001863 // if the next item is a header, skip it
1864 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001865 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001866 }
1867 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001868 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001869 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001870 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1871 // If user reaches top, reset selected column
1872 if (gSelectedIndex < 0) {
1873 gSelectedColumn = -1;
1874 }
Scott Mainf5089842012-08-14 16:31:07 -07001875 }
1876 return false;
1877 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001878 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001879 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001880 // if the next item is a header, skip it
1881 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001882 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08001883 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001884 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
1885 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
1886 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001887 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07001888 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07001889 }
1890 return false;
1891 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001892 // Consider left/right arrow navigation
1893 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
1894 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
1895 // 37 LEFT ARROW
1896 // go left only if current column is not left-most column (last column)
1897 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
1898 $('li', $selectedUl).removeClass('jd-selected');
1899 gSelectedColumn++;
1900 $selectedUl = $columns[gSelectedColumn];
1901 // keep or reset the selected item to last item as appropriate
1902 gSelectedIndex = gSelectedIndex >
1903 $("li", $selectedUl).length-1 ?
1904 $("li", $selectedUl).length-1 : gSelectedIndex;
1905 // if the corresponding item is a header, move down
1906 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1907 gSelectedIndex++;
1908 }
Scott Main3b90aff2013-08-01 18:09:35 -07001909 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07001910 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1911 return false;
1912 }
1913 // 39 RIGHT ARROW
1914 // go right only if current column is not the right-most column (first column)
1915 else if (e.keyCode == 39 && gSelectedColumn > 0) {
1916 $('li', $selectedUl).removeClass('jd-selected');
1917 gSelectedColumn--;
1918 $selectedUl = $columns[gSelectedColumn];
1919 // keep or reset the selected item to last item as appropriate
1920 gSelectedIndex = gSelectedIndex >
1921 $("li", $selectedUl).length-1 ?
1922 $("li", $selectedUl).length-1 : gSelectedIndex;
1923 // if the corresponding item is a header, move down
1924 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1925 gSelectedIndex++;
1926 }
Scott Main3b90aff2013-08-01 18:09:35 -07001927 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07001928 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1929 return false;
1930 }
1931 }
1932
Scott Main719acb42013-12-05 16:05:09 -08001933 // if key-up event and not arrow down/up/left/right,
1934 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07001935 else if (!kd && (e.keyCode != 40)
1936 && (e.keyCode != 38)
1937 && (e.keyCode != 37)
1938 && (e.keyCode != 39)) {
1939 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07001940 gMatches = new Array();
1941 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08001942 gGoogleMatches = new Array();
1943 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07001944 gDocsMatches = new Array();
1945 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08001946
1947 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07001948 for (var i=0; i<DATA.length; i++) {
1949 var s = DATA[i];
1950 if (text.length != 0 &&
1951 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1952 gMatches[matchedCount] = s;
1953 matchedCount++;
1954 }
1955 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001956 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07001957 for (var i=0; i<gMatches.length; i++) {
1958 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08001959 }
1960
1961
1962 // Search for Google matches
1963 for (var i=0; i<GOOGLE_DATA.length; i++) {
1964 var s = GOOGLE_DATA[i];
1965 if (text.length != 0 &&
1966 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1967 gGoogleMatches[matchedCountGoogle] = s;
1968 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07001969 }
1970 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001971 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08001972 for (var i=0; i<gGoogleMatches.length; i++) {
1973 var s = gGoogleMatches[i];
1974 }
1975
Scott Mainf5089842012-08-14 16:31:07 -07001976 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07001977
1978
1979
Scott Main719acb42013-12-05 16:05:09 -08001980 // Search for matching JD docs
Dirk Dougherty9b7f8f22014-11-01 17:08:56 -07001981 if (text.length >= 2) {
Scott Main719acb42013-12-05 16:05:09 -08001982 // Regex to match only the beginning of a word
1983 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
1984
1985
1986 // Search for Training classes
1987 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001988 // current search comparison, with counters for tag and title,
1989 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08001990 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07001991 s.matched_tag = 0;
1992 s.matched_title = 0;
1993 var matched = false;
1994
1995 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08001996 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001997 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08001998 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001999 matched = true;
2000 s.matched_tag = j + 1; // add 1 to index position
2001 }
2002 }
Scott Main719acb42013-12-05 16:05:09 -08002003 // Don't consider doc title for lessons (only for class landing pages),
2004 // unless the lesson has a tag that already matches
2005 if ((s.lang == currentLang) &&
2006 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002007 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08002008 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002009 matched = true;
2010 s.matched_title = 1;
2011 }
2012 }
2013 if (matched) {
2014 gDocsMatches[matchedCountDocs] = s;
2015 matchedCountDocs++;
2016 }
2017 }
Scott Main719acb42013-12-05 16:05:09 -08002018
2019
2020 // Search for API Guides
2021 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2022 // current search comparison, with counters for tag and title,
2023 // used later to improve ranking
2024 var s = GUIDE_RESOURCES[i];
2025 s.matched_tag = 0;
2026 s.matched_title = 0;
2027 var matched = false;
2028
2029 // Check if query matches any tags; work backwards toward 1 to assist ranking
2030 for (var j = s.keywords.length - 1; j >= 0; j--) {
2031 // it matches a tag
2032 if (s.keywords[j].toLowerCase().match(textRegex)) {
2033 matched = true;
2034 s.matched_tag = j + 1; // add 1 to index position
2035 }
2036 }
2037 // Check if query matches the doc title, but only for current language
2038 if (s.lang == currentLang) {
2039 // if query matches the doc title
2040 if (s.title.toLowerCase().match(textRegex)) {
2041 matched = true;
2042 s.matched_title = 1;
2043 }
2044 }
2045 if (matched) {
2046 gDocsMatches[matchedCountDocs] = s;
2047 matchedCountDocs++;
2048 }
2049 }
2050
2051
2052 // Search for Tools Guides
2053 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2054 // current search comparison, with counters for tag and title,
2055 // used later to improve ranking
2056 var s = TOOLS_RESOURCES[i];
2057 s.matched_tag = 0;
2058 s.matched_title = 0;
2059 var matched = false;
2060
2061 // Check if query matches any tags; work backwards toward 1 to assist ranking
2062 for (var j = s.keywords.length - 1; j >= 0; j--) {
2063 // it matches a tag
2064 if (s.keywords[j].toLowerCase().match(textRegex)) {
2065 matched = true;
2066 s.matched_tag = j + 1; // add 1 to index position
2067 }
2068 }
2069 // Check if query matches the doc title, but only for current language
2070 if (s.lang == currentLang) {
2071 // if query matches the doc title
2072 if (s.title.toLowerCase().match(textRegex)) {
2073 matched = true;
2074 s.matched_title = 1;
2075 }
2076 }
2077 if (matched) {
2078 gDocsMatches[matchedCountDocs] = s;
2079 matchedCountDocs++;
2080 }
2081 }
2082
2083
2084 // Search for About docs
2085 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2086 // current search comparison, with counters for tag and title,
2087 // used later to improve ranking
2088 var s = ABOUT_RESOURCES[i];
2089 s.matched_tag = 0;
2090 s.matched_title = 0;
2091 var matched = false;
2092
2093 // Check if query matches any tags; work backwards toward 1 to assist ranking
2094 for (var j = s.keywords.length - 1; j >= 0; j--) {
2095 // it matches a tag
2096 if (s.keywords[j].toLowerCase().match(textRegex)) {
2097 matched = true;
2098 s.matched_tag = j + 1; // add 1 to index position
2099 }
2100 }
2101 // Check if query matches the doc title, but only for current language
2102 if (s.lang == currentLang) {
2103 // if query matches the doc title
2104 if (s.title.toLowerCase().match(textRegex)) {
2105 matched = true;
2106 s.matched_title = 1;
2107 }
2108 }
2109 if (matched) {
2110 gDocsMatches[matchedCountDocs] = s;
2111 matchedCountDocs++;
2112 }
2113 }
2114
2115
2116 // Search for Design guides
2117 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2118 // current search comparison, with counters for tag and title,
2119 // used later to improve ranking
2120 var s = DESIGN_RESOURCES[i];
2121 s.matched_tag = 0;
2122 s.matched_title = 0;
2123 var matched = false;
2124
2125 // Check if query matches any tags; work backwards toward 1 to assist ranking
2126 for (var j = s.keywords.length - 1; j >= 0; j--) {
2127 // it matches a tag
2128 if (s.keywords[j].toLowerCase().match(textRegex)) {
2129 matched = true;
2130 s.matched_tag = j + 1; // add 1 to index position
2131 }
2132 }
2133 // Check if query matches the doc title, but only for current language
2134 if (s.lang == currentLang) {
2135 // if query matches the doc title
2136 if (s.title.toLowerCase().match(textRegex)) {
2137 matched = true;
2138 s.matched_title = 1;
2139 }
2140 }
2141 if (matched) {
2142 gDocsMatches[matchedCountDocs] = s;
2143 matchedCountDocs++;
2144 }
2145 }
2146
2147
2148 // Search for Distribute guides
2149 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2150 // current search comparison, with counters for tag and title,
2151 // used later to improve ranking
2152 var s = DISTRIBUTE_RESOURCES[i];
2153 s.matched_tag = 0;
2154 s.matched_title = 0;
2155 var matched = false;
2156
2157 // Check if query matches any tags; work backwards toward 1 to assist ranking
2158 for (var j = s.keywords.length - 1; j >= 0; j--) {
2159 // it matches a tag
2160 if (s.keywords[j].toLowerCase().match(textRegex)) {
2161 matched = true;
2162 s.matched_tag = j + 1; // add 1 to index position
2163 }
2164 }
2165 // Check if query matches the doc title, but only for current language
2166 if (s.lang == currentLang) {
2167 // if query matches the doc title
2168 if (s.title.toLowerCase().match(textRegex)) {
2169 matched = true;
2170 s.matched_title = 1;
2171 }
2172 }
2173 if (matched) {
2174 gDocsMatches[matchedCountDocs] = s;
2175 matchedCountDocs++;
2176 }
2177 }
2178
2179
2180 // Search for Google guides
2181 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2182 // current search comparison, with counters for tag and title,
2183 // used later to improve ranking
2184 var s = GOOGLE_RESOURCES[i];
2185 s.matched_tag = 0;
2186 s.matched_title = 0;
2187 var matched = false;
2188
2189 // Check if query matches any tags; work backwards toward 1 to assist ranking
2190 for (var j = s.keywords.length - 1; j >= 0; j--) {
2191 // it matches a tag
2192 if (s.keywords[j].toLowerCase().match(textRegex)) {
2193 matched = true;
2194 s.matched_tag = j + 1; // add 1 to index position
2195 }
2196 }
2197 // Check if query matches the doc title, but only for current language
2198 if (s.lang == currentLang) {
2199 // if query matches the doc title
2200 if (s.title.toLowerCase().match(textRegex)) {
2201 matched = true;
2202 s.matched_title = 1;
2203 }
2204 }
2205 if (matched) {
2206 gDocsMatches[matchedCountDocs] = s;
2207 matchedCountDocs++;
2208 }
2209 }
2210
2211
2212 // Search for Samples
2213 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2214 // current search comparison, with counters for tag and title,
2215 // used later to improve ranking
2216 var s = SAMPLES_RESOURCES[i];
2217 s.matched_tag = 0;
2218 s.matched_title = 0;
2219 var matched = false;
2220 // Check if query matches any tags; work backwards toward 1 to assist ranking
2221 for (var j = s.keywords.length - 1; j >= 0; j--) {
2222 // it matches a tag
2223 if (s.keywords[j].toLowerCase().match(textRegex)) {
2224 matched = true;
2225 s.matched_tag = j + 1; // add 1 to index position
2226 }
2227 }
2228 // Check if query matches the doc title, but only for current language
2229 if (s.lang == currentLang) {
2230 // if query matches the doc title.t
2231 if (s.title.toLowerCase().match(textRegex)) {
2232 matched = true;
2233 s.matched_title = 1;
2234 }
2235 }
2236 if (matched) {
2237 gDocsMatches[matchedCountDocs] = s;
2238 matchedCountDocs++;
2239 }
2240 }
2241
2242 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002243 rank_autocomplete_doc_results(text, gDocsMatches);
2244 }
2245
2246 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002247 sync_selection_table(toroot);
2248 return true; // allow the event to bubble up to the search api
2249 }
2250}
2251
Scott Main0e76e7e2013-03-12 10:24:07 -07002252/* Order the jd doc result list based on match quality */
2253function rank_autocomplete_doc_results(query, matches) {
2254 query = query || '';
2255 if (!matches || !matches.length)
2256 return;
2257
2258 var _resultScoreFn = function(match) {
2259 var score = 1.0;
2260
2261 // if the query matched a tag
2262 if (match.matched_tag > 0) {
2263 // multiply score by factor relative to position in tags list (max of 3)
2264 score *= 3 / match.matched_tag;
2265
2266 // if it also matched the title
2267 if (match.matched_title > 0) {
2268 score *= 2;
2269 }
2270 } else if (match.matched_title > 0) {
2271 score *= 3;
2272 }
2273
2274 return score;
2275 };
2276
2277 for (var i=0; i<matches.length; i++) {
2278 matches[i].__resultScore = _resultScoreFn(matches[i]);
2279 }
2280
2281 matches.sort(function(a,b){
2282 var n = b.__resultScore - a.__resultScore;
2283 if (n == 0) // lexicographical sort if scores are the same
2284 n = (a.label < b.label) ? -1 : 1;
2285 return n;
2286 });
2287}
2288
Scott Main7e447ed2013-02-19 17:22:37 -08002289/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002290function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002291 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002292 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002293 return;
2294
2295 // helper function that gets the last occurence index of the given regex
2296 // in the given string, or -1 if not found
2297 var _lastSearch = function(s, re) {
2298 if (s == '')
2299 return -1;
2300 var l = -1;
2301 var tmp;
2302 while ((tmp = s.search(re)) >= 0) {
2303 if (l < 0) l = 0;
2304 l += tmp;
2305 s = s.substr(tmp + 1);
2306 }
2307 return l;
2308 };
2309
2310 // helper function that counts the occurrences of a given character in
2311 // a given string
2312 var _countChar = function(s, c) {
2313 var n = 0;
2314 for (var i=0; i<s.length; i++)
2315 if (s.charAt(i) == c) ++n;
2316 return n;
2317 };
2318
2319 var queryLower = query.toLowerCase();
2320 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2321 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2322 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2323
2324 var _resultScoreFn = function(result) {
2325 // scores are calculated based on exact and prefix matches,
2326 // and then number of path separators (dots) from the last
2327 // match (i.e. favoring classes and deep package names)
2328 var score = 1.0;
2329 var labelLower = result.label.toLowerCase();
2330 var t;
2331 t = _lastSearch(labelLower, partExactAlnumRE);
2332 if (t >= 0) {
2333 // exact part match
2334 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2335 score *= 200 / (partsAfter + 1);
2336 } else {
2337 t = _lastSearch(labelLower, partPrefixAlnumRE);
2338 if (t >= 0) {
2339 // part prefix match
2340 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2341 score *= 20 / (partsAfter + 1);
2342 }
2343 }
2344
2345 return score;
2346 };
2347
Scott Main7e447ed2013-02-19 17:22:37 -08002348 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002349 // if the API is deprecated, default score is 0; otherwise, perform scoring
2350 if (matches[i].deprecated == "true") {
2351 matches[i].__resultScore = 0;
2352 } else {
2353 matches[i].__resultScore = _resultScoreFn(matches[i]);
2354 }
Scott Mainf5089842012-08-14 16:31:07 -07002355 }
2356
Scott Main7e447ed2013-02-19 17:22:37 -08002357 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002358 var n = b.__resultScore - a.__resultScore;
2359 if (n == 0) // lexicographical sort if scores are the same
2360 n = (a.label < b.label) ? -1 : 1;
2361 return n;
2362 });
2363}
2364
Scott Main7e447ed2013-02-19 17:22:37 -08002365/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002366function highlight_autocomplete_result_labels(query) {
2367 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002368 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002369 return;
2370
2371 var queryLower = query.toLowerCase();
2372 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2373 var queryRE = new RegExp(
2374 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2375 for (var i=0; i<gMatches.length; i++) {
2376 gMatches[i].__hilabel = gMatches[i].label.replace(
2377 queryRE, '<b>$1</b>');
2378 }
Scott Main7e447ed2013-02-19 17:22:37 -08002379 for (var i=0; i<gGoogleMatches.length; i++) {
2380 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2381 queryRE, '<b>$1</b>');
2382 }
Scott Mainf5089842012-08-14 16:31:07 -07002383}
2384
2385function search_focus_changed(obj, focused)
2386{
Scott Main3b90aff2013-08-01 18:09:35 -07002387 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002388 if(obj.value == ""){
2389 $(".search .close").addClass("hide");
2390 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002391 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002392 }
2393}
2394
2395function submit_search() {
2396 var query = document.getElementById('search_autocomplete').value;
2397 location.hash = 'q=' + query;
2398 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002399 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002400 return false;
2401}
2402
2403
2404function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002405 $("#searchResults").slideUp('fast', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002406 $(".search .close").addClass("hide");
2407 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002408
Scott Mainf5089842012-08-14 16:31:07 -07002409 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002410
Scott Mainf5089842012-08-14 16:31:07 -07002411 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2412 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002413
2414 // forcefully regain key-up event control (previously jacked by search api)
2415 $("#search_autocomplete").keyup(function(event) {
2416 return search_changed(event, false, toRoot);
2417 });
2418
Scott Mainf5089842012-08-14 16:31:07 -07002419 return false;
2420}
2421
2422
2423
2424/* ########################################################## */
2425/* ################ CUSTOM SEARCH ENGINE ################## */
2426/* ########################################################## */
2427
Scott Mainf5089842012-08-14 16:31:07 -07002428var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002429google.load('search', '1', {"callback" : function() {
2430 searchControl = new google.search.SearchControl();
2431 } });
Scott Mainf5089842012-08-14 16:31:07 -07002432
2433function loadSearchResults() {
2434 document.getElementById("search_autocomplete").style.color = "#000";
2435
Scott Mainf5089842012-08-14 16:31:07 -07002436 searchControl = new google.search.SearchControl();
2437
2438 // use our existing search form and use tabs when multiple searchers are used
2439 drawOptions = new google.search.DrawOptions();
2440 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2441 drawOptions.setInput(document.getElementById("search_autocomplete"));
2442
2443 // configure search result options
2444 searchOptions = new google.search.SearcherOptions();
2445 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2446
2447 // configure each of the searchers, for each tab
2448 devSiteSearcher = new google.search.WebSearch();
2449 devSiteSearcher.setUserDefinedLabel("All");
2450 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2451
2452 designSearcher = new google.search.WebSearch();
2453 designSearcher.setUserDefinedLabel("Design");
2454 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2455
2456 trainingSearcher = new google.search.WebSearch();
2457 trainingSearcher.setUserDefinedLabel("Training");
2458 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2459
2460 guidesSearcher = new google.search.WebSearch();
2461 guidesSearcher.setUserDefinedLabel("Guides");
2462 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2463
2464 referenceSearcher = new google.search.WebSearch();
2465 referenceSearcher.setUserDefinedLabel("Reference");
2466 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2467
Scott Maindf08ada2012-12-03 08:54:37 -08002468 googleSearcher = new google.search.WebSearch();
2469 googleSearcher.setUserDefinedLabel("Google Services");
2470 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2471
Scott Mainf5089842012-08-14 16:31:07 -07002472 blogSearcher = new google.search.WebSearch();
2473 blogSearcher.setUserDefinedLabel("Blog");
2474 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2475
2476 // add each searcher to the search control
2477 searchControl.addSearcher(devSiteSearcher, searchOptions);
2478 searchControl.addSearcher(designSearcher, searchOptions);
2479 searchControl.addSearcher(trainingSearcher, searchOptions);
2480 searchControl.addSearcher(guidesSearcher, searchOptions);
2481 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002482 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002483 searchControl.addSearcher(blogSearcher, searchOptions);
2484
2485 // configure result options
2486 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2487 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2488 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2489 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2490
2491 // upon ajax search, refresh the url and search title
2492 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2493 updateResultTitle(query);
2494 var query = document.getElementById('search_autocomplete').value;
2495 location.hash = 'q=' + query;
2496 });
2497
Scott Mainde295272013-03-25 15:48:35 -07002498 // once search results load, set up click listeners
2499 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2500 addResultClickListeners();
2501 });
2502
Scott Mainf5089842012-08-14 16:31:07 -07002503 // draw the search results box
2504 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2505
2506 // get query and execute the search
2507 searchControl.execute(decodeURI(getQuery(location.hash)));
2508
2509 document.getElementById("search_autocomplete").focus();
2510 addTabListeners();
2511}
2512// End of loadSearchResults
2513
2514
2515google.setOnLoadCallback(function(){
2516 if (location.hash.indexOf("q=") == -1) {
2517 // if there's no query in the url, don't search and make sure results are hidden
2518 $('#searchResults').hide();
2519 return;
2520 } else {
2521 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002522 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002523 $(".search .close").removeClass("hide");
2524 loadSearchResults();
2525 }
2526}, true);
2527
smain@google.com9a818f52014-10-03 09:25:59 -07002528/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2529 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002530function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002531 // Ignore if there's no search bar (some special pages have no header)
2532 if ($("#search-container").length < 1) return;
2533
smain@google.com3b77ab52014-06-17 11:57:27 -07002534 var hash = escape(location.hash.substr(1));
2535 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002536 // Sanity check that there's an element with that ID on the page
2537 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002538 // If the position of the target element is near the top of the page (<20px, where we expect it
2539 // to be because we need to move it down 60px to become in view), then move it down 60px
2540 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2541 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002542 }
2543 }
2544}
2545
Scott Mainf5089842012-08-14 16:31:07 -07002546// when an event on the browser history occurs (back, forward, load) requery hash and do search
2547$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002548 // Ignore if there's no search bar (some special pages have no header)
2549 if ($("#search-container").length < 1) return;
2550
Dirk Doughertyc3921652014-05-13 16:55:26 -07002551 // If the hash isn't a search query or there's an error in the query,
2552 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002553 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2554 // If the results pane is open, close it.
2555 if (!$("#searchResults").is(":hidden")) {
2556 hideResults();
2557 }
Scott Mainb16376f2014-05-21 20:35:47 -07002558 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002559 return;
2560 }
2561
2562 // Otherwise, we have a search to do
2563 var query = decodeURI(getQuery(location.hash));
2564 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002565 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002566 $("#search_autocomplete").focus();
2567 $(".search .close").removeClass("hide");
2568
2569 updateResultTitle(query);
2570});
2571
2572function updateResultTitle(query) {
2573 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2574}
2575
2576// forcefully regain key-up event control (previously jacked by search api)
2577$("#search_autocomplete").keyup(function(event) {
2578 return search_changed(event, false, toRoot);
2579});
2580
2581// add event listeners to each tab so we can track the browser history
2582function addTabListeners() {
2583 var tabHeaders = $(".gsc-tabHeader");
2584 for (var i = 0; i < tabHeaders.length; i++) {
2585 $(tabHeaders[i]).attr("id",i).click(function() {
2586 /*
2587 // make a copy of the page numbers for the search left pane
2588 setTimeout(function() {
2589 // remove any residual page numbers
2590 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002591 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002592 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002593 // and because we're going to remove it (previous line),
2594 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002595 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2596 .clone().appendTo('#searchResults .gsc-tabsArea');
2597 }, 200);
2598 */
2599 });
2600 }
2601 setTimeout(function(){$(tabHeaders[0]).click()},200);
2602}
2603
Scott Mainde295272013-03-25 15:48:35 -07002604// add analytics tracking events to each result link
2605function addResultClickListeners() {
2606 $("#searchResults a.gs-title").each(function(index, link) {
2607 // When user clicks enter for Google search results, track it
2608 $(link).click(function() {
smain@google.com633f3222014-10-03 15:49:45 -07002609 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).text(),
2610 'from: ' + $("#search_autocomplete").val());
Scott Mainde295272013-03-25 15:48:35 -07002611 });
2612 });
2613}
2614
Scott Mainf5089842012-08-14 16:31:07 -07002615
2616function getQuery(hash) {
2617 var queryParts = hash.split('=');
2618 return queryParts[1];
2619}
2620
2621/* returns the given string with all HTML brackets converted to entities
2622 TODO: move this to the site's JS library */
2623function escapeHTML(string) {
2624 return string.replace(/</g,"&lt;")
2625 .replace(/>/g,"&gt;");
2626}
2627
2628
2629
2630
2631
2632
2633
2634/* ######################################################## */
2635/* ################# JAVADOC REFERENCE ################### */
2636/* ######################################################## */
2637
Scott Main65511c02012-09-07 15:51:32 -07002638/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002639if (location.pathname.indexOf("/reference") == 0) {
2640 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2641 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2642 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002643 $(document).ready(function() {
2644 // init available apis based on user pref
2645 changeApiLevel();
2646 initSidenavHeightResize()
2647 });
2648 }
Scott Main65511c02012-09-07 15:51:32 -07002649}
Scott Mainf5089842012-08-14 16:31:07 -07002650
2651var API_LEVEL_COOKIE = "api_level";
2652var minLevel = 1;
2653var maxLevel = 1;
2654
2655/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002656
Scott Mainf5089842012-08-14 16:31:07 -07002657 function initSidenavHeightResize() {
2658 // Change the drag bar size to nicely fit the scrollbar positions
2659 var $dragBar = $(".ui-resizable-s");
2660 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002661
2662 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002663 containment: "#nav-panels",
2664 handles: "s",
2665 alsoResize: "#packages-nav",
2666 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2667 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2668 });
Scott Main3b90aff2013-08-01 18:09:35 -07002669
Scott Mainf5089842012-08-14 16:31:07 -07002670 }
Scott Main3b90aff2013-08-01 18:09:35 -07002671
Scott Mainf5089842012-08-14 16:31:07 -07002672function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002673 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002674 $('#devdoc-nav').css({
2675 'width' : $('#side-nav').css('width'),
2676 'margin' : $('#side-nav').css('margin')
2677 });
2678 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002679
Scott Mainf5089842012-08-14 16:31:07 -07002680 initSidenavHeightResize();
2681}
2682
2683function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002684 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002685 $('#devdoc-nav').css({
2686 'width' : $('#side-nav').css('width'),
2687 'margin' : $('#side-nav').css('margin')
2688 });
2689 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002690
Scott Mainf5089842012-08-14 16:31:07 -07002691 initSidenavHeightResize();
2692}
2693
2694function buildApiLevelSelector() {
2695 maxLevel = SINCE_DATA.length;
2696 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2697 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2698
2699 minLevel = parseInt($("#doc-api-level").attr("class"));
2700 // Handle provisional api levels; the provisional level will always be the highest possible level
2701 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2702 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2703 if (isNaN(minLevel) && minLevel.length) {
2704 minLevel = maxLevel;
2705 }
2706 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2707 for (var i = maxLevel-1; i >= 0; i--) {
2708 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2709 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2710 select.append(option);
2711 }
2712
2713 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2714 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2715 selectedLevelItem.setAttribute('selected',true);
2716}
2717
2718function changeApiLevel() {
2719 maxLevel = SINCE_DATA.length;
2720 var selectedLevel = maxLevel;
2721
2722 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2723 toggleVisisbleApis(selectedLevel, "body");
2724
smain@google.com6bdcb982014-11-14 11:53:07 -08002725 writeCookie(API_LEVEL_COOKIE, selectedLevel, null);
Scott Mainf5089842012-08-14 16:31:07 -07002726
2727 if (selectedLevel < minLevel) {
2728 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002729 $("#naMessage").show().html("<div><p><strong>This " + thing
2730 + " requires API level " + minLevel + " or higher.</strong></p>"
2731 + "<p>This document is hidden because your selected API level for the documentation is "
2732 + selectedLevel + ". You can change the documentation API level with the selector "
2733 + "above the left navigation.</p>"
2734 + "<p>For more information about specifying the API level your app requires, "
2735 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2736 + ">Supporting Different Platform Versions</a>.</p>"
2737 + "<input type='button' value='OK, make this page visible' "
2738 + "title='Change the API level to " + minLevel + "' "
2739 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2740 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002741 } else {
2742 $("#naMessage").hide();
2743 }
2744}
2745
2746function toggleVisisbleApis(selectedLevel, context) {
2747 var apis = $(".api",context);
2748 apis.each(function(i) {
2749 var obj = $(this);
2750 var className = obj.attr("class");
2751 var apiLevelIndex = className.lastIndexOf("-")+1;
2752 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2753 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2754 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2755 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2756 return;
2757 }
2758 apiLevel = parseInt(apiLevel);
2759
2760 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2761 var selectedLevelNum = parseInt(selectedLevel)
2762 var apiLevelNum = parseInt(apiLevel);
2763 if (isNaN(apiLevelNum)) {
2764 apiLevelNum = maxLevel;
2765 }
2766
2767 // Grey things out that aren't available and give a tooltip title
2768 if (apiLevelNum > selectedLevelNum) {
2769 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002770 + apiLevel + "\" or higher. To reveal, change the target API level "
2771 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002772 }
Scott Mainf5089842012-08-14 16:31:07 -07002773 else obj.removeClass("absent").removeAttr("title");
2774 });
2775}
2776
2777
2778
2779
2780/* ################# SIDENAV TREE VIEW ################### */
2781
2782function new_node(me, mom, text, link, children_data, api_level)
2783{
2784 var node = new Object();
2785 node.children = Array();
2786 node.children_data = children_data;
2787 node.depth = mom.depth + 1;
2788
2789 node.li = document.createElement("li");
2790 mom.get_children_ul().appendChild(node.li);
2791
2792 node.label_div = document.createElement("div");
2793 node.label_div.className = "label";
2794 if (api_level != null) {
2795 $(node.label_div).addClass("api");
2796 $(node.label_div).addClass("api-level-"+api_level);
2797 }
2798 node.li.appendChild(node.label_div);
2799
2800 if (children_data != null) {
2801 node.expand_toggle = document.createElement("a");
2802 node.expand_toggle.href = "javascript:void(0)";
2803 node.expand_toggle.onclick = function() {
2804 if (node.expanded) {
2805 $(node.get_children_ul()).slideUp("fast");
2806 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2807 node.expanded = false;
2808 } else {
2809 expand_node(me, node);
2810 }
2811 };
2812 node.label_div.appendChild(node.expand_toggle);
2813
2814 node.plus_img = document.createElement("img");
2815 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2816 node.plus_img.className = "plus";
2817 node.plus_img.width = "8";
2818 node.plus_img.border = "0";
2819 node.expand_toggle.appendChild(node.plus_img);
2820
2821 node.expanded = false;
2822 }
2823
2824 var a = document.createElement("a");
2825 node.label_div.appendChild(a);
2826 node.label = document.createTextNode(text);
2827 a.appendChild(node.label);
2828 if (link) {
2829 a.href = me.toroot + link;
2830 } else {
2831 if (children_data != null) {
2832 a.className = "nolink";
2833 a.href = "javascript:void(0)";
2834 a.onclick = node.expand_toggle.onclick;
2835 // This next line shouldn't be necessary. I'll buy a beer for the first
2836 // person who figures out how to remove this line and have the link
2837 // toggle shut on the first try. --joeo@android.com
2838 node.expanded = false;
2839 }
2840 }
Scott Main3b90aff2013-08-01 18:09:35 -07002841
Scott Mainf5089842012-08-14 16:31:07 -07002842
2843 node.children_ul = null;
2844 node.get_children_ul = function() {
2845 if (!node.children_ul) {
2846 node.children_ul = document.createElement("ul");
2847 node.children_ul.className = "children_ul";
2848 node.children_ul.style.display = "none";
2849 node.li.appendChild(node.children_ul);
2850 }
2851 return node.children_ul;
2852 };
2853
2854 return node;
2855}
2856
Robert Lyd2dd6e52012-11-29 21:28:48 -08002857
2858
2859
Scott Mainf5089842012-08-14 16:31:07 -07002860function expand_node(me, node)
2861{
2862 if (node.children_data && !node.expanded) {
2863 if (node.children_visited) {
2864 $(node.get_children_ul()).slideDown("fast");
2865 } else {
2866 get_node(me, node);
2867 if ($(node.label_div).hasClass("absent")) {
2868 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07002869 }
Scott Mainf5089842012-08-14 16:31:07 -07002870 $(node.get_children_ul()).slideDown("fast");
2871 }
2872 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2873 node.expanded = true;
2874
2875 // perform api level toggling because new nodes are new to the DOM
2876 var selectedLevel = $("#apiLevelSelector option:selected").val();
2877 toggleVisisbleApis(selectedLevel, "#side-nav");
2878 }
2879}
2880
2881function get_node(me, mom)
2882{
2883 mom.children_visited = true;
2884 for (var i in mom.children_data) {
2885 var node_data = mom.children_data[i];
2886 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2887 node_data[2], node_data[3]);
2888 }
2889}
2890
2891function this_page_relative(toroot)
2892{
2893 var full = document.location.pathname;
2894 var file = "";
2895 if (toroot.substr(0, 1) == "/") {
2896 if (full.substr(0, toroot.length) == toroot) {
2897 return full.substr(toroot.length);
2898 } else {
2899 // the file isn't under toroot. Fail.
2900 return null;
2901 }
2902 } else {
2903 if (toroot != "./") {
2904 toroot = "./" + toroot;
2905 }
2906 do {
2907 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
2908 var pos = full.lastIndexOf("/");
2909 file = full.substr(pos) + file;
2910 full = full.substr(0, pos);
2911 toroot = toroot.substr(0, toroot.length-3);
2912 }
2913 } while (toroot != "" && toroot != "/");
2914 return file.substr(1);
2915 }
2916}
2917
2918function find_page(url, data)
2919{
2920 var nodes = data;
2921 var result = null;
2922 for (var i in nodes) {
2923 var d = nodes[i];
2924 if (d[1] == url) {
2925 return new Array(i);
2926 }
2927 else if (d[2] != null) {
2928 result = find_page(url, d[2]);
2929 if (result != null) {
2930 return (new Array(i).concat(result));
2931 }
2932 }
2933 }
2934 return null;
2935}
2936
Scott Mainf5089842012-08-14 16:31:07 -07002937function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07002938 // load json file for navtree data
2939 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
2940 // when the file is loaded, initialize the tree
2941 if(jqxhr.status === 200) {
2942 init_navtree("tree-list", toroot, NAVTREE_DATA);
2943 }
2944 });
Scott Main3b90aff2013-08-01 18:09:35 -07002945
Scott Mainf5089842012-08-14 16:31:07 -07002946 // perform api level toggling because because the whole tree is new to the DOM
2947 var selectedLevel = $("#apiLevelSelector option:selected").val();
2948 toggleVisisbleApis(selectedLevel, "#side-nav");
2949}
2950
2951function init_navtree(navtree_id, toroot, root_nodes)
2952{
2953 var me = new Object();
2954 me.toroot = toroot;
2955 me.node = new Object();
2956
2957 me.node.li = document.getElementById(navtree_id);
2958 me.node.children_data = root_nodes;
2959 me.node.children = new Array();
2960 me.node.children_ul = document.createElement("ul");
2961 me.node.get_children_ul = function() { return me.node.children_ul; };
2962 //me.node.children_ul.className = "children_ul";
2963 me.node.li.appendChild(me.node.children_ul);
2964 me.node.depth = 0;
2965
2966 get_node(me, me.node);
2967
2968 me.this_page = this_page_relative(toroot);
2969 me.breadcrumbs = find_page(me.this_page, root_nodes);
2970 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
2971 var mom = me.node;
2972 for (var i in me.breadcrumbs) {
2973 var j = me.breadcrumbs[i];
2974 mom = mom.children[j];
2975 expand_node(me, mom);
2976 }
2977 mom.label_div.className = mom.label_div.className + " selected";
2978 addLoadEvent(function() {
2979 scrollIntoView("nav-tree");
2980 });
2981 }
2982}
2983
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07002984
2985
2986
2987
2988
2989
2990
Robert Lyd2dd6e52012-11-29 21:28:48 -08002991/* TODO: eliminate redundancy with non-google functions */
2992function init_google_navtree(navtree_id, toroot, root_nodes)
2993{
2994 var me = new Object();
2995 me.toroot = toroot;
2996 me.node = new Object();
2997
2998 me.node.li = document.getElementById(navtree_id);
2999 me.node.children_data = root_nodes;
3000 me.node.children = new Array();
3001 me.node.children_ul = document.createElement("ul");
3002 me.node.get_children_ul = function() { return me.node.children_ul; };
3003 //me.node.children_ul.className = "children_ul";
3004 me.node.li.appendChild(me.node.children_ul);
3005 me.node.depth = 0;
3006
3007 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003008}
3009
3010function new_google_node(me, mom, text, link, children_data, api_level)
3011{
3012 var node = new Object();
3013 var child;
3014 node.children = Array();
3015 node.children_data = children_data;
3016 node.depth = mom.depth + 1;
3017 node.get_children_ul = function() {
3018 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003019 node.children_ul = document.createElement("ul");
3020 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003021 node.li.appendChild(node.children_ul);
3022 }
3023 return node.children_ul;
3024 };
3025 node.li = document.createElement("li");
3026
3027 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003028
3029
Robert Lyd2dd6e52012-11-29 21:28:48 -08003030 if(link) {
3031 child = document.createElement("a");
3032
3033 }
3034 else {
3035 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003036 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003037
3038 }
3039 if (children_data != null) {
3040 node.li.className="nav-section";
3041 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003042 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003043 node.li.appendChild(node.label_div);
3044 get_google_node(me, node);
3045 node.label_div.appendChild(child);
3046 }
3047 else {
3048 node.li.appendChild(child);
3049 }
3050 if(link) {
3051 child.href = me.toroot + link;
3052 }
3053 node.label = document.createTextNode(text);
3054 child.appendChild(node.label);
3055
3056 node.children_ul = null;
3057
3058 return node;
3059}
3060
3061function get_google_node(me, mom)
3062{
3063 mom.children_visited = true;
3064 var linkText;
3065 for (var i in mom.children_data) {
3066 var node_data = mom.children_data[i];
3067 linkText = node_data[0];
3068
3069 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3070 linkText = linkText.substr(19, linkText.length);
3071 }
3072 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3073 node_data[2], node_data[3]);
3074 }
3075}
Scott Mainad08f072013-08-20 16:49:57 -07003076
3077
3078
3079
3080
3081
3082/****** NEW version of script to build google and sample navs dynamically ******/
3083// TODO: update Google reference docs to tolerate this new implementation
3084
Scott Maine624b3f2013-09-12 12:56:41 -07003085var NODE_NAME = 0;
3086var NODE_HREF = 1;
3087var NODE_GROUP = 2;
3088var NODE_TAGS = 3;
3089var NODE_CHILDREN = 4;
3090
Scott Mainad08f072013-08-20 16:49:57 -07003091function init_google_navtree2(navtree_id, data)
3092{
3093 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003094 for (var i in data) {
3095 var node_data = data[i];
3096 $containerUl.append(new_google_node2(node_data));
3097 }
3098
Scott Main70557ee2013-10-30 14:47:40 -07003099 // Make all third-generation list items 'sticky' to prevent them from collapsing
3100 $containerUl.find('li li li.nav-section').addClass('sticky');
3101
Scott Mainad08f072013-08-20 16:49:57 -07003102 initExpandableNavItems("#"+navtree_id);
3103}
3104
3105function new_google_node2(node_data)
3106{
Scott Maine624b3f2013-09-12 12:56:41 -07003107 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003108 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3109 linkText = linkText.substr(19, linkText.length);
3110 }
3111 var $li = $('<li>');
3112 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003113 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003114 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3115 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003116 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003117 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3118 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003119 }
3120 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003121 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003122 $li.addClass("nav-section");
3123 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003124 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003125
Scott Maine624b3f2013-09-12 12:56:41 -07003126 for (var i in node_data[NODE_CHILDREN]) {
3127 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003128 $childUl.append(new_google_node2(child_node_data));
3129 }
3130 $li.append($childUl);
3131 }
3132 $li.prepend($a);
3133
3134 return $li;
3135}
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
Robert Lyd2dd6e52012-11-29 21:28:48 -08003147function showGoogleRefTree() {
3148 init_default_google_navtree(toRoot);
3149 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003150}
3151
3152function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003153 // load json file for navtree data
3154 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3155 // when the file is loaded, initialize the tree
3156 if(jqxhr.status === 200) {
3157 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3158 highlightSidenav();
3159 resizeNav();
3160 }
3161 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003162}
3163
3164function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003165 // load json file for navtree data
3166 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3167 // when the file is loaded, initialize the tree
3168 if(jqxhr.status === 200) {
3169 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3170 highlightSidenav();
3171 resizeNav();
3172 }
3173 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003174}
3175
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003176function showSamplesRefTree() {
3177 init_default_samples_navtree(toRoot);
3178}
3179
3180function init_default_samples_navtree(toroot) {
3181 // load json file for navtree data
3182 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3183 // when the file is loaded, initialize the tree
3184 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003185 // hack to remove the "about the samples" link then put it back in
3186 // after we nuke the list to remove the dummy static list of samples
3187 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3188 $("#nav.samples-nav").empty();
3189 $("#nav.samples-nav").append($firstLi);
3190
Scott Mainad08f072013-08-20 16:49:57 -07003191 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003192 highlightSidenav();
3193 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003194 if ($("#jd-content #samples").length) {
3195 showSamples();
3196 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003197 }
3198 });
3199}
3200
Scott Mainf5089842012-08-14 16:31:07 -07003201/* TOGGLE INHERITED MEMBERS */
3202
3203/* Toggle an inherited class (arrow toggle)
3204 * @param linkObj The link that was clicked.
3205 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3206 * 'null' to simply toggle.
3207 */
3208function toggleInherited(linkObj, expand) {
3209 var base = linkObj.getAttribute("id");
3210 var list = document.getElementById(base + "-list");
3211 var summary = document.getElementById(base + "-summary");
3212 var trigger = document.getElementById(base + "-trigger");
3213 var a = $(linkObj);
3214 if ( (expand == null && a.hasClass("closed")) || expand ) {
3215 list.style.display = "none";
3216 summary.style.display = "block";
3217 trigger.src = toRoot + "assets/images/triangle-opened.png";
3218 a.removeClass("closed");
3219 a.addClass("opened");
3220 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3221 list.style.display = "block";
3222 summary.style.display = "none";
3223 trigger.src = toRoot + "assets/images/triangle-closed.png";
3224 a.removeClass("opened");
3225 a.addClass("closed");
3226 }
3227 return false;
3228}
3229
3230/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3231 * @param linkObj The link that was clicked.
3232 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3233 * 'null' to simply toggle.
3234 */
3235function toggleAllInherited(linkObj, expand) {
3236 var a = $(linkObj);
3237 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3238 var expandos = $(".jd-expando-trigger", table);
3239 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3240 expandos.each(function(i) {
3241 toggleInherited(this, true);
3242 });
3243 a.text("[Collapse]");
3244 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3245 expandos.each(function(i) {
3246 toggleInherited(this, false);
3247 });
3248 a.text("[Expand]");
3249 }
3250 return false;
3251}
3252
3253/* Toggle all inherited members in the class (link in the class title)
3254 */
3255function toggleAllClassInherited() {
3256 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3257 var toggles = $(".toggle-all", $("#body-content"));
3258 if (a.text() == "[Expand All]") {
3259 toggles.each(function(i) {
3260 toggleAllInherited(this, true);
3261 });
3262 a.text("[Collapse All]");
3263 } else {
3264 toggles.each(function(i) {
3265 toggleAllInherited(this, false);
3266 });
3267 a.text("[Expand All]");
3268 }
3269 return false;
3270}
3271
3272/* Expand all inherited members in the class. Used when initiating page search */
3273function ensureAllInheritedExpanded() {
3274 var toggles = $(".toggle-all", $("#body-content"));
3275 toggles.each(function(i) {
3276 toggleAllInherited(this, true);
3277 });
3278 $("#toggleAllClassInherited").text("[Collapse All]");
3279}
3280
3281
3282/* HANDLE KEY EVENTS
3283 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3284 */
3285var agent = navigator['userAgent'].toLowerCase();
3286var mac = agent.indexOf("macintosh") != -1;
3287
3288$(document).keydown( function(e) {
3289var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3290 if (control && e.which == 70) { // 70 is "F"
3291 ensureAllInheritedExpanded();
3292 }
3293});
Scott Main498d7102013-08-21 15:47:38 -07003294
3295
3296
3297
3298
3299
3300/* On-demand functions */
3301
3302/** Move sample code line numbers out of PRE block and into non-copyable column */
3303function initCodeLineNumbers() {
3304 var numbers = $("#codesample-block a.number");
3305 if (numbers.length) {
3306 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3307 }
3308
3309 $(document).ready(function() {
3310 // select entire line when clicked
3311 $("span.code-line").click(function() {
3312 if (!shifted) {
3313 selectText(this);
3314 }
3315 });
3316 // invoke line link on double click
3317 $(".code-line").dblclick(function() {
3318 document.location.hash = $(this).attr('id');
3319 });
3320 // highlight the line when hovering on the number
3321 $("#codesample-line-numbers a.number").mouseover(function() {
3322 var id = $(this).attr('href');
3323 $(id).css('background','#e7e7e7');
3324 });
3325 $("#codesample-line-numbers a.number").mouseout(function() {
3326 var id = $(this).attr('href');
3327 $(id).css('background','none');
3328 });
3329 });
3330}
3331
3332// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3333var shifted = false;
3334$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3335
3336// courtesy of jasonedelman.com
3337function selectText(element) {
3338 var doc = document
3339 , range, selection
3340 ;
3341 if (doc.body.createTextRange) { //ms
3342 range = doc.body.createTextRange();
3343 range.moveToElementText(element);
3344 range.select();
3345 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003346 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003347 range = doc.createRange();
3348 range.selectNodeContents(element);
3349 selection.removeAllRanges();
3350 selection.addRange(range);
3351 }
Scott Main285f0772013-08-22 23:22:09 +00003352}
Scott Main03aca9a2013-10-31 07:20:55 -07003353
3354
3355
3356
3357/** Display links and other information about samples that match the
3358 group specified by the URL */
3359function showSamples() {
3360 var group = $("#samples").attr('class');
3361 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3362
3363 var $ul = $("<ul>");
3364 $selectedLi = $("#nav li.selected");
3365
3366 $selectedLi.children("ul").children("li").each(function() {
3367 var $li = $("<li>").append($(this).find("a").first().clone());
3368 $ul.append($li);
3369 });
3370
3371 $("#samples").append($ul);
3372
3373}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003374
3375
3376
3377/* ########################################################## */
3378/* ################### RESOURCE CARDS ##################### */
3379/* ########################################################## */
3380
3381/** Handle resource queries, collections, and grids (sections). Requires
3382 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3383
3384(function() {
3385 // Prevent the same resource from being loaded more than once per page.
3386 var addedPageResources = {};
3387
3388 $(document).ready(function() {
3389 $('.resource-widget').each(function() {
3390 initResourceWidget(this);
3391 });
3392
3393 /* Pass the line height to ellipsisfade() to adjust the height of the
3394 text container to show the max number of lines possible, without
3395 showing lines that are cut off. This works with the css ellipsis
3396 classes to fade last text line and apply an ellipsis char. */
3397
Scott Mainb16376f2014-05-21 20:35:47 -07003398 //card text currently uses 15px line height.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003399 var lineHeight = 15;
3400 $('.card-info .text').ellipsisfade(lineHeight);
3401 });
3402
3403 /*
3404 Three types of resource layouts:
3405 Flow - Uses a fixed row-height flow using float left style.
3406 Carousel - Single card slideshow all same dimension absolute.
3407 Stack - Uses fixed columns and flexible element height.
3408 */
3409 function initResourceWidget(widget) {
3410 var $widget = $(widget);
3411 var isFlow = $widget.hasClass('resource-flow-layout'),
3412 isCarousel = $widget.hasClass('resource-carousel-layout'),
3413 isStack = $widget.hasClass('resource-stack-layout');
3414
3415 // find size of widget by pulling out its class name
3416 var sizeCols = 1;
3417 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
3418 if (m) {
3419 sizeCols = parseInt(m[1], 10);
3420 }
3421
3422 var opts = {
3423 cardSizes: ($widget.data('cardsizes') || '').split(','),
3424 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3425 itemsPerPage: $widget.data('itemsperpage'),
3426 sortOrder: $widget.data('sortorder'),
3427 query: $widget.data('query'),
3428 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003429 sizeCols: sizeCols,
3430 /* Added by LFL 6/6/14 */
3431 resourceStyle: $widget.data('resourcestyle') || 'card',
3432 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003433 };
3434
3435 // run the search for the set of resources to show
3436
3437 var resources = buildResourceList(opts);
3438
3439 if (isFlow) {
3440 drawResourcesFlowWidget($widget, opts, resources);
3441 } else if (isCarousel) {
3442 drawResourcesCarouselWidget($widget, opts, resources);
3443 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003444 /* Looks like this got removed and is not used, so repurposing for the
3445 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003446 Modified by LFL 6/6/14
3447 */
3448 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003449 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003450 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003451 }
3452 }
3453
3454 /* Initializes a Resource Carousel Widget */
3455 function drawResourcesCarouselWidget($widget, opts, resources) {
3456 $widget.empty();
3457 var plusone = true; //always show plusone on carousel
3458
3459 $widget.addClass('resource-card slideshow-container')
3460 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3461 .append($('<a>').addClass('slideshow-next').text('Next'));
3462
3463 var css = { 'width': $widget.width() + 'px',
3464 'height': $widget.height() + 'px' };
3465
3466 var $ul = $('<ul>');
3467
3468 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003469 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003470 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003471 .decorateResourceCard(resources[i],plusone);
3472
3473 $('<li>').css(css)
3474 .append($card)
3475 .appendTo($ul);
3476 }
3477
3478 $('<div>').addClass('frame')
3479 .append($ul)
3480 .appendTo($widget);
3481
3482 $widget.dacSlideshow({
3483 auto: true,
3484 btnPrev: '.slideshow-prev',
3485 btnNext: '.slideshow-next'
3486 });
3487 };
3488
Robert Lye7eeb402014-06-03 19:35:24 -07003489 /* Initializes a Resource Card Stack Widget (column-based layout)
3490 Modified by LFL 6/6/14
3491 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003492 function drawResourcesStackWidget($widget, opts, resources, sections) {
3493 // Don't empty widget, grab all items inside since they will be the first
3494 // items stacked, followed by the resource query
3495 var plusone = true; //by default show plusone on section cards
3496 var cards = $widget.find('.resource-card').detach().toArray();
3497 var numStacks = opts.numStacks || 1;
3498 var $stacks = [];
3499 var urlString;
3500
3501 for (var i = 0; i < numStacks; ++i) {
3502 $stacks[i] = $('<div>').addClass('resource-card-stack')
3503 .appendTo($widget);
3504 }
3505
3506 var sectionResources = [];
3507
3508 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003509 if (sections) {
3510 for (var i = 0; i < sections.length; ++i) {
3511 if (!sections[i].sections || !sections[i].sections.length) {
3512 // Render it as a resource card
3513 sectionResources.push(
3514 $('<a>')
3515 .addClass('resource-card section-card')
3516 .attr('href', cleanUrl(sections[i].resource.url))
3517 .decorateResourceCard(sections[i].resource,plusone)[0]
3518 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003519
Robert Lye7eeb402014-06-03 19:35:24 -07003520 } else {
3521 cards.push(
3522 $('<div>')
3523 .addClass('resource-card section-card-menu')
3524 .decorateResourceSection(sections[i],plusone)[0]
3525 );
3526 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003527 }
3528 }
3529
3530 cards = cards.concat(sectionResources);
3531
3532 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003533 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003534
Robert Lye7eeb402014-06-03 19:35:24 -07003535 if (opts.resourceStyle.indexOf('related') > -1) {
3536 $card.addClass('related-card');
3537 }
smain@google.com95948b82014-06-16 19:24:25 -07003538
Dirk Doughertyc3921652014-05-13 16:55:26 -07003539 cards.push($card[0]);
3540 }
3541
Robert Lye7eeb402014-06-03 19:35:24 -07003542 if (opts.stackSort != 'false') {
3543 for (var i = 0; i < cards.length; ++i) {
3544 // Find the stack with the shortest height, but give preference to
3545 // left to right order.
3546 var minHeight = $stacks[0].height();
3547 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003548
Robert Lye7eeb402014-06-03 19:35:24 -07003549 for (var j = 1; j < numStacks; ++j) {
3550 var height = $stacks[j].height();
3551 if (height < minHeight - 45) {
3552 minHeight = height;
3553 minIndex = j;
3554 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003555 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003556
Robert Lye7eeb402014-06-03 19:35:24 -07003557 $stacks[minIndex].append($(cards[i]));
3558 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003559 }
3560
3561 };
smain@google.com95948b82014-06-16 19:24:25 -07003562
3563 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003564 Create a resource card using the given resource object and a list of html
3565 configured options. Returns a jquery object containing the element.
3566 */
smain@google.com95948b82014-06-16 19:24:25 -07003567 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003568 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003569
Robert Lye7eeb402014-06-03 19:35:24 -07003570 // The difference here is that generic cards are not entirely clickable
3571 // so its a div instead of an a tag, also the generic one is not given
3572 // the resource-card class so it appears with a transparent background
3573 // and can be styled in whatever way the css setup.
3574 if (opts.resourceStyle == 'generic') {
3575 $el = $('<div>')
3576 .addClass('resource')
3577 .attr('href', cleanUrl(resource.url))
3578 .decorateResource(resource, opts);
3579 } else {
3580 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003581
Robert Lye7eeb402014-06-03 19:35:24 -07003582 $el = $('<a>')
3583 .addClass(cls)
3584 .attr('href', cleanUrl(resource.url))
3585 .decorateResourceCard(resource, plusone);
3586 }
smain@google.com95948b82014-06-16 19:24:25 -07003587
Robert Lye7eeb402014-06-03 19:35:24 -07003588 return $el;
3589 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003590
3591 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3592 function drawResourcesFlowWidget($widget, opts, resources) {
3593 $widget.empty();
3594 var cardSizes = opts.cardSizes || ['6x6'];
3595 var i = 0, j = 0;
3596 var plusone = true; // by default show plusone on resource cards
3597
3598 while (i < resources.length) {
3599 var cardSize = cardSizes[j++ % cardSizes.length];
3600 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003601 // Some card sizes do not get a plusone button, such as where space is constrained
3602 // or for cards commonly embedded in docs (to improve overall page speed).
3603 plusone = !((cardSize == "6x2") || (cardSize == "6x3") ||
3604 (cardSize == "9x2") || (cardSize == "9x3") ||
3605 (cardSize == "12x2") || (cardSize == "12x3"));
3606
3607 // A stack has a third dimension which is the number of stacked items
3608 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3609 var stackCount = 0;
3610 var $stackDiv = null;
3611
3612 if (isStack) {
3613 // Create a stack container which should have the dimensions defined
3614 // by the product of the items inside.
3615 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
3616 + 'x' + isStack[2] * isStack[3]) .appendTo($widget);
3617 }
3618
3619 // Build each stack item or just a single item
3620 do {
3621 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003622
Robert Lye7eeb402014-06-03 19:35:24 -07003623 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003624
3625 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003626 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003627
Dirk Doughertyc3921652014-05-13 16:55:26 -07003628 if (isStack) {
3629 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3630 if (++stackCount == parseInt(isStack[3])) {
3631 $card.addClass('resource-card-row-stack-last');
3632 stackCount = 0;
3633 }
3634 } else {
3635 stackCount = 0;
3636 }
3637
Robert Lye7eeb402014-06-03 19:35:24 -07003638 $card.appendTo($stackDiv || $widget);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003639
3640 } while (++i < resources.length && stackCount > 0);
3641 }
3642 }
3643
3644 /* Build a site map of resources using a section as a root. */
3645 function buildSectionList(opts) {
3646 if (opts.section && SECTION_BY_ID[opts.section]) {
3647 return SECTION_BY_ID[opts.section].sections || [];
3648 }
3649 return [];
3650 }
3651
3652 function buildResourceList(opts) {
3653 var maxResults = opts.maxResults || 100;
3654
3655 var query = opts.query || '';
3656 var expressions = parseResourceQuery(query);
3657 var addedResourceIndices = {};
3658 var results = [];
3659
3660 for (var i = 0; i < expressions.length; i++) {
3661 var clauses = expressions[i];
3662
3663 // build initial set of resources from first clause
3664 var firstClause = clauses[0];
3665 var resources = [];
3666 switch (firstClause.attr) {
3667 case 'type':
3668 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3669 break;
3670 case 'lang':
3671 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3672 break;
3673 case 'tag':
3674 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3675 break;
3676 case 'collection':
3677 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3678 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3679 break;
3680 case 'section':
3681 var urls = SITE_MAP[firstClause.value].sections || [];
3682 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3683 break;
3684 }
3685 // console.log(firstClause.attr + ':' + firstClause.value);
3686 resources = resources || [];
3687
3688 // use additional clauses to filter corpus
3689 if (clauses.length > 1) {
3690 var otherClauses = clauses.slice(1);
3691 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3692 }
3693
3694 // filter out resources already added
3695 if (i > 1) {
3696 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3697 }
3698
3699 // add to list of already added indices
3700 for (var j = 0; j < resources.length; j++) {
3701 // console.log(resources[j].title);
3702 addedResourceIndices[resources[j].index] = 1;
3703 }
3704
3705 // concat to final results list
3706 results = results.concat(resources);
3707 }
3708
3709 if (opts.sortOrder && results.length) {
3710 var attr = opts.sortOrder;
3711
3712 if (opts.sortOrder == 'random') {
3713 var i = results.length, j, temp;
3714 while (--i) {
3715 j = Math.floor(Math.random() * (i + 1));
3716 temp = results[i];
3717 results[i] = results[j];
3718 results[j] = temp;
3719 }
3720 } else {
3721 var desc = attr.charAt(0) == '-';
3722 if (desc) {
3723 attr = attr.substring(1);
3724 }
3725 results = results.sort(function(x,y) {
3726 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3727 });
3728 }
3729 }
3730
3731 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3732 results = results.slice(0, maxResults);
3733
3734 for (var j = 0; j < results.length; ++j) {
3735 addedPageResources[results[j].index] = 1;
3736 }
3737
3738 return results;
3739 }
3740
3741
3742 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3743 return function(resource) {
3744 return !addedResourceIndices[resource.index];
3745 };
3746 }
3747
3748
3749 function getResourceMatchesClausesFilter(clauses) {
3750 return function(resource) {
3751 return doesResourceMatchClauses(resource, clauses);
3752 };
3753 }
3754
3755
3756 function doesResourceMatchClauses(resource, clauses) {
3757 for (var i = 0; i < clauses.length; i++) {
3758 var map;
3759 switch (clauses[i].attr) {
3760 case 'type':
3761 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3762 break;
3763 case 'lang':
3764 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3765 break;
3766 case 'tag':
3767 map = IS_RESOURCE_TAGGED[clauses[i].value];
3768 break;
3769 }
3770
3771 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3772 return clauses[i].negative;
3773 }
3774 }
3775 return true;
3776 }
smain@google.com95948b82014-06-16 19:24:25 -07003777
Robert Lye7eeb402014-06-03 19:35:24 -07003778 function cleanUrl(url)
3779 {
3780 if (url && url.indexOf('//') === -1) {
3781 url = toRoot + url;
3782 }
smain@google.com95948b82014-06-16 19:24:25 -07003783
Robert Lye7eeb402014-06-03 19:35:24 -07003784 return url;
3785 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003786
3787
3788 function parseResourceQuery(query) {
3789 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3790 var expressions = [];
3791 var expressionStrs = query.split(',') || [];
3792 for (var i = 0; i < expressionStrs.length; i++) {
3793 var expr = expressionStrs[i] || '';
3794
3795 // Break expression into clauses (clause e.g. 'tag:foo')
3796 var clauses = [];
3797 var clauseStrs = expr.split(/(?=[\+\-])/);
3798 for (var j = 0; j < clauseStrs.length; j++) {
3799 var clauseStr = clauseStrs[j] || '';
3800
3801 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3802 var parts = clauseStr.split(':');
3803 var clause = {};
3804
3805 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3806 if (clause.attr) {
3807 if (clause.attr.charAt(0) == '+') {
3808 clause.attr = clause.attr.substring(1);
3809 } else if (clause.attr.charAt(0) == '-') {
3810 clause.negative = true;
3811 clause.attr = clause.attr.substring(1);
3812 }
3813 }
3814
3815 if (parts.length > 1) {
3816 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3817 }
3818
3819 clauses.push(clause);
3820 }
3821
3822 if (!clauses.length) {
3823 continue;
3824 }
3825
3826 expressions.push(clauses);
3827 }
3828
3829 return expressions;
3830 }
3831})();
3832
3833(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07003834
smain@google.com95948b82014-06-16 19:24:25 -07003835 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003836 Utility method for creating dom for the description area of a card.
3837 Used in decorateResourceCard and decorateResource.
3838 */
3839 function buildResourceCardDescription(resource, plusone) {
3840 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07003841
Robert Lye7eeb402014-06-03 19:35:24 -07003842 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07003843
Robert Lye7eeb402014-06-03 19:35:24 -07003844 if (resource.cta) {
3845 $description.append($('<a>').addClass('cta').html(resource.cta));
3846 }
smain@google.com95948b82014-06-16 19:24:25 -07003847
Robert Lye7eeb402014-06-03 19:35:24 -07003848 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07003849 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07003850 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07003851
Robert Lye7eeb402014-06-03 19:35:24 -07003852 $description.append($('<div>').addClass('util')
3853 .append($('<div>').addClass('g-plusone')
3854 .attr('data-size', 'small')
3855 .attr('data-align', 'right')
3856 .attr('data-href', plusurl)));
3857 }
smain@google.com95948b82014-06-16 19:24:25 -07003858
Robert Lye7eeb402014-06-03 19:35:24 -07003859 return $description;
3860 }
smain@google.com95948b82014-06-16 19:24:25 -07003861
3862
Dirk Doughertyc3921652014-05-13 16:55:26 -07003863 /* Simple jquery function to create dom for a standard resource card */
3864 $.fn.decorateResourceCard = function(resource,plusone) {
3865 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07003866 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07003867 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07003868
Robert Lye7eeb402014-06-03 19:35:24 -07003869 if (imgUrl.indexOf('//') === -1) {
3870 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003871 }
Robert Lye7eeb402014-06-03 19:35:24 -07003872
3873 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07003874 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07003875 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07003876 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07003877
Robert Lye7eeb402014-06-03 19:35:24 -07003878 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
3879 .append($('<div>').addClass('section').text(section))
3880 .append($('<div>').addClass('title').html(resource.title))
3881 .append(buildResourceCardDescription(resource, plusone))
3882 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003883
3884 return this;
3885 };
3886
3887 /* Simple jquery function to create dom for a resource section card (menu) */
3888 $.fn.decorateResourceSection = function(section,plusone) {
3889 var resource = section.resource;
3890 //keep url clean for matching and offline mode handling
3891 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
3892 var $base = $('<a>')
3893 .addClass('card-bg')
3894 .attr('href', resource.url)
3895 .append($('<div>').addClass('card-section-icon')
3896 .append($('<div>').addClass('icon'))
3897 .append($('<div>').addClass('section').html(resource.title)))
3898 .appendTo(this);
3899
3900 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
3901
3902 if (section.sections && section.sections.length) {
3903 // Recurse the section sub-tree to find a resource image.
3904 var stack = [section];
3905
3906 while (stack.length) {
3907 if (stack[0].resource.image) {
3908 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
3909 break;
3910 }
3911
3912 if (stack[0].sections) {
3913 stack = stack.concat(stack[0].sections);
3914 }
3915
3916 stack.shift();
3917 }
3918
3919 var $ul = $('<ul>')
3920 .appendTo($cardInfo);
3921
3922 var max = section.sections.length > 3 ? 3 : section.sections.length;
3923
3924 for (var i = 0; i < max; ++i) {
3925
3926 var subResource = section.sections[i];
3927 if (!plusone) {
3928 $('<li>')
3929 .append($('<a>').attr('href', subResource.url)
3930 .append($('<div>').addClass('title').html(subResource.title))
3931 .append($('<div>').addClass('description ellipsis')
3932 .append($('<div>').addClass('text').html(subResource.summary))
3933 .append($('<div>').addClass('util'))))
3934 .appendTo($ul);
3935 } else {
3936 $('<li>')
3937 .append($('<a>').attr('href', subResource.url)
3938 .append($('<div>').addClass('title').html(subResource.title))
3939 .append($('<div>').addClass('description ellipsis')
3940 .append($('<div>').addClass('text').html(subResource.summary))
3941 .append($('<div>').addClass('util')
3942 .append($('<div>').addClass('g-plusone')
3943 .attr('data-size', 'small')
3944 .attr('data-align', 'right')
3945 .attr('data-href', resource.url)))))
3946 .appendTo($ul);
3947 }
3948 }
3949
3950 // Add a more row
3951 if (max < section.sections.length) {
3952 $('<li>')
3953 .append($('<a>').attr('href', resource.url)
3954 .append($('<div>')
3955 .addClass('title')
3956 .text('More')))
3957 .appendTo($ul);
3958 }
3959 } else {
3960 // No sub-resources, just render description?
3961 }
3962
3963 return this;
3964 };
smain@google.com95948b82014-06-16 19:24:25 -07003965
3966
3967
3968
Robert Lye7eeb402014-06-03 19:35:24 -07003969 /* Render other types of resource styles that are not cards. */
3970 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07003971 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07003972 'assets/images/resource-card-default-android.jpg';
3973 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07003974
Robert Lye7eeb402014-06-03 19:35:24 -07003975 if (imgUrl.indexOf('//') === -1) {
3976 imgUrl = toRoot + imgUrl;
3977 }
smain@google.com95948b82014-06-16 19:24:25 -07003978
Robert Lye7eeb402014-06-03 19:35:24 -07003979 if (linkUrl && linkUrl.indexOf('//') === -1) {
3980 linkUrl = toRoot + linkUrl;
3981 }
3982
3983 $(this).append(
3984 $('<div>').addClass('image')
3985 .css('background-image', 'url(' + imgUrl + ')'),
3986 $('<div>').addClass('info').append(
3987 $('<h4>').addClass('title').html(resource.title),
3988 $('<p>').addClass('summary').html(resource.summary),
3989 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
3990 )
3991 );
3992
3993 return this;
3994 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07003995})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07003996
3997
Dirk Doughertyc3921652014-05-13 16:55:26 -07003998/* Calculate the vertical area remaining */
3999(function($) {
4000 $.fn.ellipsisfade= function(lineHeight) {
4001 this.each(function() {
4002 // get element text
4003 var $this = $(this);
4004 var remainingHeight = $this.parent().parent().height();
4005 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004006 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004007 if ($(this).is(":visible")) {
4008 var h = $(this).height();
4009 remainingHeight = remainingHeight - h;
4010 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004011 });
4012
4013 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4014 $this.parent().css({'height': adjustedRemainingHeight});
4015 $this.css({'height': "auto"});
4016 });
4017
4018 return this;
4019 };
4020}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004021
4022/*
4023 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004024
Robert Lye7eeb402014-06-03 19:35:24 -07004025 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004026 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004027 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004028
Robert Lye7eeb402014-06-03 19:35:24 -07004029 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004030
Robert Lye7eeb402014-06-03 19:35:24 -07004031 <div class="fullscreen-carousel">
4032 <div class="fullscreen-carousel-content">
4033 <!-- content here -->
4034 </div>
4035 <div class="fullscreen-carousel-content">
4036 <!-- content here -->
4037 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004038
Robert Lye7eeb402014-06-03 19:35:24 -07004039 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004040
Robert Lye7eeb402014-06-03 19:35:24 -07004041 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004042
Robert Lye7eeb402014-06-03 19:35:24 -07004043 Control over how the carousel takes over the screen can mostly be defined in
4044 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004045 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004046 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004047 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004048 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004049
Robert Lye7eeb402014-06-03 19:35:24 -07004050 There is limited functionality for having multiple sections since that request
4051 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4052 scroll between multiple content areas.
4053*/
4054
4055(function() {
4056 $(document).ready(function() {
4057 $('.fullscreen-carousel').each(function() {
4058 initWidget(this);
4059 });
4060 });
4061
4062 function initWidget(widget) {
4063 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004064
Robert Lye7eeb402014-06-03 19:35:24 -07004065 var topOffset = $widget.offset().top;
4066 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4067 var maxHeight = 0;
4068 var minHeight = 0;
4069 var $content = $widget.find('.fullscreen-carousel-content');
4070 var $nextArrow = $widget.find('.next-arrow');
4071 var $prevArrow = $widget.find('.prev-arrow');
4072 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004073
Robert Lye7eeb402014-06-03 19:35:24 -07004074 if ($content.length <= 1) {
4075 $nextArrow.hide();
4076 $prevArrow.hide();
4077 } else {
4078 $nextArrow.click(function() {
4079 var index = ($content.index($curSection) + 1);
4080 $curSection.hide();
4081 $curSection = $($content[index >= $content.length ? 0 : index]);
4082 $curSection.show();
4083 });
smain@google.com95948b82014-06-16 19:24:25 -07004084
Robert Lye7eeb402014-06-03 19:35:24 -07004085 $prevArrow.click(function() {
4086 var index = ($content.index($curSection) - 1);
4087 $curSection.hide();
4088 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4089 $curSection.show();
4090 });
4091 }
4092
4093 // Just hide all content sections except first.
4094 $content.each(function(index) {
4095 if ($(this).height() > minHeight) minHeight = $(this).height();
4096 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4097 });
4098
4099 // Register for changes to window size, and trigger.
4100 $(window).resize(resizeWidget);
4101 resizeWidget();
4102
4103 function resizeWidget() {
4104 var height = $(window).height() - topOffset - padBottom;
4105 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004106 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004107 (maxHeight && height > maxHeight ? maxHeight : height));
4108 }
smain@google.com95948b82014-06-16 19:24:25 -07004109 }
Robert Lye7eeb402014-06-03 19:35:24 -07004110})();
4111
4112
4113
4114
4115
4116/*
4117 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004118
Robert Lye7eeb402014-06-03 19:35:24 -07004119 The following allows tab widgets to be installed via the html below. Each
4120 tab content section should have a data-tab attribute matching one of the
4121 nav items'. Also each tab content section should have a width matching the
4122 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004123
Robert Lye7eeb402014-06-03 19:35:24 -07004124 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004125
Robert Lye7eeb402014-06-03 19:35:24 -07004126 <div class="tab-carousel">
4127 <ul class="tab-nav">
4128 <li><a href="#" data-tab="handsets">Handsets</a>
4129 <li><a href="#" data-tab="wearable">Wearable</a>
4130 <li><a href="#" data-tab="tv">TV</a>
4131 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004132
Robert Lye7eeb402014-06-03 19:35:24 -07004133 <div class="tab-carousel-content">
4134 <div data-tab="handsets">
4135 <!--Full width content here-->
4136 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004137
Robert Lye7eeb402014-06-03 19:35:24 -07004138 <div data-tab="wearable">
4139 <!--Full width content here-->
4140 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004141
Robert Lye7eeb402014-06-03 19:35:24 -07004142 <div data-tab="tv">
4143 <!--Full width content here-->
4144 </div>
4145 </div>
4146 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004147
Robert Lye7eeb402014-06-03 19:35:24 -07004148*/
4149(function() {
4150 $(document).ready(function() {
4151 $('.tab-carousel').each(function() {
4152 initWidget(this);
4153 });
4154 });
4155
4156 function initWidget(widget) {
4157 var $widget = $(widget);
4158 var $nav = $widget.find('.tab-nav');
4159 var $anchors = $nav.find('[data-tab]');
4160 var $li = $nav.find('li');
4161 var $contentContainer = $widget.find('.tab-carousel-content');
4162 var $tabs = $contentContainer.find('[data-tab]');
4163 var $curTab = $($tabs[0]); // Current tab is first tab.
4164 var width = $widget.width();
4165
4166 // Setup nav interactivity.
4167 $anchors.click(function(evt) {
4168 evt.preventDefault();
4169 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004170 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004171 });
smain@google.com95948b82014-06-16 19:24:25 -07004172
Robert Lye7eeb402014-06-03 19:35:24 -07004173 // Add highlight for navigation on first item.
4174 var $highlight = $('<div>').addClass('highlight')
4175 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4176 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004177
Robert Lye7eeb402014-06-03 19:35:24 -07004178 // Store height since we will change contents to absolute.
4179 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004180
Robert Lye7eeb402014-06-03 19:35:24 -07004181 // Absolutely position tabs so they're ready for transition.
4182 $tabs.each(function(index) {
4183 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4184 });
smain@google.com95948b82014-06-16 19:24:25 -07004185
Robert Lye7eeb402014-06-03 19:35:24 -07004186 function transitionWidget($toTab) {
4187 if (!$curTab.is($toTab)) {
4188 var curIndex = $tabs.index($curTab[0]);
4189 var toIndex = $tabs.index($toTab[0]);
4190 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004191
Robert Lye7eeb402014-06-03 19:35:24 -07004192 // Animate content sections.
4193 $toTab.css({left:(width * dir) + 'px'});
4194 $curTab.animate({left:(width * -dir) + 'px'});
4195 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004196
Robert Lye7eeb402014-06-03 19:35:24 -07004197 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004198 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004199 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004200
Robert Lye7eeb402014-06-03 19:35:24 -07004201 // Store new current section.
4202 $curTab = $toTab;
4203 }
4204 }
smain@google.com95948b82014-06-16 19:24:25 -07004205 }
Robert Lye7eeb402014-06-03 19:35:24 -07004206})();