blob: a556f4f6cb3998bef87f1481ce44da2ffc0c41d3 [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 }
661 writeCookie("fullscreen", enabled, null, null);
662 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));
822 writeCookie("height", resizePackagesNav.css("height"), section, null);
823}
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
903function writeCookie(cookie, val, section, expiration) {
904 if (val==undefined) return;
905 section = section == null ? "_" : "_"+section+"_";
906 if (expiration == null) {
907 var date = new Date();
908 date.setTime(date.getTime()+(10*365*24*60*60*1000)); // default expiration is one week
909 expiration = date.toGMTString();
910 }
Scott Main3b90aff2013-08-01 18:09:35 -0700911 var cookieValue = cookie_namespace + section + cookie + "=" + val
Scott Maine4d8f1b2012-06-21 18:03:05 -0700912 + "; expires=" + expiration+"; path=/";
913 document.cookie = cookieValue;
914}
915
916/* ######### END COOKIES! ########## */
917
918
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700919var sticky = false;
Dirk Doughertyc3921652014-05-13 16:55:26 -0700920var stickyTop;
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700921var prevScrollLeft = 0; // used to compare current position to previous position of horiz scroll
Dirk Doughertyc3921652014-05-13 16:55:26 -0700922/* Sets the vertical scoll position at which the sticky bar should appear.
923 This method is called to reset the position when search results appear or hide */
924function setStickyTop() {
925 stickyTop = $('#header-wrapper').outerHeight() - $('#sticky-header').outerHeight();
926}
Scott Maine4d8f1b2012-06-21 18:03:05 -0700927
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700928/*
Scott Mainb16376f2014-05-21 20:35:47 -0700929 * Displays sticky nav bar on pages when dac header scrolls out of view
Dirk Doughertyc3921652014-05-13 16:55:26 -0700930 */
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700931$(window).scroll(function(event) {
932
933 setStickyTop();
934 var hiding = false;
935 var $stickyEl = $('#sticky-header');
936 var $menuEl = $('.menu-container');
937 // Exit if there's no sidenav
938 if ($('#side-nav').length == 0) return;
939 // Exit if the mouse target is a DIV, because that means the event is coming
940 // from a scrollable div and so there's no need to make adjustments to our layout
941 if ($(event.target).nodeName == "DIV") {
942 return;
943 }
944
945 var top = $(window).scrollTop();
946 // we set the navbar fixed when the scroll position is beyond the height of the site header...
947 var shouldBeSticky = top >= stickyTop;
948 // ... except if the document content is shorter than the sidenav height.
949 // (this is necessary to avoid crazy behavior on OSX Lion due to overscroll bouncing)
950 if ($("#doc-col").height() < $("#side-nav").height()) {
951 shouldBeSticky = false;
952 }
Scott Mainf5257812014-05-22 17:26:38 -0700953 // Account for horizontal scroll
954 var scrollLeft = $(window).scrollLeft();
955 // When the sidenav is fixed and user scrolls horizontally, reposition the sidenav to match
956 if (sticky && (scrollLeft != prevScrollLeft)) {
957 updateSideNavPosition();
958 prevScrollLeft = scrollLeft;
959 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700960
961 // Don't continue if the header is sufficently far away
962 // (to avoid intensive resizing that slows scrolling)
963 if (sticky == shouldBeSticky) {
964 return;
965 }
Dirk Doughertyca1230c2014-05-14 20:00:03 -0700966
967 // If sticky header visible and position is now near top, hide sticky
968 if (sticky && !shouldBeSticky) {
969 sticky = false;
970 hiding = true;
971 // make the sidenav static again
972 $('#devdoc-nav')
973 .removeClass('fixed')
974 .css({'width':'auto','margin':''})
975 .prependTo('#side-nav');
976 // delay hide the sticky
977 $menuEl.removeClass('sticky-menu');
978 $stickyEl.fadeOut(250);
979 hiding = false;
980
981 // update the sidenaav position for side scrolling
982 updateSideNavPosition();
983 } else if (!sticky && shouldBeSticky) {
984 sticky = true;
985 $stickyEl.fadeIn(10);
986 $menuEl.addClass('sticky-menu');
987
988 // make the sidenav fixed
989 var width = $('#devdoc-nav').width();
990 $('#devdoc-nav')
991 .addClass('fixed')
992 .css({'width':width+'px'})
993 .prependTo('#body-content');
994
995 // update the sidenaav position for side scrolling
996 updateSideNavPosition();
997
998 } else if (hiding && top < 15) {
999 $menuEl.removeClass('sticky-menu');
1000 $stickyEl.hide();
1001 hiding = false;
1002 }
1003 resizeNav(250); // pass true in order to delay the scrollbar re-initialization for performance
1004});
1005
1006/*
1007 * Manages secion card states and nav resize to conclude loading
1008 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07001009(function() {
1010 $(document).ready(function() {
1011
Dirk Doughertyc3921652014-05-13 16:55:26 -07001012 // Stack hover states
1013 $('.section-card-menu').each(function(index, el) {
1014 var height = $(el).height();
1015 $(el).css({height:height+'px', position:'relative'});
1016 var $cardInfo = $(el).find('.card-info');
1017
1018 $cardInfo.css({position: 'absolute', bottom:'0px', left:'0px', right:'0px', overflow:'visible'});
1019 });
1020
Dirk Doughertyc3921652014-05-13 16:55:26 -07001021 });
1022
1023})();
1024
Scott Maine4d8f1b2012-06-21 18:03:05 -07001025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
Scott Maind7026f72013-06-17 15:08:49 -07001038/* MISC LIBRARY FUNCTIONS */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001039
1040
1041
1042
1043
1044function toggle(obj, slide) {
1045 var ul = $("ul:first", obj);
1046 var li = ul.parent();
1047 if (li.hasClass("closed")) {
1048 if (slide) {
1049 ul.slideDown("fast");
1050 } else {
1051 ul.show();
1052 }
1053 li.removeClass("closed");
1054 li.addClass("open");
1055 $(".toggle-img", li).attr("title", "hide pages");
1056 } else {
1057 ul.slideUp("fast");
1058 li.removeClass("open");
1059 li.addClass("closed");
1060 $(".toggle-img", li).attr("title", "show pages");
1061 }
1062}
1063
1064
Scott Maine4d8f1b2012-06-21 18:03:05 -07001065function buildToggleLists() {
1066 $(".toggle-list").each(
1067 function(i) {
1068 $("div:first", this).append("<a class='toggle-img' href='#' title='show pages' onClick='toggle(this.parentNode.parentNode, true); return false;'></a>");
1069 $(this).addClass("closed");
1070 });
1071}
1072
1073
1074
Scott Maind7026f72013-06-17 15:08:49 -07001075function hideNestedItems(list, toggle) {
1076 $list = $(list);
1077 // hide nested lists
1078 if($list.hasClass('showing')) {
1079 $("li ol", $list).hide('fast');
1080 $list.removeClass('showing');
1081 // show nested lists
1082 } else {
1083 $("li ol", $list).show('fast');
1084 $list.addClass('showing');
1085 }
1086 $(".more,.less",$(toggle)).toggle();
1087}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001088
1089
smain@google.com95948b82014-06-16 19:24:25 -07001090/* Call this to add listeners to a <select> element for Studio/Eclipse/Other docs */
1091function setupIdeDocToggle() {
1092 $( "select.ide" ).change(function() {
1093 var selected = $(this).find("option:selected").attr("value");
1094 $(".select-ide").hide();
1095 $(".select-ide."+selected).show();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001096
smain@google.com95948b82014-06-16 19:24:25 -07001097 $("select.ide").val(selected);
1098 });
1099}
Scott Maine4d8f1b2012-06-21 18:03:05 -07001100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124/* REFERENCE NAV SWAP */
1125
1126
1127function getNavPref() {
1128 var v = readCookie('reference_nav');
1129 if (v != NAV_PREF_TREE) {
1130 v = NAV_PREF_PANELS;
1131 }
1132 return v;
1133}
1134
1135function chooseDefaultNav() {
1136 nav_pref = getNavPref();
1137 if (nav_pref == NAV_PREF_TREE) {
1138 $("#nav-panels").toggle();
1139 $("#panel-link").toggle();
1140 $("#nav-tree").toggle();
1141 $("#tree-link").toggle();
1142 }
1143}
1144
1145function swapNav() {
1146 if (nav_pref == NAV_PREF_TREE) {
1147 nav_pref = NAV_PREF_PANELS;
1148 } else {
1149 nav_pref = NAV_PREF_TREE;
1150 init_default_navtree(toRoot);
1151 }
1152 var date = new Date();
1153 date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
1154 writeCookie("nav", nav_pref, "reference", date.toGMTString());
1155
1156 $("#nav-panels").toggle();
1157 $("#panel-link").toggle();
1158 $("#nav-tree").toggle();
1159 $("#tree-link").toggle();
Scott Main3b90aff2013-08-01 18:09:35 -07001160
Scott Maine4d8f1b2012-06-21 18:03:05 -07001161 resizeNav();
1162
1163 // Gross nasty hack to make tree view show up upon first swap by setting height manually
1164 $("#nav-tree .jspContainer:visible")
1165 .css({'height':$("#nav-tree .jspContainer .jspPane").height() +'px'});
1166 // Another nasty hack to make the scrollbar appear now that we have height
1167 resizeNav();
Scott Main3b90aff2013-08-01 18:09:35 -07001168
Scott Maine4d8f1b2012-06-21 18:03:05 -07001169 if ($("#nav-tree").is(':visible')) {
1170 scrollIntoView("nav-tree");
1171 } else {
1172 scrollIntoView("packages-nav");
1173 scrollIntoView("classes-nav");
1174 }
1175}
1176
1177
1178
Scott Mainf5089842012-08-14 16:31:07 -07001179/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001180/* ########## LOCALIZATION ############ */
Scott Mainf5089842012-08-14 16:31:07 -07001181/* ############################################ */
Scott Maine4d8f1b2012-06-21 18:03:05 -07001182
1183function getBaseUri(uri) {
1184 var intlUrl = (uri.substring(0,6) == "/intl/");
1185 if (intlUrl) {
1186 base = uri.substring(uri.indexOf('intl/')+5,uri.length);
1187 base = base.substring(base.indexOf('/')+1, base.length);
1188 //alert("intl, returning base url: /" + base);
1189 return ("/" + base);
1190 } else {
1191 //alert("not intl, returning uri as found.");
1192 return uri;
1193 }
1194}
1195
1196function requestAppendHL(uri) {
1197//append "?hl=<lang> to an outgoing request (such as to blog)
1198 var lang = getLangPref();
1199 if (lang) {
1200 var q = 'hl=' + lang;
1201 uri += '?' + q;
1202 window.location = uri;
1203 return false;
1204 } else {
1205 return true;
1206 }
1207}
1208
1209
Scott Maine4d8f1b2012-06-21 18:03:05 -07001210function changeNavLang(lang) {
Scott Main6eb95f12012-10-02 17:12:23 -07001211 var $links = $("#devdoc-nav,#header,#nav-x,.training-nav-top,.content-footer").find("a["+lang+"-lang]");
1212 $links.each(function(i){ // for each link with a translation
1213 var $link = $(this);
1214 if (lang != "en") { // No need to worry about English, because a language change invokes new request
1215 // put the desired language from the attribute as the text
1216 $link.text($link.attr(lang+"-lang"))
Scott Maine4d8f1b2012-06-21 18:03:05 -07001217 }
Scott Main6eb95f12012-10-02 17:12:23 -07001218 });
Scott Maine4d8f1b2012-06-21 18:03:05 -07001219}
1220
Scott Main015d6162013-01-29 09:01:52 -08001221function changeLangPref(lang, submit) {
Scott Maine4d8f1b2012-06-21 18:03:05 -07001222 var date = new Date();
Scott Main3b90aff2013-08-01 18:09:35 -07001223 expires = date.toGMTString(date.setTime(date.getTime()+(10*365*24*60*60*1000)));
Scott Maine4d8f1b2012-06-21 18:03:05 -07001224 // keep this for 50 years
1225 //alert("expires: " + expires)
1226 writeCookie("pref_lang", lang, null, expires);
Scott Main015d6162013-01-29 09:01:52 -08001227
1228 // ####### TODO: Remove this condition once we're stable on devsite #######
1229 // This condition is only needed if we still need to support legacy GAE server
1230 if (devsite) {
1231 // Switch language when on Devsite server
1232 if (submit) {
1233 $("#setlang").submit();
1234 }
1235 } else {
1236 // Switch language when on legacy GAE server
Scott Main015d6162013-01-29 09:01:52 -08001237 if (submit) {
1238 window.location = getBaseUri(location.pathname);
1239 }
Scott Maine4d8f1b2012-06-21 18:03:05 -07001240 }
1241}
1242
1243function loadLangPref() {
1244 var lang = readCookie("pref_lang");
1245 if (lang != 0) {
1246 $("#language").find("option[value='"+lang+"']").attr("selected",true);
1247 }
1248}
1249
1250function getLangPref() {
1251 var lang = $("#language").find(":selected").attr("value");
1252 if (!lang) {
1253 lang = readCookie("pref_lang");
1254 }
1255 return (lang != 0) ? lang : 'en';
1256}
1257
1258/* ########## END LOCALIZATION ############ */
1259
1260
1261
1262
1263
1264
1265/* Used to hide and reveal supplemental content, such as long code samples.
1266 See the companion CSS in android-developer-docs.css */
1267function toggleContent(obj) {
Scott Maindc63dda2013-08-22 16:03:21 -07001268 var div = $(obj).closest(".toggle-content");
1269 var toggleMe = $(".toggle-content-toggleme:eq(0)",div);
Scott Maine4d8f1b2012-06-21 18:03:05 -07001270 if (div.hasClass("closed")) { // if it's closed, open it
1271 toggleMe.slideDown();
Scott Maindc63dda2013-08-22 16:03:21 -07001272 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001273 div.removeClass("closed").addClass("open");
Scott Maindc63dda2013-08-22 16:03:21 -07001274 $(".toggle-content-img:eq(0)", div).attr("title", "hide").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001275 + "assets/images/triangle-opened.png");
1276 } else { // if it's open, close it
1277 toggleMe.slideUp('fast', function() { // Wait until the animation is done before closing arrow
Scott Maindc63dda2013-08-22 16:03:21 -07001278 $(".toggle-content-text:eq(0)", obj).toggle();
Scott Maine4d8f1b2012-06-21 18:03:05 -07001279 div.removeClass("open").addClass("closed");
Scott Maindc63dda2013-08-22 16:03:21 -07001280 div.find(".toggle-content").removeClass("open").addClass("closed")
1281 .find(".toggle-content-toggleme").hide();
Scott Main3b90aff2013-08-01 18:09:35 -07001282 $(".toggle-content-img", div).attr("title", "show").attr("src", toRoot
Scott Maine4d8f1b2012-06-21 18:03:05 -07001283 + "assets/images/triangle-closed.png");
1284 });
1285 }
1286 return false;
1287}
Scott Mainf5089842012-08-14 16:31:07 -07001288
1289
Scott Maindb3678b2012-10-23 14:13:41 -07001290/* New version of expandable content */
1291function toggleExpandable(link,id) {
1292 if($(id).is(':visible')) {
1293 $(id).slideUp();
1294 $(link).removeClass('expanded');
1295 } else {
1296 $(id).slideDown();
1297 $(link).addClass('expanded');
1298 }
1299}
1300
1301function hideExpandable(ids) {
1302 $(ids).slideUp();
Scott Main55d99832012-11-12 23:03:59 -08001303 $(ids).prev('h4').find('a.expandable').removeClass('expanded');
Scott Maindb3678b2012-10-23 14:13:41 -07001304}
1305
Scott Mainf5089842012-08-14 16:31:07 -07001306
1307
1308
1309
Scott Main3b90aff2013-08-01 18:09:35 -07001310/*
Scott Mainf5089842012-08-14 16:31:07 -07001311 * Slideshow 1.0
1312 * Used on /index.html and /develop/index.html for carousel
1313 *
1314 * Sample usage:
1315 * HTML -
1316 * <div class="slideshow-container">
1317 * <a href="" class="slideshow-prev">Prev</a>
1318 * <a href="" class="slideshow-next">Next</a>
1319 * <ul>
1320 * <li class="item"><img src="images/marquee1.jpg"></li>
1321 * <li class="item"><img src="images/marquee2.jpg"></li>
1322 * <li class="item"><img src="images/marquee3.jpg"></li>
1323 * <li class="item"><img src="images/marquee4.jpg"></li>
1324 * </ul>
1325 * </div>
1326 *
1327 * <script type="text/javascript">
1328 * $('.slideshow-container').dacSlideshow({
1329 * auto: true,
1330 * btnPrev: '.slideshow-prev',
1331 * btnNext: '.slideshow-next'
1332 * });
1333 * </script>
1334 *
1335 * Options:
1336 * btnPrev: optional identifier for previous button
1337 * btnNext: optional identifier for next button
Scott Maineb410352013-01-14 19:03:40 -08001338 * btnPause: optional identifier for pause button
Scott Mainf5089842012-08-14 16:31:07 -07001339 * auto: whether or not to auto-proceed
1340 * speed: animation speed
1341 * autoTime: time between auto-rotation
1342 * easing: easing function for transition
1343 * start: item to select by default
1344 * scroll: direction to scroll in
1345 * pagination: whether or not to include dotted pagination
1346 *
1347 */
1348
1349 (function($) {
1350 $.fn.dacSlideshow = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001351
Scott Mainf5089842012-08-14 16:31:07 -07001352 //Options - see above
1353 o = $.extend({
1354 btnPrev: null,
1355 btnNext: null,
Scott Maineb410352013-01-14 19:03:40 -08001356 btnPause: null,
Scott Mainf5089842012-08-14 16:31:07 -07001357 auto: true,
1358 speed: 500,
1359 autoTime: 12000,
1360 easing: null,
1361 start: 0,
1362 scroll: 1,
1363 pagination: true
1364
1365 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001366
1367 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001368 return this.each(function() {
1369
1370 var running = false;
1371 var animCss = o.vertical ? "top" : "left";
1372 var sizeCss = o.vertical ? "height" : "width";
1373 var div = $(this);
1374 var ul = $("ul", div);
1375 var tLi = $("li", ul);
Scott Main3b90aff2013-08-01 18:09:35 -07001376 var tl = tLi.size();
Scott Mainf5089842012-08-14 16:31:07 -07001377 var timer = null;
1378
1379 var li = $("li", ul);
1380 var itemLength = li.size();
1381 var curr = o.start;
1382
1383 li.css({float: o.vertical ? "none" : "left"});
1384 ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
1385 div.css({position: "relative", "z-index": "2", left: "0px"});
1386
1387 var liSize = o.vertical ? height(li) : width(li);
1388 var ulSize = liSize * itemLength;
1389 var divSize = liSize;
1390
1391 li.css({width: li.width(), height: li.height()});
1392 ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));
1393
1394 div.css(sizeCss, divSize+"px");
Scott Main3b90aff2013-08-01 18:09:35 -07001395
Scott Mainf5089842012-08-14 16:31:07 -07001396 //Pagination
1397 if (o.pagination) {
1398 var pagination = $("<div class='pagination'></div>");
1399 var pag_ul = $("<ul></ul>");
1400 if (tl > 1) {
1401 for (var i=0;i<tl;i++) {
1402 var li = $("<li>"+i+"</li>");
1403 pag_ul.append(li);
1404 if (i==o.start) li.addClass('active');
1405 li.click(function() {
1406 go(parseInt($(this).text()));
1407 })
1408 }
1409 pagination.append(pag_ul);
1410 div.append(pagination);
1411 }
1412 }
Scott Main3b90aff2013-08-01 18:09:35 -07001413
Scott Mainf5089842012-08-14 16:31:07 -07001414 //Previous button
1415 if(o.btnPrev)
1416 $(o.btnPrev).click(function(e) {
1417 e.preventDefault();
1418 return go(curr-o.scroll);
1419 });
1420
1421 //Next button
1422 if(o.btnNext)
1423 $(o.btnNext).click(function(e) {
1424 e.preventDefault();
1425 return go(curr+o.scroll);
1426 });
Scott Maineb410352013-01-14 19:03:40 -08001427
1428 //Pause button
1429 if(o.btnPause)
1430 $(o.btnPause).click(function(e) {
1431 e.preventDefault();
1432 if ($(this).hasClass('paused')) {
1433 startRotateTimer();
1434 } else {
1435 pauseRotateTimer();
1436 }
1437 });
Scott Main3b90aff2013-08-01 18:09:35 -07001438
Scott Mainf5089842012-08-14 16:31:07 -07001439 //Auto rotation
1440 if(o.auto) startRotateTimer();
Scott Main3b90aff2013-08-01 18:09:35 -07001441
Scott Mainf5089842012-08-14 16:31:07 -07001442 function startRotateTimer() {
1443 clearInterval(timer);
1444 timer = setInterval(function() {
1445 if (curr == tl-1) {
1446 go(0);
1447 } else {
Scott Main3b90aff2013-08-01 18:09:35 -07001448 go(curr+o.scroll);
1449 }
Scott Mainf5089842012-08-14 16:31:07 -07001450 }, o.autoTime);
Scott Maineb410352013-01-14 19:03:40 -08001451 $(o.btnPause).removeClass('paused');
1452 }
1453
1454 function pauseRotateTimer() {
1455 clearInterval(timer);
1456 $(o.btnPause).addClass('paused');
Scott Mainf5089842012-08-14 16:31:07 -07001457 }
1458
1459 //Go to an item
1460 function go(to) {
1461 if(!running) {
1462
1463 if(to<0) {
1464 to = itemLength-1;
1465 } else if (to>itemLength-1) {
1466 to = 0;
1467 }
1468 curr = to;
1469
1470 running = true;
1471
1472 ul.animate(
1473 animCss == "left" ? { left: -(curr*liSize) } : { top: -(curr*liSize) } , o.speed, o.easing,
1474 function() {
1475 running = false;
1476 }
1477 );
1478
1479 $(o.btnPrev + "," + o.btnNext).removeClass("disabled");
1480 $( (curr-o.scroll<0 && o.btnPrev)
1481 ||
1482 (curr+o.scroll > itemLength && o.btnNext)
1483 ||
1484 []
1485 ).addClass("disabled");
1486
Scott Main3b90aff2013-08-01 18:09:35 -07001487
Scott Mainf5089842012-08-14 16:31:07 -07001488 var nav_items = $('li', pagination);
1489 nav_items.removeClass('active');
1490 nav_items.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001491
Scott Mainf5089842012-08-14 16:31:07 -07001492
1493 }
1494 if(o.auto) startRotateTimer();
1495 return false;
1496 };
1497 });
1498 };
1499
1500 function css(el, prop) {
1501 return parseInt($.css(el[0], prop)) || 0;
1502 };
1503 function width(el) {
1504 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1505 };
1506 function height(el) {
1507 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1508 };
1509
1510 })(jQuery);
1511
1512
Scott Main3b90aff2013-08-01 18:09:35 -07001513/*
Scott Mainf5089842012-08-14 16:31:07 -07001514 * dacSlideshow 1.0
1515 * Used on develop/index.html for side-sliding tabs
1516 *
1517 * Sample usage:
1518 * HTML -
1519 * <div class="slideshow-container">
1520 * <a href="" class="slideshow-prev">Prev</a>
1521 * <a href="" class="slideshow-next">Next</a>
1522 * <ul>
1523 * <li class="item"><img src="images/marquee1.jpg"></li>
1524 * <li class="item"><img src="images/marquee2.jpg"></li>
1525 * <li class="item"><img src="images/marquee3.jpg"></li>
1526 * <li class="item"><img src="images/marquee4.jpg"></li>
1527 * </ul>
1528 * </div>
1529 *
1530 * <script type="text/javascript">
1531 * $('.slideshow-container').dacSlideshow({
1532 * auto: true,
1533 * btnPrev: '.slideshow-prev',
1534 * btnNext: '.slideshow-next'
1535 * });
1536 * </script>
1537 *
1538 * Options:
1539 * btnPrev: optional identifier for previous button
1540 * btnNext: optional identifier for next button
1541 * auto: whether or not to auto-proceed
1542 * speed: animation speed
1543 * autoTime: time between auto-rotation
1544 * easing: easing function for transition
1545 * start: item to select by default
1546 * scroll: direction to scroll in
1547 * pagination: whether or not to include dotted pagination
1548 *
1549 */
1550 (function($) {
1551 $.fn.dacTabbedList = function(o) {
Scott Main3b90aff2013-08-01 18:09:35 -07001552
Scott Mainf5089842012-08-14 16:31:07 -07001553 //Options - see above
1554 o = $.extend({
1555 speed : 250,
1556 easing: null,
1557 nav_id: null,
1558 frame_id: null
1559 }, o || {});
Scott Main3b90aff2013-08-01 18:09:35 -07001560
1561 //Set up a carousel for each
Scott Mainf5089842012-08-14 16:31:07 -07001562 return this.each(function() {
1563
1564 var curr = 0;
1565 var running = false;
1566 var animCss = "margin-left";
1567 var sizeCss = "width";
1568 var div = $(this);
Scott Main3b90aff2013-08-01 18:09:35 -07001569
Scott Mainf5089842012-08-14 16:31:07 -07001570 var nav = $(o.nav_id, div);
1571 var nav_li = $("li", nav);
Scott Main3b90aff2013-08-01 18:09:35 -07001572 var nav_size = nav_li.size();
Scott Mainf5089842012-08-14 16:31:07 -07001573 var frame = div.find(o.frame_id);
1574 var content_width = $(frame).find('ul').width();
1575 //Buttons
1576 $(nav_li).click(function(e) {
1577 go($(nav_li).index($(this)));
1578 })
Scott Main3b90aff2013-08-01 18:09:35 -07001579
Scott Mainf5089842012-08-14 16:31:07 -07001580 //Go to an item
1581 function go(to) {
1582 if(!running) {
1583 curr = to;
1584 running = true;
1585
1586 frame.animate({ 'margin-left' : -(curr*content_width) }, o.speed, o.easing,
1587 function() {
1588 running = false;
1589 }
1590 );
1591
Scott Main3b90aff2013-08-01 18:09:35 -07001592
Scott Mainf5089842012-08-14 16:31:07 -07001593 nav_li.removeClass('active');
1594 nav_li.eq(to).addClass('active');
Scott Main3b90aff2013-08-01 18:09:35 -07001595
Scott Mainf5089842012-08-14 16:31:07 -07001596
1597 }
1598 return false;
1599 };
1600 });
1601 };
1602
1603 function css(el, prop) {
1604 return parseInt($.css(el[0], prop)) || 0;
1605 };
1606 function width(el) {
1607 return el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
1608 };
1609 function height(el) {
1610 return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
1611 };
1612
1613 })(jQuery);
1614
1615
1616
1617
1618
1619/* ######################################################## */
1620/* ################ SEARCH SUGGESTIONS ################## */
1621/* ######################################################## */
1622
1623
Scott Main7e447ed2013-02-19 17:22:37 -08001624
Scott Main0e76e7e2013-03-12 10:24:07 -07001625var gSelectedIndex = -1; // the index position of currently highlighted suggestion
1626var gSelectedColumn = -1; // which column of suggestion lists is currently focused
1627
Scott Mainf5089842012-08-14 16:31:07 -07001628var gMatches = new Array();
1629var gLastText = "";
Scott Mainf5089842012-08-14 16:31:07 -07001630var gInitialized = false;
Scott Main7e447ed2013-02-19 17:22:37 -08001631var ROW_COUNT_FRAMEWORK = 20; // max number of results in list
1632var gListLength = 0;
1633
1634
1635var gGoogleMatches = new Array();
1636var ROW_COUNT_GOOGLE = 15; // max number of results in list
1637var gGoogleListLength = 0;
Scott Mainf5089842012-08-14 16:31:07 -07001638
Scott Main0e76e7e2013-03-12 10:24:07 -07001639var gDocsMatches = new Array();
1640var ROW_COUNT_DOCS = 100; // max number of results in list
1641var gDocsListLength = 0;
1642
Scott Mainde295272013-03-25 15:48:35 -07001643function onSuggestionClick(link) {
1644 // When user clicks a suggested document, track it
smain@google.com633f3222014-10-03 15:49:45 -07001645 ga('send', 'event', 'Suggestion Click', 'clicked: ' + $(link).text(),
1646 'from: ' + $("#search_autocomplete").val());
Scott Mainde295272013-03-25 15:48:35 -07001647}
1648
Scott Mainf5089842012-08-14 16:31:07 -07001649function set_item_selected($li, selected)
1650{
1651 if (selected) {
1652 $li.attr('class','jd-autocomplete jd-selected');
1653 } else {
1654 $li.attr('class','jd-autocomplete');
1655 }
1656}
1657
1658function set_item_values(toroot, $li, match)
1659{
1660 var $link = $('a',$li);
1661 $link.html(match.__hilabel || match.label);
1662 $link.attr('href',toroot + match.link);
1663}
1664
Scott Main719acb42013-12-05 16:05:09 -08001665function set_item_values_jd(toroot, $li, match)
1666{
1667 var $link = $('a',$li);
1668 $link.html(match.title);
1669 $link.attr('href',toroot + match.url);
1670}
1671
Scott Main0e76e7e2013-03-12 10:24:07 -07001672function new_suggestion($list) {
Scott Main7e447ed2013-02-19 17:22:37 -08001673 var $li = $("<li class='jd-autocomplete'></li>");
1674 $list.append($li);
1675
1676 $li.mousedown(function() {
1677 window.location = this.firstChild.getAttribute("href");
1678 });
1679 $li.mouseover(function() {
Scott Main0e76e7e2013-03-12 10:24:07 -07001680 $('.search_filtered_wrapper li').removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001681 $(this).addClass('jd-selected');
Scott Main0e76e7e2013-03-12 10:24:07 -07001682 gSelectedColumn = $(".search_filtered:visible").index($(this).closest('.search_filtered'));
1683 gSelectedIndex = $("li", $(".search_filtered:visible")[gSelectedColumn]).index(this);
Scott Main7e447ed2013-02-19 17:22:37 -08001684 });
Scott Mainde295272013-03-25 15:48:35 -07001685 $li.append("<a onclick='onSuggestionClick(this)'></a>");
Scott Main7e447ed2013-02-19 17:22:37 -08001686 $li.attr('class','show-item');
1687 return $li;
1688}
1689
Scott Mainf5089842012-08-14 16:31:07 -07001690function sync_selection_table(toroot)
1691{
Scott Mainf5089842012-08-14 16:31:07 -07001692 var $li; //list item jquery object
1693 var i; //list item iterator
Scott Main7e447ed2013-02-19 17:22:37 -08001694
Scott Main0e76e7e2013-03-12 10:24:07 -07001695 // if there are NO results at all, hide all columns
1696 if (!(gMatches.length > 0) && !(gGoogleMatches.length > 0) && !(gDocsMatches.length > 0)) {
1697 $('.suggest-card').hide(300);
1698 return;
1699 }
1700
1701 // if there are api results
Scott Main7e447ed2013-02-19 17:22:37 -08001702 if ((gMatches.length > 0) || (gGoogleMatches.length > 0)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001703 // reveal suggestion list
1704 $('.suggest-card.dummy').show();
1705 $('.suggest-card.reference').show();
1706 var listIndex = 0; // list index position
Scott Main7e447ed2013-02-19 17:22:37 -08001707
Scott Main0e76e7e2013-03-12 10:24:07 -07001708 // reset the lists
1709 $(".search_filtered_wrapper.reference li").remove();
Scott Main7e447ed2013-02-19 17:22:37 -08001710
Scott Main0e76e7e2013-03-12 10:24:07 -07001711 // ########### ANDROID RESULTS #############
1712 if (gMatches.length > 0) {
Scott Main7e447ed2013-02-19 17:22:37 -08001713
Scott Main0e76e7e2013-03-12 10:24:07 -07001714 // determine android results to show
1715 gListLength = gMatches.length < ROW_COUNT_FRAMEWORK ?
1716 gMatches.length : ROW_COUNT_FRAMEWORK;
1717 for (i=0; i<gListLength; i++) {
1718 var $li = new_suggestion($(".suggest-card.reference ul"));
1719 set_item_values(toroot, $li, gMatches[i]);
1720 set_item_selected($li, i == gSelectedIndex);
1721 }
1722 }
Scott Main7e447ed2013-02-19 17:22:37 -08001723
Scott Main0e76e7e2013-03-12 10:24:07 -07001724 // ########### GOOGLE RESULTS #############
1725 if (gGoogleMatches.length > 0) {
1726 // show header for list
1727 $(".suggest-card.reference ul").append("<li class='header'>in Google Services:</li>");
Scott Main7e447ed2013-02-19 17:22:37 -08001728
Scott Main0e76e7e2013-03-12 10:24:07 -07001729 // determine google results to show
1730 gGoogleListLength = gGoogleMatches.length < ROW_COUNT_GOOGLE ? gGoogleMatches.length : ROW_COUNT_GOOGLE;
1731 for (i=0; i<gGoogleListLength; i++) {
1732 var $li = new_suggestion($(".suggest-card.reference ul"));
1733 set_item_values(toroot, $li, gGoogleMatches[i]);
1734 set_item_selected($li, i == gSelectedIndex);
1735 }
1736 }
Scott Mainf5089842012-08-14 16:31:07 -07001737 } else {
Scott Main0e76e7e2013-03-12 10:24:07 -07001738 $('.suggest-card.reference').hide();
1739 $('.suggest-card.dummy').hide();
1740 }
1741
1742 // ########### JD DOC RESULTS #############
1743 if (gDocsMatches.length > 0) {
1744 // reset the lists
1745 $(".search_filtered_wrapper.docs li").remove();
1746
1747 // determine google results to show
Scott Main719acb42013-12-05 16:05:09 -08001748 // NOTE: The order of the conditions below for the sugg.type MUST BE SPECIFIC:
1749 // The order must match the reverse order that each section appears as a card in
1750 // the suggestion UI... this may be only for the "develop" grouped items though.
Scott Main0e76e7e2013-03-12 10:24:07 -07001751 gDocsListLength = gDocsMatches.length < ROW_COUNT_DOCS ? gDocsMatches.length : ROW_COUNT_DOCS;
1752 for (i=0; i<gDocsListLength; i++) {
1753 var sugg = gDocsMatches[i];
1754 var $li;
1755 if (sugg.type == "design") {
1756 $li = new_suggestion($(".suggest-card.design ul"));
1757 } else
1758 if (sugg.type == "distribute") {
1759 $li = new_suggestion($(".suggest-card.distribute ul"));
1760 } else
Scott Main719acb42013-12-05 16:05:09 -08001761 if (sugg.type == "samples") {
1762 $li = new_suggestion($(".suggest-card.develop .child-card.samples"));
1763 } else
Scott Main0e76e7e2013-03-12 10:24:07 -07001764 if (sugg.type == "training") {
1765 $li = new_suggestion($(".suggest-card.develop .child-card.training"));
1766 } else
Scott Main719acb42013-12-05 16:05:09 -08001767 if (sugg.type == "about"||"guide"||"tools"||"google") {
Scott Main0e76e7e2013-03-12 10:24:07 -07001768 $li = new_suggestion($(".suggest-card.develop .child-card.guides"));
1769 } else {
1770 continue;
1771 }
1772
Scott Main719acb42013-12-05 16:05:09 -08001773 set_item_values_jd(toroot, $li, sugg);
Scott Main0e76e7e2013-03-12 10:24:07 -07001774 set_item_selected($li, i == gSelectedIndex);
1775 }
1776
1777 // add heading and show or hide card
1778 if ($(".suggest-card.design li").length > 0) {
1779 $(".suggest-card.design ul").prepend("<li class='header'>Design:</li>");
1780 $(".suggest-card.design").show(300);
1781 } else {
1782 $('.suggest-card.design').hide(300);
1783 }
1784 if ($(".suggest-card.distribute li").length > 0) {
1785 $(".suggest-card.distribute ul").prepend("<li class='header'>Distribute:</li>");
1786 $(".suggest-card.distribute").show(300);
1787 } else {
1788 $('.suggest-card.distribute').hide(300);
1789 }
1790 if ($(".child-card.guides li").length > 0) {
1791 $(".child-card.guides").prepend("<li class='header'>Guides:</li>");
1792 $(".child-card.guides li").appendTo(".suggest-card.develop ul");
1793 }
1794 if ($(".child-card.training li").length > 0) {
1795 $(".child-card.training").prepend("<li class='header'>Training:</li>");
1796 $(".child-card.training li").appendTo(".suggest-card.develop ul");
1797 }
Scott Main719acb42013-12-05 16:05:09 -08001798 if ($(".child-card.samples li").length > 0) {
1799 $(".child-card.samples").prepend("<li class='header'>Samples:</li>");
1800 $(".child-card.samples li").appendTo(".suggest-card.develop ul");
1801 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001802
1803 if ($(".suggest-card.develop li").length > 0) {
1804 $(".suggest-card.develop").show(300);
1805 } else {
1806 $('.suggest-card.develop').hide(300);
1807 }
1808
1809 } else {
1810 $('.search_filtered_wrapper.docs .suggest-card:not(.dummy)').hide(300);
Scott Mainf5089842012-08-14 16:31:07 -07001811 }
1812}
1813
Scott Main0e76e7e2013-03-12 10:24:07 -07001814/** Called by the search input's onkeydown and onkeyup events.
1815 * Handles navigation with keyboard arrows, Enter key to invoke search,
1816 * otherwise invokes search suggestions on key-up event.
1817 * @param e The JS event
1818 * @param kd True if the event is key-down
Scott Main3b90aff2013-08-01 18:09:35 -07001819 * @param toroot A string for the site's root path
Scott Main0e76e7e2013-03-12 10:24:07 -07001820 * @returns True if the event should bubble up
1821 */
Scott Mainf5089842012-08-14 16:31:07 -07001822function search_changed(e, kd, toroot)
1823{
Scott Main719acb42013-12-05 16:05:09 -08001824 var currentLang = getLangPref();
Scott Mainf5089842012-08-14 16:31:07 -07001825 var search = document.getElementById("search_autocomplete");
1826 var text = search.value.replace(/(^ +)|( +$)/g, '');
Scott Main0e76e7e2013-03-12 10:24:07 -07001827 // get the ul hosting the currently selected item
1828 gSelectedColumn = gSelectedColumn >= 0 ? gSelectedColumn : 0;
1829 var $columns = $(".search_filtered_wrapper").find(".search_filtered:visible");
1830 var $selectedUl = $columns[gSelectedColumn];
1831
Scott Mainf5089842012-08-14 16:31:07 -07001832 // show/hide the close button
1833 if (text != '') {
1834 $(".search .close").removeClass("hide");
1835 } else {
1836 $(".search .close").addClass("hide");
1837 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001838 // 27 = esc
1839 if (e.keyCode == 27) {
1840 // close all search results
1841 if (kd) $('.search .close').trigger('click');
1842 return true;
1843 }
Scott Mainf5089842012-08-14 16:31:07 -07001844 // 13 = enter
Scott Main0e76e7e2013-03-12 10:24:07 -07001845 else if (e.keyCode == 13) {
1846 if (gSelectedIndex < 0) {
1847 $('.suggest-card').hide();
Scott Main7e447ed2013-02-19 17:22:37 -08001848 if ($("#searchResults").is(":hidden") && (search.value != "")) {
1849 // if results aren't showing (and text not empty), return true to allow search to execute
Dirk Doughertyc3921652014-05-13 16:55:26 -07001850 $('body,html').animate({scrollTop:0}, '500', 'swing');
Scott Mainf5089842012-08-14 16:31:07 -07001851 return true;
1852 } else {
1853 // otherwise, results are already showing, so allow ajax to auto refresh the results
1854 // and ignore this Enter press to avoid the reload.
1855 return false;
1856 }
1857 } else if (kd && gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001858 // click the link corresponding to selected item
1859 $("a",$("li",$selectedUl)[gSelectedIndex]).get()[0].click();
Scott Mainf5089842012-08-14 16:31:07 -07001860 return false;
1861 }
1862 }
Scott Mainb16376f2014-05-21 20:35:47 -07001863 // If Google results are showing, return true to allow ajax search to execute
Scott Main0e76e7e2013-03-12 10:24:07 -07001864 else if ($("#searchResults").is(":visible")) {
Scott Mainb16376f2014-05-21 20:35:47 -07001865 // Also, if search_results is scrolled out of view, scroll to top to make results visible
Dirk Doughertyca1230c2014-05-14 20:00:03 -07001866 if ((sticky ) && (search.value != "")) {
1867 $('body,html').animate({scrollTop:0}, '500', 'swing');
1868 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001869 return true;
1870 }
1871 // 38 UP ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001872 else if (kd && (e.keyCode == 38)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001873 // if the next item is a header, skip it
1874 if ($($("li", $selectedUl)[gSelectedIndex-1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001875 gSelectedIndex--;
Scott Main7e447ed2013-02-19 17:22:37 -08001876 }
1877 if (gSelectedIndex >= 0) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001878 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001879 gSelectedIndex--;
Scott Main0e76e7e2013-03-12 10:24:07 -07001880 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1881 // If user reaches top, reset selected column
1882 if (gSelectedIndex < 0) {
1883 gSelectedColumn = -1;
1884 }
Scott Mainf5089842012-08-14 16:31:07 -07001885 }
1886 return false;
1887 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001888 // 40 DOWN ARROW
Scott Mainf5089842012-08-14 16:31:07 -07001889 else if (kd && (e.keyCode == 40)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001890 // if the next item is a header, skip it
1891 if ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header")) {
Scott Mainf5089842012-08-14 16:31:07 -07001892 gSelectedIndex++;
Scott Main7e447ed2013-02-19 17:22:37 -08001893 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001894 if ((gSelectedIndex < $("li", $selectedUl).length-1) ||
1895 ($($("li", $selectedUl)[gSelectedIndex+1]).hasClass("header"))) {
1896 $('li', $selectedUl).removeClass('jd-selected');
Scott Main7e447ed2013-02-19 17:22:37 -08001897 gSelectedIndex++;
Scott Main0e76e7e2013-03-12 10:24:07 -07001898 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
Scott Mainf5089842012-08-14 16:31:07 -07001899 }
1900 return false;
1901 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001902 // Consider left/right arrow navigation
1903 // NOTE: Order of suggest columns are reverse order (index position 0 is on right)
1904 else if (kd && $columns.length > 1 && gSelectedColumn >= 0) {
1905 // 37 LEFT ARROW
1906 // go left only if current column is not left-most column (last column)
1907 if (e.keyCode == 37 && gSelectedColumn < $columns.length - 1) {
1908 $('li', $selectedUl).removeClass('jd-selected');
1909 gSelectedColumn++;
1910 $selectedUl = $columns[gSelectedColumn];
1911 // keep or reset the selected item to last item as appropriate
1912 gSelectedIndex = gSelectedIndex >
1913 $("li", $selectedUl).length-1 ?
1914 $("li", $selectedUl).length-1 : gSelectedIndex;
1915 // if the corresponding item is a header, move down
1916 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1917 gSelectedIndex++;
1918 }
Scott Main3b90aff2013-08-01 18:09:35 -07001919 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07001920 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1921 return false;
1922 }
1923 // 39 RIGHT ARROW
1924 // go right only if current column is not the right-most column (first column)
1925 else if (e.keyCode == 39 && gSelectedColumn > 0) {
1926 $('li', $selectedUl).removeClass('jd-selected');
1927 gSelectedColumn--;
1928 $selectedUl = $columns[gSelectedColumn];
1929 // keep or reset the selected item to last item as appropriate
1930 gSelectedIndex = gSelectedIndex >
1931 $("li", $selectedUl).length-1 ?
1932 $("li", $selectedUl).length-1 : gSelectedIndex;
1933 // if the corresponding item is a header, move down
1934 if ($($("li", $selectedUl)[gSelectedIndex]).hasClass("header")) {
1935 gSelectedIndex++;
1936 }
Scott Main3b90aff2013-08-01 18:09:35 -07001937 // set item selected
Scott Main0e76e7e2013-03-12 10:24:07 -07001938 $('li:nth-child('+(gSelectedIndex+1)+')', $selectedUl).addClass('jd-selected');
1939 return false;
1940 }
1941 }
1942
Scott Main719acb42013-12-05 16:05:09 -08001943 // if key-up event and not arrow down/up/left/right,
1944 // read the search query and add suggestions to gMatches
Scott Main0e76e7e2013-03-12 10:24:07 -07001945 else if (!kd && (e.keyCode != 40)
1946 && (e.keyCode != 38)
1947 && (e.keyCode != 37)
1948 && (e.keyCode != 39)) {
1949 gSelectedIndex = -1;
Scott Mainf5089842012-08-14 16:31:07 -07001950 gMatches = new Array();
1951 matchedCount = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08001952 gGoogleMatches = new Array();
1953 matchedCountGoogle = 0;
Scott Main0e76e7e2013-03-12 10:24:07 -07001954 gDocsMatches = new Array();
1955 matchedCountDocs = 0;
Scott Main7e447ed2013-02-19 17:22:37 -08001956
1957 // Search for Android matches
Scott Mainf5089842012-08-14 16:31:07 -07001958 for (var i=0; i<DATA.length; i++) {
1959 var s = DATA[i];
1960 if (text.length != 0 &&
1961 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1962 gMatches[matchedCount] = s;
1963 matchedCount++;
1964 }
1965 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001966 rank_autocomplete_api_results(text, gMatches);
Scott Mainf5089842012-08-14 16:31:07 -07001967 for (var i=0; i<gMatches.length; i++) {
1968 var s = gMatches[i];
Scott Main7e447ed2013-02-19 17:22:37 -08001969 }
1970
1971
1972 // Search for Google matches
1973 for (var i=0; i<GOOGLE_DATA.length; i++) {
1974 var s = GOOGLE_DATA[i];
1975 if (text.length != 0 &&
1976 s.label.toLowerCase().indexOf(text.toLowerCase()) != -1) {
1977 gGoogleMatches[matchedCountGoogle] = s;
1978 matchedCountGoogle++;
Scott Mainf5089842012-08-14 16:31:07 -07001979 }
1980 }
Scott Main0e76e7e2013-03-12 10:24:07 -07001981 rank_autocomplete_api_results(text, gGoogleMatches);
Scott Main7e447ed2013-02-19 17:22:37 -08001982 for (var i=0; i<gGoogleMatches.length; i++) {
1983 var s = gGoogleMatches[i];
1984 }
1985
Scott Mainf5089842012-08-14 16:31:07 -07001986 highlight_autocomplete_result_labels(text);
Scott Main0e76e7e2013-03-12 10:24:07 -07001987
1988
1989
Scott Main719acb42013-12-05 16:05:09 -08001990 // Search for matching JD docs
Scott Main0e76e7e2013-03-12 10:24:07 -07001991 if (text.length >= 3) {
Scott Main719acb42013-12-05 16:05:09 -08001992 // Regex to match only the beginning of a word
1993 var textRegex = new RegExp("\\b" + text.toLowerCase(), "g");
1994
1995
1996 // Search for Training classes
1997 for (var i=0; i<TRAINING_RESOURCES.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07001998 // current search comparison, with counters for tag and title,
1999 // used later to improve ranking
Scott Main719acb42013-12-05 16:05:09 -08002000 var s = TRAINING_RESOURCES[i];
Scott Main0e76e7e2013-03-12 10:24:07 -07002001 s.matched_tag = 0;
2002 s.matched_title = 0;
2003 var matched = false;
2004
2005 // Check if query matches any tags; work backwards toward 1 to assist ranking
Scott Main719acb42013-12-05 16:05:09 -08002006 for (var j = s.keywords.length - 1; j >= 0; j--) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002007 // it matches a tag
Scott Main719acb42013-12-05 16:05:09 -08002008 if (s.keywords[j].toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002009 matched = true;
2010 s.matched_tag = j + 1; // add 1 to index position
2011 }
2012 }
Scott Main719acb42013-12-05 16:05:09 -08002013 // Don't consider doc title for lessons (only for class landing pages),
2014 // unless the lesson has a tag that already matches
2015 if ((s.lang == currentLang) &&
2016 (!(s.type == "training" && s.url.indexOf("index.html") == -1) || matched)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002017 // it matches the doc title
Scott Main719acb42013-12-05 16:05:09 -08002018 if (s.title.toLowerCase().match(textRegex)) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002019 matched = true;
2020 s.matched_title = 1;
2021 }
2022 }
2023 if (matched) {
2024 gDocsMatches[matchedCountDocs] = s;
2025 matchedCountDocs++;
2026 }
2027 }
Scott Main719acb42013-12-05 16:05:09 -08002028
2029
2030 // Search for API Guides
2031 for (var i=0; i<GUIDE_RESOURCES.length; i++) {
2032 // current search comparison, with counters for tag and title,
2033 // used later to improve ranking
2034 var s = GUIDE_RESOURCES[i];
2035 s.matched_tag = 0;
2036 s.matched_title = 0;
2037 var matched = false;
2038
2039 // Check if query matches any tags; work backwards toward 1 to assist ranking
2040 for (var j = s.keywords.length - 1; j >= 0; j--) {
2041 // it matches a tag
2042 if (s.keywords[j].toLowerCase().match(textRegex)) {
2043 matched = true;
2044 s.matched_tag = j + 1; // add 1 to index position
2045 }
2046 }
2047 // Check if query matches the doc title, but only for current language
2048 if (s.lang == currentLang) {
2049 // if query matches the doc title
2050 if (s.title.toLowerCase().match(textRegex)) {
2051 matched = true;
2052 s.matched_title = 1;
2053 }
2054 }
2055 if (matched) {
2056 gDocsMatches[matchedCountDocs] = s;
2057 matchedCountDocs++;
2058 }
2059 }
2060
2061
2062 // Search for Tools Guides
2063 for (var i=0; i<TOOLS_RESOURCES.length; i++) {
2064 // current search comparison, with counters for tag and title,
2065 // used later to improve ranking
2066 var s = TOOLS_RESOURCES[i];
2067 s.matched_tag = 0;
2068 s.matched_title = 0;
2069 var matched = false;
2070
2071 // Check if query matches any tags; work backwards toward 1 to assist ranking
2072 for (var j = s.keywords.length - 1; j >= 0; j--) {
2073 // it matches a tag
2074 if (s.keywords[j].toLowerCase().match(textRegex)) {
2075 matched = true;
2076 s.matched_tag = j + 1; // add 1 to index position
2077 }
2078 }
2079 // Check if query matches the doc title, but only for current language
2080 if (s.lang == currentLang) {
2081 // if query matches the doc title
2082 if (s.title.toLowerCase().match(textRegex)) {
2083 matched = true;
2084 s.matched_title = 1;
2085 }
2086 }
2087 if (matched) {
2088 gDocsMatches[matchedCountDocs] = s;
2089 matchedCountDocs++;
2090 }
2091 }
2092
2093
2094 // Search for About docs
2095 for (var i=0; i<ABOUT_RESOURCES.length; i++) {
2096 // current search comparison, with counters for tag and title,
2097 // used later to improve ranking
2098 var s = ABOUT_RESOURCES[i];
2099 s.matched_tag = 0;
2100 s.matched_title = 0;
2101 var matched = false;
2102
2103 // Check if query matches any tags; work backwards toward 1 to assist ranking
2104 for (var j = s.keywords.length - 1; j >= 0; j--) {
2105 // it matches a tag
2106 if (s.keywords[j].toLowerCase().match(textRegex)) {
2107 matched = true;
2108 s.matched_tag = j + 1; // add 1 to index position
2109 }
2110 }
2111 // Check if query matches the doc title, but only for current language
2112 if (s.lang == currentLang) {
2113 // if query matches the doc title
2114 if (s.title.toLowerCase().match(textRegex)) {
2115 matched = true;
2116 s.matched_title = 1;
2117 }
2118 }
2119 if (matched) {
2120 gDocsMatches[matchedCountDocs] = s;
2121 matchedCountDocs++;
2122 }
2123 }
2124
2125
2126 // Search for Design guides
2127 for (var i=0; i<DESIGN_RESOURCES.length; i++) {
2128 // current search comparison, with counters for tag and title,
2129 // used later to improve ranking
2130 var s = DESIGN_RESOURCES[i];
2131 s.matched_tag = 0;
2132 s.matched_title = 0;
2133 var matched = false;
2134
2135 // Check if query matches any tags; work backwards toward 1 to assist ranking
2136 for (var j = s.keywords.length - 1; j >= 0; j--) {
2137 // it matches a tag
2138 if (s.keywords[j].toLowerCase().match(textRegex)) {
2139 matched = true;
2140 s.matched_tag = j + 1; // add 1 to index position
2141 }
2142 }
2143 // Check if query matches the doc title, but only for current language
2144 if (s.lang == currentLang) {
2145 // if query matches the doc title
2146 if (s.title.toLowerCase().match(textRegex)) {
2147 matched = true;
2148 s.matched_title = 1;
2149 }
2150 }
2151 if (matched) {
2152 gDocsMatches[matchedCountDocs] = s;
2153 matchedCountDocs++;
2154 }
2155 }
2156
2157
2158 // Search for Distribute guides
2159 for (var i=0; i<DISTRIBUTE_RESOURCES.length; i++) {
2160 // current search comparison, with counters for tag and title,
2161 // used later to improve ranking
2162 var s = DISTRIBUTE_RESOURCES[i];
2163 s.matched_tag = 0;
2164 s.matched_title = 0;
2165 var matched = false;
2166
2167 // Check if query matches any tags; work backwards toward 1 to assist ranking
2168 for (var j = s.keywords.length - 1; j >= 0; j--) {
2169 // it matches a tag
2170 if (s.keywords[j].toLowerCase().match(textRegex)) {
2171 matched = true;
2172 s.matched_tag = j + 1; // add 1 to index position
2173 }
2174 }
2175 // Check if query matches the doc title, but only for current language
2176 if (s.lang == currentLang) {
2177 // if query matches the doc title
2178 if (s.title.toLowerCase().match(textRegex)) {
2179 matched = true;
2180 s.matched_title = 1;
2181 }
2182 }
2183 if (matched) {
2184 gDocsMatches[matchedCountDocs] = s;
2185 matchedCountDocs++;
2186 }
2187 }
2188
2189
2190 // Search for Google guides
2191 for (var i=0; i<GOOGLE_RESOURCES.length; i++) {
2192 // current search comparison, with counters for tag and title,
2193 // used later to improve ranking
2194 var s = GOOGLE_RESOURCES[i];
2195 s.matched_tag = 0;
2196 s.matched_title = 0;
2197 var matched = false;
2198
2199 // Check if query matches any tags; work backwards toward 1 to assist ranking
2200 for (var j = s.keywords.length - 1; j >= 0; j--) {
2201 // it matches a tag
2202 if (s.keywords[j].toLowerCase().match(textRegex)) {
2203 matched = true;
2204 s.matched_tag = j + 1; // add 1 to index position
2205 }
2206 }
2207 // Check if query matches the doc title, but only for current language
2208 if (s.lang == currentLang) {
2209 // if query matches the doc title
2210 if (s.title.toLowerCase().match(textRegex)) {
2211 matched = true;
2212 s.matched_title = 1;
2213 }
2214 }
2215 if (matched) {
2216 gDocsMatches[matchedCountDocs] = s;
2217 matchedCountDocs++;
2218 }
2219 }
2220
2221
2222 // Search for Samples
2223 for (var i=0; i<SAMPLES_RESOURCES.length; i++) {
2224 // current search comparison, with counters for tag and title,
2225 // used later to improve ranking
2226 var s = SAMPLES_RESOURCES[i];
2227 s.matched_tag = 0;
2228 s.matched_title = 0;
2229 var matched = false;
2230 // Check if query matches any tags; work backwards toward 1 to assist ranking
2231 for (var j = s.keywords.length - 1; j >= 0; j--) {
2232 // it matches a tag
2233 if (s.keywords[j].toLowerCase().match(textRegex)) {
2234 matched = true;
2235 s.matched_tag = j + 1; // add 1 to index position
2236 }
2237 }
2238 // Check if query matches the doc title, but only for current language
2239 if (s.lang == currentLang) {
2240 // if query matches the doc title.t
2241 if (s.title.toLowerCase().match(textRegex)) {
2242 matched = true;
2243 s.matched_title = 1;
2244 }
2245 }
2246 if (matched) {
2247 gDocsMatches[matchedCountDocs] = s;
2248 matchedCountDocs++;
2249 }
2250 }
2251
2252 // Rank/sort all the matched pages
Scott Main0e76e7e2013-03-12 10:24:07 -07002253 rank_autocomplete_doc_results(text, gDocsMatches);
2254 }
2255
2256 // draw the suggestions
Scott Mainf5089842012-08-14 16:31:07 -07002257 sync_selection_table(toroot);
2258 return true; // allow the event to bubble up to the search api
2259 }
2260}
2261
Scott Main0e76e7e2013-03-12 10:24:07 -07002262/* Order the jd doc result list based on match quality */
2263function rank_autocomplete_doc_results(query, matches) {
2264 query = query || '';
2265 if (!matches || !matches.length)
2266 return;
2267
2268 var _resultScoreFn = function(match) {
2269 var score = 1.0;
2270
2271 // if the query matched a tag
2272 if (match.matched_tag > 0) {
2273 // multiply score by factor relative to position in tags list (max of 3)
2274 score *= 3 / match.matched_tag;
2275
2276 // if it also matched the title
2277 if (match.matched_title > 0) {
2278 score *= 2;
2279 }
2280 } else if (match.matched_title > 0) {
2281 score *= 3;
2282 }
2283
2284 return score;
2285 };
2286
2287 for (var i=0; i<matches.length; i++) {
2288 matches[i].__resultScore = _resultScoreFn(matches[i]);
2289 }
2290
2291 matches.sort(function(a,b){
2292 var n = b.__resultScore - a.__resultScore;
2293 if (n == 0) // lexicographical sort if scores are the same
2294 n = (a.label < b.label) ? -1 : 1;
2295 return n;
2296 });
2297}
2298
Scott Main7e447ed2013-02-19 17:22:37 -08002299/* Order the result list based on match quality */
Scott Main0e76e7e2013-03-12 10:24:07 -07002300function rank_autocomplete_api_results(query, matches) {
Scott Mainf5089842012-08-14 16:31:07 -07002301 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002302 if (!matches || !matches.length)
Scott Mainf5089842012-08-14 16:31:07 -07002303 return;
2304
2305 // helper function that gets the last occurence index of the given regex
2306 // in the given string, or -1 if not found
2307 var _lastSearch = function(s, re) {
2308 if (s == '')
2309 return -1;
2310 var l = -1;
2311 var tmp;
2312 while ((tmp = s.search(re)) >= 0) {
2313 if (l < 0) l = 0;
2314 l += tmp;
2315 s = s.substr(tmp + 1);
2316 }
2317 return l;
2318 };
2319
2320 // helper function that counts the occurrences of a given character in
2321 // a given string
2322 var _countChar = function(s, c) {
2323 var n = 0;
2324 for (var i=0; i<s.length; i++)
2325 if (s.charAt(i) == c) ++n;
2326 return n;
2327 };
2328
2329 var queryLower = query.toLowerCase();
2330 var queryAlnum = (queryLower.match(/\w+/) || [''])[0];
2331 var partPrefixAlnumRE = new RegExp('\\b' + queryAlnum);
2332 var partExactAlnumRE = new RegExp('\\b' + queryAlnum + '\\b');
2333
2334 var _resultScoreFn = function(result) {
2335 // scores are calculated based on exact and prefix matches,
2336 // and then number of path separators (dots) from the last
2337 // match (i.e. favoring classes and deep package names)
2338 var score = 1.0;
2339 var labelLower = result.label.toLowerCase();
2340 var t;
2341 t = _lastSearch(labelLower, partExactAlnumRE);
2342 if (t >= 0) {
2343 // exact part match
2344 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2345 score *= 200 / (partsAfter + 1);
2346 } else {
2347 t = _lastSearch(labelLower, partPrefixAlnumRE);
2348 if (t >= 0) {
2349 // part prefix match
2350 var partsAfter = _countChar(labelLower.substr(t + 1), '.');
2351 score *= 20 / (partsAfter + 1);
2352 }
2353 }
2354
2355 return score;
2356 };
2357
Scott Main7e447ed2013-02-19 17:22:37 -08002358 for (var i=0; i<matches.length; i++) {
Scott Main0e76e7e2013-03-12 10:24:07 -07002359 // if the API is deprecated, default score is 0; otherwise, perform scoring
2360 if (matches[i].deprecated == "true") {
2361 matches[i].__resultScore = 0;
2362 } else {
2363 matches[i].__resultScore = _resultScoreFn(matches[i]);
2364 }
Scott Mainf5089842012-08-14 16:31:07 -07002365 }
2366
Scott Main7e447ed2013-02-19 17:22:37 -08002367 matches.sort(function(a,b){
Scott Mainf5089842012-08-14 16:31:07 -07002368 var n = b.__resultScore - a.__resultScore;
2369 if (n == 0) // lexicographical sort if scores are the same
2370 n = (a.label < b.label) ? -1 : 1;
2371 return n;
2372 });
2373}
2374
Scott Main7e447ed2013-02-19 17:22:37 -08002375/* Add emphasis to part of string that matches query */
Scott Mainf5089842012-08-14 16:31:07 -07002376function highlight_autocomplete_result_labels(query) {
2377 query = query || '';
Scott Main7e447ed2013-02-19 17:22:37 -08002378 if ((!gMatches || !gMatches.length) && (!gGoogleMatches || !gGoogleMatches.length))
Scott Mainf5089842012-08-14 16:31:07 -07002379 return;
2380
2381 var queryLower = query.toLowerCase();
2382 var queryAlnumDot = (queryLower.match(/[\w\.]+/) || [''])[0];
2383 var queryRE = new RegExp(
2384 '(' + queryAlnumDot.replace(/\./g, '\\.') + ')', 'ig');
2385 for (var i=0; i<gMatches.length; i++) {
2386 gMatches[i].__hilabel = gMatches[i].label.replace(
2387 queryRE, '<b>$1</b>');
2388 }
Scott Main7e447ed2013-02-19 17:22:37 -08002389 for (var i=0; i<gGoogleMatches.length; i++) {
2390 gGoogleMatches[i].__hilabel = gGoogleMatches[i].label.replace(
2391 queryRE, '<b>$1</b>');
2392 }
Scott Mainf5089842012-08-14 16:31:07 -07002393}
2394
2395function search_focus_changed(obj, focused)
2396{
Scott Main3b90aff2013-08-01 18:09:35 -07002397 if (!focused) {
Scott Mainf5089842012-08-14 16:31:07 -07002398 if(obj.value == ""){
2399 $(".search .close").addClass("hide");
2400 }
Scott Main0e76e7e2013-03-12 10:24:07 -07002401 $(".suggest-card").hide();
Scott Mainf5089842012-08-14 16:31:07 -07002402 }
2403}
2404
2405function submit_search() {
2406 var query = document.getElementById('search_autocomplete').value;
2407 location.hash = 'q=' + query;
2408 loadSearchResults();
Dirk Doughertyc3921652014-05-13 16:55:26 -07002409 $("#searchResults").slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002410 return false;
2411}
2412
2413
2414function hideResults() {
Dirk Doughertyc3921652014-05-13 16:55:26 -07002415 $("#searchResults").slideUp('fast', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002416 $(".search .close").addClass("hide");
2417 location.hash = '';
Scott Main3b90aff2013-08-01 18:09:35 -07002418
Scott Mainf5089842012-08-14 16:31:07 -07002419 $("#search_autocomplete").val("").blur();
Scott Main3b90aff2013-08-01 18:09:35 -07002420
Scott Mainf5089842012-08-14 16:31:07 -07002421 // reset the ajax search callback to nothing, so results don't appear unless ENTER
2422 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {});
Scott Main0e76e7e2013-03-12 10:24:07 -07002423
2424 // forcefully regain key-up event control (previously jacked by search api)
2425 $("#search_autocomplete").keyup(function(event) {
2426 return search_changed(event, false, toRoot);
2427 });
2428
Scott Mainf5089842012-08-14 16:31:07 -07002429 return false;
2430}
2431
2432
2433
2434/* ########################################################## */
2435/* ################ CUSTOM SEARCH ENGINE ################## */
2436/* ########################################################## */
2437
Scott Mainf5089842012-08-14 16:31:07 -07002438var searchControl;
Scott Main0e76e7e2013-03-12 10:24:07 -07002439google.load('search', '1', {"callback" : function() {
2440 searchControl = new google.search.SearchControl();
2441 } });
Scott Mainf5089842012-08-14 16:31:07 -07002442
2443function loadSearchResults() {
2444 document.getElementById("search_autocomplete").style.color = "#000";
2445
Scott Mainf5089842012-08-14 16:31:07 -07002446 searchControl = new google.search.SearchControl();
2447
2448 // use our existing search form and use tabs when multiple searchers are used
2449 drawOptions = new google.search.DrawOptions();
2450 drawOptions.setDrawMode(google.search.SearchControl.DRAW_MODE_TABBED);
2451 drawOptions.setInput(document.getElementById("search_autocomplete"));
2452
2453 // configure search result options
2454 searchOptions = new google.search.SearcherOptions();
2455 searchOptions.setExpandMode(GSearchControl.EXPAND_MODE_OPEN);
2456
2457 // configure each of the searchers, for each tab
2458 devSiteSearcher = new google.search.WebSearch();
2459 devSiteSearcher.setUserDefinedLabel("All");
2460 devSiteSearcher.setSiteRestriction("001482626316274216503:zu90b7s047u");
2461
2462 designSearcher = new google.search.WebSearch();
2463 designSearcher.setUserDefinedLabel("Design");
2464 designSearcher.setSiteRestriction("http://developer.android.com/design/");
2465
2466 trainingSearcher = new google.search.WebSearch();
2467 trainingSearcher.setUserDefinedLabel("Training");
2468 trainingSearcher.setSiteRestriction("http://developer.android.com/training/");
2469
2470 guidesSearcher = new google.search.WebSearch();
2471 guidesSearcher.setUserDefinedLabel("Guides");
2472 guidesSearcher.setSiteRestriction("http://developer.android.com/guide/");
2473
2474 referenceSearcher = new google.search.WebSearch();
2475 referenceSearcher.setUserDefinedLabel("Reference");
2476 referenceSearcher.setSiteRestriction("http://developer.android.com/reference/");
2477
Scott Maindf08ada2012-12-03 08:54:37 -08002478 googleSearcher = new google.search.WebSearch();
2479 googleSearcher.setUserDefinedLabel("Google Services");
2480 googleSearcher.setSiteRestriction("http://developer.android.com/google/");
2481
Scott Mainf5089842012-08-14 16:31:07 -07002482 blogSearcher = new google.search.WebSearch();
2483 blogSearcher.setUserDefinedLabel("Blog");
2484 blogSearcher.setSiteRestriction("http://android-developers.blogspot.com");
2485
2486 // add each searcher to the search control
2487 searchControl.addSearcher(devSiteSearcher, searchOptions);
2488 searchControl.addSearcher(designSearcher, searchOptions);
2489 searchControl.addSearcher(trainingSearcher, searchOptions);
2490 searchControl.addSearcher(guidesSearcher, searchOptions);
2491 searchControl.addSearcher(referenceSearcher, searchOptions);
Scott Maindf08ada2012-12-03 08:54:37 -08002492 searchControl.addSearcher(googleSearcher, searchOptions);
Scott Mainf5089842012-08-14 16:31:07 -07002493 searchControl.addSearcher(blogSearcher, searchOptions);
2494
2495 // configure result options
2496 searchControl.setResultSetSize(google.search.Search.LARGE_RESULTSET);
2497 searchControl.setLinkTarget(google.search.Search.LINK_TARGET_SELF);
2498 searchControl.setTimeoutInterval(google.search.SearchControl.TIMEOUT_SHORT);
2499 searchControl.setNoResultsString(google.search.SearchControl.NO_RESULTS_DEFAULT_STRING);
2500
2501 // upon ajax search, refresh the url and search title
2502 searchControl.setSearchStartingCallback(this, function(control, searcher, query) {
2503 updateResultTitle(query);
2504 var query = document.getElementById('search_autocomplete').value;
2505 location.hash = 'q=' + query;
2506 });
2507
Scott Mainde295272013-03-25 15:48:35 -07002508 // once search results load, set up click listeners
2509 searchControl.setSearchCompleteCallback(this, function(control, searcher, query) {
2510 addResultClickListeners();
2511 });
2512
Scott Mainf5089842012-08-14 16:31:07 -07002513 // draw the search results box
2514 searchControl.draw(document.getElementById("leftSearchControl"), drawOptions);
2515
2516 // get query and execute the search
2517 searchControl.execute(decodeURI(getQuery(location.hash)));
2518
2519 document.getElementById("search_autocomplete").focus();
2520 addTabListeners();
2521}
2522// End of loadSearchResults
2523
2524
2525google.setOnLoadCallback(function(){
2526 if (location.hash.indexOf("q=") == -1) {
2527 // if there's no query in the url, don't search and make sure results are hidden
2528 $('#searchResults').hide();
2529 return;
2530 } else {
2531 // first time loading search results for this page
Dirk Doughertyc3921652014-05-13 16:55:26 -07002532 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002533 $(".search .close").removeClass("hide");
2534 loadSearchResults();
2535 }
2536}, true);
2537
smain@google.com9a818f52014-10-03 09:25:59 -07002538/* Adjust the scroll position to account for sticky header, only if the hash matches an id.
2539 This does not handle <a name=""> tags. Some CSS fixes those, but only for reference docs. */
Scott Mainb16376f2014-05-21 20:35:47 -07002540function offsetScrollForSticky() {
smain@google.com11fc0092014-10-16 22:10:00 -07002541 // Ignore if there's no search bar (some special pages have no header)
2542 if ($("#search-container").length < 1) return;
2543
smain@google.com3b77ab52014-06-17 11:57:27 -07002544 var hash = escape(location.hash.substr(1));
2545 var $matchingElement = $("#"+hash);
smain@google.com08f336ea2014-10-03 17:40:00 -07002546 // Sanity check that there's an element with that ID on the page
2547 if ($matchingElement.length) {
Scott Mainb16376f2014-05-21 20:35:47 -07002548 // If the position of the target element is near the top of the page (<20px, where we expect it
2549 // to be because we need to move it down 60px to become in view), then move it down 60px
2550 if (Math.abs($matchingElement.offset().top - $(window).scrollTop()) < 20) {
2551 $(window).scrollTop($(window).scrollTop() - 60);
Scott Mainb16376f2014-05-21 20:35:47 -07002552 }
2553 }
2554}
2555
Scott Mainf5089842012-08-14 16:31:07 -07002556// when an event on the browser history occurs (back, forward, load) requery hash and do search
2557$(window).hashchange( function(){
smain@google.com2f077192014-10-09 18:04:10 -07002558 // Ignore if there's no search bar (some special pages have no header)
2559 if ($("#search-container").length < 1) return;
2560
Dirk Doughertyc3921652014-05-13 16:55:26 -07002561 // If the hash isn't a search query or there's an error in the query,
2562 // then adjust the scroll position to account for sticky header, then exit.
Scott Mainf5089842012-08-14 16:31:07 -07002563 if ((location.hash.indexOf("q=") == -1) || (query == "undefined")) {
2564 // If the results pane is open, close it.
2565 if (!$("#searchResults").is(":hidden")) {
2566 hideResults();
2567 }
Scott Mainb16376f2014-05-21 20:35:47 -07002568 offsetScrollForSticky();
Scott Mainf5089842012-08-14 16:31:07 -07002569 return;
2570 }
2571
2572 // Otherwise, we have a search to do
2573 var query = decodeURI(getQuery(location.hash));
2574 searchControl.execute(query);
Dirk Doughertyc3921652014-05-13 16:55:26 -07002575 $('#searchResults').slideDown('slow', setStickyTop);
Scott Mainf5089842012-08-14 16:31:07 -07002576 $("#search_autocomplete").focus();
2577 $(".search .close").removeClass("hide");
2578
2579 updateResultTitle(query);
2580});
2581
2582function updateResultTitle(query) {
2583 $("#searchTitle").html("Results for <em>" + escapeHTML(query) + "</em>");
2584}
2585
2586// forcefully regain key-up event control (previously jacked by search api)
2587$("#search_autocomplete").keyup(function(event) {
2588 return search_changed(event, false, toRoot);
2589});
2590
2591// add event listeners to each tab so we can track the browser history
2592function addTabListeners() {
2593 var tabHeaders = $(".gsc-tabHeader");
2594 for (var i = 0; i < tabHeaders.length; i++) {
2595 $(tabHeaders[i]).attr("id",i).click(function() {
2596 /*
2597 // make a copy of the page numbers for the search left pane
2598 setTimeout(function() {
2599 // remove any residual page numbers
2600 $('#searchResults .gsc-tabsArea .gsc-cursor-box.gs-bidi-start-align').remove();
Scott Main3b90aff2013-08-01 18:09:35 -07002601 // move the page numbers to the left position; make a clone,
Scott Mainf5089842012-08-14 16:31:07 -07002602 // because the element is drawn to the DOM only once
Scott Main3b90aff2013-08-01 18:09:35 -07002603 // and because we're going to remove it (previous line),
2604 // we need it to be available to move again as the user navigates
Scott Mainf5089842012-08-14 16:31:07 -07002605 $('#searchResults .gsc-webResult .gsc-cursor-box.gs-bidi-start-align:visible')
2606 .clone().appendTo('#searchResults .gsc-tabsArea');
2607 }, 200);
2608 */
2609 });
2610 }
2611 setTimeout(function(){$(tabHeaders[0]).click()},200);
2612}
2613
Scott Mainde295272013-03-25 15:48:35 -07002614// add analytics tracking events to each result link
2615function addResultClickListeners() {
2616 $("#searchResults a.gs-title").each(function(index, link) {
2617 // When user clicks enter for Google search results, track it
2618 $(link).click(function() {
smain@google.com633f3222014-10-03 15:49:45 -07002619 ga('send', 'event', 'Google Click', 'clicked: ' + $(this).text(),
2620 'from: ' + $("#search_autocomplete").val());
Scott Mainde295272013-03-25 15:48:35 -07002621 });
2622 });
2623}
2624
Scott Mainf5089842012-08-14 16:31:07 -07002625
2626function getQuery(hash) {
2627 var queryParts = hash.split('=');
2628 return queryParts[1];
2629}
2630
2631/* returns the given string with all HTML brackets converted to entities
2632 TODO: move this to the site's JS library */
2633function escapeHTML(string) {
2634 return string.replace(/</g,"&lt;")
2635 .replace(/>/g,"&gt;");
2636}
2637
2638
2639
2640
2641
2642
2643
2644/* ######################################################## */
2645/* ################# JAVADOC REFERENCE ################### */
2646/* ######################################################## */
2647
Scott Main65511c02012-09-07 15:51:32 -07002648/* Initialize some droiddoc stuff, but only if we're in the reference */
Scott Main52dd2062013-08-15 12:22:28 -07002649if (location.pathname.indexOf("/reference") == 0) {
2650 if(!(location.pathname.indexOf("/reference-gms/packages.html") == 0)
2651 && !(location.pathname.indexOf("/reference-gcm/packages.html") == 0)
2652 && !(location.pathname.indexOf("/reference/com/google") == 0)) {
Robert Ly67d75f12012-12-03 12:53:42 -08002653 $(document).ready(function() {
2654 // init available apis based on user pref
2655 changeApiLevel();
2656 initSidenavHeightResize()
2657 });
2658 }
Scott Main65511c02012-09-07 15:51:32 -07002659}
Scott Mainf5089842012-08-14 16:31:07 -07002660
2661var API_LEVEL_COOKIE = "api_level";
2662var minLevel = 1;
2663var maxLevel = 1;
2664
2665/******* SIDENAV DIMENSIONS ************/
Scott Main3b90aff2013-08-01 18:09:35 -07002666
Scott Mainf5089842012-08-14 16:31:07 -07002667 function initSidenavHeightResize() {
2668 // Change the drag bar size to nicely fit the scrollbar positions
2669 var $dragBar = $(".ui-resizable-s");
2670 $dragBar.css({'width': $dragBar.parent().width() - 5 + "px"});
Scott Main3b90aff2013-08-01 18:09:35 -07002671
2672 $( "#resize-packages-nav" ).resizable({
Scott Mainf5089842012-08-14 16:31:07 -07002673 containment: "#nav-panels",
2674 handles: "s",
2675 alsoResize: "#packages-nav",
2676 resize: function(event, ui) { resizeNav(); }, /* resize the nav while dragging */
2677 stop: function(event, ui) { saveNavPanels(); } /* once stopped, save the sizes to cookie */
2678 });
Scott Main3b90aff2013-08-01 18:09:35 -07002679
Scott Mainf5089842012-08-14 16:31:07 -07002680 }
Scott Main3b90aff2013-08-01 18:09:35 -07002681
Scott Mainf5089842012-08-14 16:31:07 -07002682function updateSidenavFixedWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002683 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002684 $('#devdoc-nav').css({
2685 'width' : $('#side-nav').css('width'),
2686 'margin' : $('#side-nav').css('margin')
2687 });
2688 $('#devdoc-nav a.totop').css({'display':'block','width':$("#nav").innerWidth()+'px'});
Scott Main3b90aff2013-08-01 18:09:35 -07002689
Scott Mainf5089842012-08-14 16:31:07 -07002690 initSidenavHeightResize();
2691}
2692
2693function updateSidenavFullscreenWidth() {
Scott Mainf5257812014-05-22 17:26:38 -07002694 if (!sticky) return;
Scott Mainf5089842012-08-14 16:31:07 -07002695 $('#devdoc-nav').css({
2696 'width' : $('#side-nav').css('width'),
2697 'margin' : $('#side-nav').css('margin')
2698 });
2699 $('#devdoc-nav .totop').css({'left': 'inherit'});
Scott Main3b90aff2013-08-01 18:09:35 -07002700
Scott Mainf5089842012-08-14 16:31:07 -07002701 initSidenavHeightResize();
2702}
2703
2704function buildApiLevelSelector() {
2705 maxLevel = SINCE_DATA.length;
2706 var userApiLevel = parseInt(readCookie(API_LEVEL_COOKIE));
2707 userApiLevel = userApiLevel == 0 ? maxLevel : userApiLevel; // If there's no cookie (zero), use the max by default
2708
2709 minLevel = parseInt($("#doc-api-level").attr("class"));
2710 // Handle provisional api levels; the provisional level will always be the highest possible level
2711 // Provisional api levels will also have a length; other stuff that's just missing a level won't,
2712 // so leave those kinds of entities at the default level of 1 (for example, the R.styleable class)
2713 if (isNaN(minLevel) && minLevel.length) {
2714 minLevel = maxLevel;
2715 }
2716 var select = $("#apiLevelSelector").html("").change(changeApiLevel);
2717 for (var i = maxLevel-1; i >= 0; i--) {
2718 var option = $("<option />").attr("value",""+SINCE_DATA[i]).append(""+SINCE_DATA[i]);
2719 // if (SINCE_DATA[i] < minLevel) option.addClass("absent"); // always false for strings (codenames)
2720 select.append(option);
2721 }
2722
2723 // get the DOM element and use setAttribute cuz IE6 fails when using jquery .attr('selected',true)
2724 var selectedLevelItem = $("#apiLevelSelector option[value='"+userApiLevel+"']").get(0);
2725 selectedLevelItem.setAttribute('selected',true);
2726}
2727
2728function changeApiLevel() {
2729 maxLevel = SINCE_DATA.length;
2730 var selectedLevel = maxLevel;
2731
2732 selectedLevel = parseInt($("#apiLevelSelector option:selected").val());
2733 toggleVisisbleApis(selectedLevel, "body");
2734
2735 var date = new Date();
2736 date.setTime(date.getTime()+(10*365*24*60*60*1000)); // keep this for 10 years
2737 var expiration = date.toGMTString();
2738 writeCookie(API_LEVEL_COOKIE, selectedLevel, null, expiration);
2739
2740 if (selectedLevel < minLevel) {
2741 var thing = ($("#jd-header").html().indexOf("package") != -1) ? "package" : "class";
Scott Main8f24ca82012-11-16 10:34:22 -08002742 $("#naMessage").show().html("<div><p><strong>This " + thing
2743 + " requires API level " + minLevel + " or higher.</strong></p>"
2744 + "<p>This document is hidden because your selected API level for the documentation is "
2745 + selectedLevel + ". You can change the documentation API level with the selector "
2746 + "above the left navigation.</p>"
2747 + "<p>For more information about specifying the API level your app requires, "
2748 + "read <a href='" + toRoot + "training/basics/supporting-devices/platforms.html'"
2749 + ">Supporting Different Platform Versions</a>.</p>"
2750 + "<input type='button' value='OK, make this page visible' "
2751 + "title='Change the API level to " + minLevel + "' "
2752 + "onclick='$(\"#apiLevelSelector\").val(\"" + minLevel + "\");changeApiLevel();' />"
2753 + "</div>");
Scott Mainf5089842012-08-14 16:31:07 -07002754 } else {
2755 $("#naMessage").hide();
2756 }
2757}
2758
2759function toggleVisisbleApis(selectedLevel, context) {
2760 var apis = $(".api",context);
2761 apis.each(function(i) {
2762 var obj = $(this);
2763 var className = obj.attr("class");
2764 var apiLevelIndex = className.lastIndexOf("-")+1;
2765 var apiLevelEndIndex = className.indexOf(" ", apiLevelIndex);
2766 apiLevelEndIndex = apiLevelEndIndex != -1 ? apiLevelEndIndex : className.length;
2767 var apiLevel = className.substring(apiLevelIndex, apiLevelEndIndex);
2768 if (apiLevel.length == 0) { // for odd cases when the since data is actually missing, just bail
2769 return;
2770 }
2771 apiLevel = parseInt(apiLevel);
2772
2773 // Handle provisional api levels; if this item's level is the provisional one, set it to the max
2774 var selectedLevelNum = parseInt(selectedLevel)
2775 var apiLevelNum = parseInt(apiLevel);
2776 if (isNaN(apiLevelNum)) {
2777 apiLevelNum = maxLevel;
2778 }
2779
2780 // Grey things out that aren't available and give a tooltip title
2781 if (apiLevelNum > selectedLevelNum) {
2782 obj.addClass("absent").attr("title","Requires API Level \""
Scott Main641c2c22013-10-31 14:48:45 -07002783 + apiLevel + "\" or higher. To reveal, change the target API level "
2784 + "above the left navigation.");
Scott Main3b90aff2013-08-01 18:09:35 -07002785 }
Scott Mainf5089842012-08-14 16:31:07 -07002786 else obj.removeClass("absent").removeAttr("title");
2787 });
2788}
2789
2790
2791
2792
2793/* ################# SIDENAV TREE VIEW ################### */
2794
2795function new_node(me, mom, text, link, children_data, api_level)
2796{
2797 var node = new Object();
2798 node.children = Array();
2799 node.children_data = children_data;
2800 node.depth = mom.depth + 1;
2801
2802 node.li = document.createElement("li");
2803 mom.get_children_ul().appendChild(node.li);
2804
2805 node.label_div = document.createElement("div");
2806 node.label_div.className = "label";
2807 if (api_level != null) {
2808 $(node.label_div).addClass("api");
2809 $(node.label_div).addClass("api-level-"+api_level);
2810 }
2811 node.li.appendChild(node.label_div);
2812
2813 if (children_data != null) {
2814 node.expand_toggle = document.createElement("a");
2815 node.expand_toggle.href = "javascript:void(0)";
2816 node.expand_toggle.onclick = function() {
2817 if (node.expanded) {
2818 $(node.get_children_ul()).slideUp("fast");
2819 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2820 node.expanded = false;
2821 } else {
2822 expand_node(me, node);
2823 }
2824 };
2825 node.label_div.appendChild(node.expand_toggle);
2826
2827 node.plus_img = document.createElement("img");
2828 node.plus_img.src = me.toroot + "assets/images/triangle-closed-small.png";
2829 node.plus_img.className = "plus";
2830 node.plus_img.width = "8";
2831 node.plus_img.border = "0";
2832 node.expand_toggle.appendChild(node.plus_img);
2833
2834 node.expanded = false;
2835 }
2836
2837 var a = document.createElement("a");
2838 node.label_div.appendChild(a);
2839 node.label = document.createTextNode(text);
2840 a.appendChild(node.label);
2841 if (link) {
2842 a.href = me.toroot + link;
2843 } else {
2844 if (children_data != null) {
2845 a.className = "nolink";
2846 a.href = "javascript:void(0)";
2847 a.onclick = node.expand_toggle.onclick;
2848 // This next line shouldn't be necessary. I'll buy a beer for the first
2849 // person who figures out how to remove this line and have the link
2850 // toggle shut on the first try. --joeo@android.com
2851 node.expanded = false;
2852 }
2853 }
Scott Main3b90aff2013-08-01 18:09:35 -07002854
Scott Mainf5089842012-08-14 16:31:07 -07002855
2856 node.children_ul = null;
2857 node.get_children_ul = function() {
2858 if (!node.children_ul) {
2859 node.children_ul = document.createElement("ul");
2860 node.children_ul.className = "children_ul";
2861 node.children_ul.style.display = "none";
2862 node.li.appendChild(node.children_ul);
2863 }
2864 return node.children_ul;
2865 };
2866
2867 return node;
2868}
2869
Robert Lyd2dd6e52012-11-29 21:28:48 -08002870
2871
2872
Scott Mainf5089842012-08-14 16:31:07 -07002873function expand_node(me, node)
2874{
2875 if (node.children_data && !node.expanded) {
2876 if (node.children_visited) {
2877 $(node.get_children_ul()).slideDown("fast");
2878 } else {
2879 get_node(me, node);
2880 if ($(node.label_div).hasClass("absent")) {
2881 $(node.get_children_ul()).addClass("absent");
Scott Main3b90aff2013-08-01 18:09:35 -07002882 }
Scott Mainf5089842012-08-14 16:31:07 -07002883 $(node.get_children_ul()).slideDown("fast");
2884 }
2885 node.plus_img.src = me.toroot + "assets/images/triangle-opened-small.png";
2886 node.expanded = true;
2887
2888 // perform api level toggling because new nodes are new to the DOM
2889 var selectedLevel = $("#apiLevelSelector option:selected").val();
2890 toggleVisisbleApis(selectedLevel, "#side-nav");
2891 }
2892}
2893
2894function get_node(me, mom)
2895{
2896 mom.children_visited = true;
2897 for (var i in mom.children_data) {
2898 var node_data = mom.children_data[i];
2899 mom.children[i] = new_node(me, mom, node_data[0], node_data[1],
2900 node_data[2], node_data[3]);
2901 }
2902}
2903
2904function this_page_relative(toroot)
2905{
2906 var full = document.location.pathname;
2907 var file = "";
2908 if (toroot.substr(0, 1) == "/") {
2909 if (full.substr(0, toroot.length) == toroot) {
2910 return full.substr(toroot.length);
2911 } else {
2912 // the file isn't under toroot. Fail.
2913 return null;
2914 }
2915 } else {
2916 if (toroot != "./") {
2917 toroot = "./" + toroot;
2918 }
2919 do {
2920 if (toroot.substr(toroot.length-3, 3) == "../" || toroot == "./") {
2921 var pos = full.lastIndexOf("/");
2922 file = full.substr(pos) + file;
2923 full = full.substr(0, pos);
2924 toroot = toroot.substr(0, toroot.length-3);
2925 }
2926 } while (toroot != "" && toroot != "/");
2927 return file.substr(1);
2928 }
2929}
2930
2931function find_page(url, data)
2932{
2933 var nodes = data;
2934 var result = null;
2935 for (var i in nodes) {
2936 var d = nodes[i];
2937 if (d[1] == url) {
2938 return new Array(i);
2939 }
2940 else if (d[2] != null) {
2941 result = find_page(url, d[2]);
2942 if (result != null) {
2943 return (new Array(i).concat(result));
2944 }
2945 }
2946 }
2947 return null;
2948}
2949
Scott Mainf5089842012-08-14 16:31:07 -07002950function init_default_navtree(toroot) {
Scott Main25e73002013-03-27 15:24:06 -07002951 // load json file for navtree data
2952 $.getScript(toRoot + 'navtree_data.js', function(data, textStatus, jqxhr) {
2953 // when the file is loaded, initialize the tree
2954 if(jqxhr.status === 200) {
2955 init_navtree("tree-list", toroot, NAVTREE_DATA);
2956 }
2957 });
Scott Main3b90aff2013-08-01 18:09:35 -07002958
Scott Mainf5089842012-08-14 16:31:07 -07002959 // perform api level toggling because because the whole tree is new to the DOM
2960 var selectedLevel = $("#apiLevelSelector option:selected").val();
2961 toggleVisisbleApis(selectedLevel, "#side-nav");
2962}
2963
2964function init_navtree(navtree_id, toroot, root_nodes)
2965{
2966 var me = new Object();
2967 me.toroot = toroot;
2968 me.node = new Object();
2969
2970 me.node.li = document.getElementById(navtree_id);
2971 me.node.children_data = root_nodes;
2972 me.node.children = new Array();
2973 me.node.children_ul = document.createElement("ul");
2974 me.node.get_children_ul = function() { return me.node.children_ul; };
2975 //me.node.children_ul.className = "children_ul";
2976 me.node.li.appendChild(me.node.children_ul);
2977 me.node.depth = 0;
2978
2979 get_node(me, me.node);
2980
2981 me.this_page = this_page_relative(toroot);
2982 me.breadcrumbs = find_page(me.this_page, root_nodes);
2983 if (me.breadcrumbs != null && me.breadcrumbs.length != 0) {
2984 var mom = me.node;
2985 for (var i in me.breadcrumbs) {
2986 var j = me.breadcrumbs[i];
2987 mom = mom.children[j];
2988 expand_node(me, mom);
2989 }
2990 mom.label_div.className = mom.label_div.className + " selected";
2991 addLoadEvent(function() {
2992 scrollIntoView("nav-tree");
2993 });
2994 }
2995}
2996
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07002997
2998
2999
3000
3001
3002
3003
Robert Lyd2dd6e52012-11-29 21:28:48 -08003004/* TODO: eliminate redundancy with non-google functions */
3005function init_google_navtree(navtree_id, toroot, root_nodes)
3006{
3007 var me = new Object();
3008 me.toroot = toroot;
3009 me.node = new Object();
3010
3011 me.node.li = document.getElementById(navtree_id);
3012 me.node.children_data = root_nodes;
3013 me.node.children = new Array();
3014 me.node.children_ul = document.createElement("ul");
3015 me.node.get_children_ul = function() { return me.node.children_ul; };
3016 //me.node.children_ul.className = "children_ul";
3017 me.node.li.appendChild(me.node.children_ul);
3018 me.node.depth = 0;
3019
3020 get_google_node(me, me.node);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003021}
3022
3023function new_google_node(me, mom, text, link, children_data, api_level)
3024{
3025 var node = new Object();
3026 var child;
3027 node.children = Array();
3028 node.children_data = children_data;
3029 node.depth = mom.depth + 1;
3030 node.get_children_ul = function() {
3031 if (!node.children_ul) {
Scott Main3b90aff2013-08-01 18:09:35 -07003032 node.children_ul = document.createElement("ul");
3033 node.children_ul.className = "tree-list-children";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003034 node.li.appendChild(node.children_ul);
3035 }
3036 return node.children_ul;
3037 };
3038 node.li = document.createElement("li");
3039
3040 mom.get_children_ul().appendChild(node.li);
Scott Main3b90aff2013-08-01 18:09:35 -07003041
3042
Robert Lyd2dd6e52012-11-29 21:28:48 -08003043 if(link) {
3044 child = document.createElement("a");
3045
3046 }
3047 else {
3048 child = document.createElement("span");
Scott Mainac71b2b2012-11-30 14:40:58 -08003049 child.className = "tree-list-subtitle";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003050
3051 }
3052 if (children_data != null) {
3053 node.li.className="nav-section";
3054 node.label_div = document.createElement("div");
Scott Main3b90aff2013-08-01 18:09:35 -07003055 node.label_div.className = "nav-section-header-ref";
Robert Lyd2dd6e52012-11-29 21:28:48 -08003056 node.li.appendChild(node.label_div);
3057 get_google_node(me, node);
3058 node.label_div.appendChild(child);
3059 }
3060 else {
3061 node.li.appendChild(child);
3062 }
3063 if(link) {
3064 child.href = me.toroot + link;
3065 }
3066 node.label = document.createTextNode(text);
3067 child.appendChild(node.label);
3068
3069 node.children_ul = null;
3070
3071 return node;
3072}
3073
3074function get_google_node(me, mom)
3075{
3076 mom.children_visited = true;
3077 var linkText;
3078 for (var i in mom.children_data) {
3079 var node_data = mom.children_data[i];
3080 linkText = node_data[0];
3081
3082 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3083 linkText = linkText.substr(19, linkText.length);
3084 }
3085 mom.children[i] = new_google_node(me, mom, linkText, node_data[1],
3086 node_data[2], node_data[3]);
3087 }
3088}
Scott Mainad08f072013-08-20 16:49:57 -07003089
3090
3091
3092
3093
3094
3095/****** NEW version of script to build google and sample navs dynamically ******/
3096// TODO: update Google reference docs to tolerate this new implementation
3097
Scott Maine624b3f2013-09-12 12:56:41 -07003098var NODE_NAME = 0;
3099var NODE_HREF = 1;
3100var NODE_GROUP = 2;
3101var NODE_TAGS = 3;
3102var NODE_CHILDREN = 4;
3103
Scott Mainad08f072013-08-20 16:49:57 -07003104function init_google_navtree2(navtree_id, data)
3105{
3106 var $containerUl = $("#"+navtree_id);
Scott Mainad08f072013-08-20 16:49:57 -07003107 for (var i in data) {
3108 var node_data = data[i];
3109 $containerUl.append(new_google_node2(node_data));
3110 }
3111
Scott Main70557ee2013-10-30 14:47:40 -07003112 // Make all third-generation list items 'sticky' to prevent them from collapsing
3113 $containerUl.find('li li li.nav-section').addClass('sticky');
3114
Scott Mainad08f072013-08-20 16:49:57 -07003115 initExpandableNavItems("#"+navtree_id);
3116}
3117
3118function new_google_node2(node_data)
3119{
Scott Maine624b3f2013-09-12 12:56:41 -07003120 var linkText = node_data[NODE_NAME];
Scott Mainad08f072013-08-20 16:49:57 -07003121 if(linkText.match("^"+"com.google.android")=="com.google.android"){
3122 linkText = linkText.substr(19, linkText.length);
3123 }
3124 var $li = $('<li>');
3125 var $a;
Scott Maine624b3f2013-09-12 12:56:41 -07003126 if (node_data[NODE_HREF] != null) {
Scott Main70557ee2013-10-30 14:47:40 -07003127 $a = $('<a href="' + toRoot + node_data[NODE_HREF] + '" title="' + linkText + '" >'
3128 + linkText + '</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003129 } else {
Scott Main70557ee2013-10-30 14:47:40 -07003130 $a = $('<a href="#" onclick="return false;" title="' + linkText + '" >'
3131 + linkText + '/</a>');
Scott Mainad08f072013-08-20 16:49:57 -07003132 }
3133 var $childUl = $('<ul>');
Scott Maine624b3f2013-09-12 12:56:41 -07003134 if (node_data[NODE_CHILDREN] != null) {
Scott Mainad08f072013-08-20 16:49:57 -07003135 $li.addClass("nav-section");
3136 $a = $('<div class="nav-section-header">').append($a);
Scott Maine624b3f2013-09-12 12:56:41 -07003137 if (node_data[NODE_HREF] == null) $a.addClass('empty');
Scott Mainad08f072013-08-20 16:49:57 -07003138
Scott Maine624b3f2013-09-12 12:56:41 -07003139 for (var i in node_data[NODE_CHILDREN]) {
3140 var child_node_data = node_data[NODE_CHILDREN][i];
Scott Mainad08f072013-08-20 16:49:57 -07003141 $childUl.append(new_google_node2(child_node_data));
3142 }
3143 $li.append($childUl);
3144 }
3145 $li.prepend($a);
3146
3147 return $li;
3148}
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
Robert Lyd2dd6e52012-11-29 21:28:48 -08003160function showGoogleRefTree() {
3161 init_default_google_navtree(toRoot);
3162 init_default_gcm_navtree(toRoot);
Robert Lyd2dd6e52012-11-29 21:28:48 -08003163}
3164
3165function init_default_google_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003166 // load json file for navtree data
3167 $.getScript(toRoot + 'gms_navtree_data.js', function(data, textStatus, jqxhr) {
3168 // when the file is loaded, initialize the tree
3169 if(jqxhr.status === 200) {
3170 init_google_navtree("gms-tree-list", toroot, GMS_NAVTREE_DATA);
3171 highlightSidenav();
3172 resizeNav();
3173 }
3174 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003175}
3176
3177function init_default_gcm_navtree(toroot) {
Scott Mainf6145542013-04-01 16:38:11 -07003178 // load json file for navtree data
3179 $.getScript(toRoot + 'gcm_navtree_data.js', function(data, textStatus, jqxhr) {
3180 // when the file is loaded, initialize the tree
3181 if(jqxhr.status === 200) {
3182 init_google_navtree("gcm-tree-list", toroot, GCM_NAVTREE_DATA);
3183 highlightSidenav();
3184 resizeNav();
3185 }
3186 });
Robert Lyd2dd6e52012-11-29 21:28:48 -08003187}
3188
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003189function showSamplesRefTree() {
3190 init_default_samples_navtree(toRoot);
3191}
3192
3193function init_default_samples_navtree(toroot) {
3194 // load json file for navtree data
3195 $.getScript(toRoot + 'samples_navtree_data.js', function(data, textStatus, jqxhr) {
3196 // when the file is loaded, initialize the tree
3197 if(jqxhr.status === 200) {
Scott Mainf1435b72013-10-30 16:27:38 -07003198 // hack to remove the "about the samples" link then put it back in
3199 // after we nuke the list to remove the dummy static list of samples
3200 var $firstLi = $("#nav.samples-nav > li:first-child").clone();
3201 $("#nav.samples-nav").empty();
3202 $("#nav.samples-nav").append($firstLi);
3203
Scott Mainad08f072013-08-20 16:49:57 -07003204 init_google_navtree2("nav.samples-nav", SAMPLES_NAVTREE_DATA);
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003205 highlightSidenav();
3206 resizeNav();
Scott Main03aca9a2013-10-31 07:20:55 -07003207 if ($("#jd-content #samples").length) {
3208 showSamples();
3209 }
Dirk Dougherty4f7e5152010-09-16 10:43:40 -07003210 }
3211 });
3212}
3213
Scott Mainf5089842012-08-14 16:31:07 -07003214/* TOGGLE INHERITED MEMBERS */
3215
3216/* Toggle an inherited class (arrow toggle)
3217 * @param linkObj The link that was clicked.
3218 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3219 * 'null' to simply toggle.
3220 */
3221function toggleInherited(linkObj, expand) {
3222 var base = linkObj.getAttribute("id");
3223 var list = document.getElementById(base + "-list");
3224 var summary = document.getElementById(base + "-summary");
3225 var trigger = document.getElementById(base + "-trigger");
3226 var a = $(linkObj);
3227 if ( (expand == null && a.hasClass("closed")) || expand ) {
3228 list.style.display = "none";
3229 summary.style.display = "block";
3230 trigger.src = toRoot + "assets/images/triangle-opened.png";
3231 a.removeClass("closed");
3232 a.addClass("opened");
3233 } else if ( (expand == null && a.hasClass("opened")) || (expand == false) ) {
3234 list.style.display = "block";
3235 summary.style.display = "none";
3236 trigger.src = toRoot + "assets/images/triangle-closed.png";
3237 a.removeClass("opened");
3238 a.addClass("closed");
3239 }
3240 return false;
3241}
3242
3243/* Toggle all inherited classes in a single table (e.g. all inherited methods)
3244 * @param linkObj The link that was clicked.
3245 * @param expand 'true' to ensure it's expanded. 'false' to ensure it's closed.
3246 * 'null' to simply toggle.
3247 */
3248function toggleAllInherited(linkObj, expand) {
3249 var a = $(linkObj);
3250 var table = $(a.parent().parent().parent()); // ugly way to get table/tbody
3251 var expandos = $(".jd-expando-trigger", table);
3252 if ( (expand == null && a.text() == "[Expand]") || expand ) {
3253 expandos.each(function(i) {
3254 toggleInherited(this, true);
3255 });
3256 a.text("[Collapse]");
3257 } else if ( (expand == null && a.text() == "[Collapse]") || (expand == false) ) {
3258 expandos.each(function(i) {
3259 toggleInherited(this, false);
3260 });
3261 a.text("[Expand]");
3262 }
3263 return false;
3264}
3265
3266/* Toggle all inherited members in the class (link in the class title)
3267 */
3268function toggleAllClassInherited() {
3269 var a = $("#toggleAllClassInherited"); // get toggle link from class title
3270 var toggles = $(".toggle-all", $("#body-content"));
3271 if (a.text() == "[Expand All]") {
3272 toggles.each(function(i) {
3273 toggleAllInherited(this, true);
3274 });
3275 a.text("[Collapse All]");
3276 } else {
3277 toggles.each(function(i) {
3278 toggleAllInherited(this, false);
3279 });
3280 a.text("[Expand All]");
3281 }
3282 return false;
3283}
3284
3285/* Expand all inherited members in the class. Used when initiating page search */
3286function ensureAllInheritedExpanded() {
3287 var toggles = $(".toggle-all", $("#body-content"));
3288 toggles.each(function(i) {
3289 toggleAllInherited(this, true);
3290 });
3291 $("#toggleAllClassInherited").text("[Collapse All]");
3292}
3293
3294
3295/* HANDLE KEY EVENTS
3296 * - Listen for Ctrl+F (Cmd on Mac) and expand all inherited members (to aid page search)
3297 */
3298var agent = navigator['userAgent'].toLowerCase();
3299var mac = agent.indexOf("macintosh") != -1;
3300
3301$(document).keydown( function(e) {
3302var control = mac ? e.metaKey && !e.ctrlKey : e.ctrlKey; // get ctrl key
3303 if (control && e.which == 70) { // 70 is "F"
3304 ensureAllInheritedExpanded();
3305 }
3306});
Scott Main498d7102013-08-21 15:47:38 -07003307
3308
3309
3310
3311
3312
3313/* On-demand functions */
3314
3315/** Move sample code line numbers out of PRE block and into non-copyable column */
3316function initCodeLineNumbers() {
3317 var numbers = $("#codesample-block a.number");
3318 if (numbers.length) {
3319 $("#codesample-line-numbers").removeClass("hidden").append(numbers);
3320 }
3321
3322 $(document).ready(function() {
3323 // select entire line when clicked
3324 $("span.code-line").click(function() {
3325 if (!shifted) {
3326 selectText(this);
3327 }
3328 });
3329 // invoke line link on double click
3330 $(".code-line").dblclick(function() {
3331 document.location.hash = $(this).attr('id');
3332 });
3333 // highlight the line when hovering on the number
3334 $("#codesample-line-numbers a.number").mouseover(function() {
3335 var id = $(this).attr('href');
3336 $(id).css('background','#e7e7e7');
3337 });
3338 $("#codesample-line-numbers a.number").mouseout(function() {
3339 var id = $(this).attr('href');
3340 $(id).css('background','none');
3341 });
3342 });
3343}
3344
3345// create SHIFT key binder to avoid the selectText method when selecting multiple lines
3346var shifted = false;
3347$(document).bind('keyup keydown', function(e){shifted = e.shiftKey; return true;} );
3348
3349// courtesy of jasonedelman.com
3350function selectText(element) {
3351 var doc = document
3352 , range, selection
3353 ;
3354 if (doc.body.createTextRange) { //ms
3355 range = doc.body.createTextRange();
3356 range.moveToElementText(element);
3357 range.select();
3358 } else if (window.getSelection) { //all others
Scott Main70557ee2013-10-30 14:47:40 -07003359 selection = window.getSelection();
Scott Main498d7102013-08-21 15:47:38 -07003360 range = doc.createRange();
3361 range.selectNodeContents(element);
3362 selection.removeAllRanges();
3363 selection.addRange(range);
3364 }
Scott Main285f0772013-08-22 23:22:09 +00003365}
Scott Main03aca9a2013-10-31 07:20:55 -07003366
3367
3368
3369
3370/** Display links and other information about samples that match the
3371 group specified by the URL */
3372function showSamples() {
3373 var group = $("#samples").attr('class');
3374 $("#samples").html("<p>Here are some samples for <b>" + group + "</b> apps:</p>");
3375
3376 var $ul = $("<ul>");
3377 $selectedLi = $("#nav li.selected");
3378
3379 $selectedLi.children("ul").children("li").each(function() {
3380 var $li = $("<li>").append($(this).find("a").first().clone());
3381 $ul.append($li);
3382 });
3383
3384 $("#samples").append($ul);
3385
3386}
Dirk Doughertyc3921652014-05-13 16:55:26 -07003387
3388
3389
3390/* ########################################################## */
3391/* ################### RESOURCE CARDS ##################### */
3392/* ########################################################## */
3393
3394/** Handle resource queries, collections, and grids (sections). Requires
3395 jd_tag_helpers.js and the *_unified_data.js to be loaded. */
3396
3397(function() {
3398 // Prevent the same resource from being loaded more than once per page.
3399 var addedPageResources = {};
3400
3401 $(document).ready(function() {
3402 $('.resource-widget').each(function() {
3403 initResourceWidget(this);
3404 });
3405
3406 /* Pass the line height to ellipsisfade() to adjust the height of the
3407 text container to show the max number of lines possible, without
3408 showing lines that are cut off. This works with the css ellipsis
3409 classes to fade last text line and apply an ellipsis char. */
3410
Scott Mainb16376f2014-05-21 20:35:47 -07003411 //card text currently uses 15px line height.
Dirk Doughertyc3921652014-05-13 16:55:26 -07003412 var lineHeight = 15;
3413 $('.card-info .text').ellipsisfade(lineHeight);
3414 });
3415
3416 /*
3417 Three types of resource layouts:
3418 Flow - Uses a fixed row-height flow using float left style.
3419 Carousel - Single card slideshow all same dimension absolute.
3420 Stack - Uses fixed columns and flexible element height.
3421 */
3422 function initResourceWidget(widget) {
3423 var $widget = $(widget);
3424 var isFlow = $widget.hasClass('resource-flow-layout'),
3425 isCarousel = $widget.hasClass('resource-carousel-layout'),
3426 isStack = $widget.hasClass('resource-stack-layout');
3427
3428 // find size of widget by pulling out its class name
3429 var sizeCols = 1;
3430 var m = $widget.get(0).className.match(/\bcol-(\d+)\b/);
3431 if (m) {
3432 sizeCols = parseInt(m[1], 10);
3433 }
3434
3435 var opts = {
3436 cardSizes: ($widget.data('cardsizes') || '').split(','),
3437 maxResults: parseInt($widget.data('maxresults') || '100', 10),
3438 itemsPerPage: $widget.data('itemsperpage'),
3439 sortOrder: $widget.data('sortorder'),
3440 query: $widget.data('query'),
3441 section: $widget.data('section'),
Robert Lye7eeb402014-06-03 19:35:24 -07003442 sizeCols: sizeCols,
3443 /* Added by LFL 6/6/14 */
3444 resourceStyle: $widget.data('resourcestyle') || 'card',
3445 stackSort: $widget.data('stacksort') || 'true'
Dirk Doughertyc3921652014-05-13 16:55:26 -07003446 };
3447
3448 // run the search for the set of resources to show
3449
3450 var resources = buildResourceList(opts);
3451
3452 if (isFlow) {
3453 drawResourcesFlowWidget($widget, opts, resources);
3454 } else if (isCarousel) {
3455 drawResourcesCarouselWidget($widget, opts, resources);
3456 } else if (isStack) {
smain@google.com95948b82014-06-16 19:24:25 -07003457 /* Looks like this got removed and is not used, so repurposing for the
3458 homepage style layout.
Robert Lye7eeb402014-06-03 19:35:24 -07003459 Modified by LFL 6/6/14
3460 */
3461 //var sections = buildSectionList(opts);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003462 opts['numStacks'] = $widget.data('numstacks');
Robert Lye7eeb402014-06-03 19:35:24 -07003463 drawResourcesStackWidget($widget, opts, resources/*, sections*/);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003464 }
3465 }
3466
3467 /* Initializes a Resource Carousel Widget */
3468 function drawResourcesCarouselWidget($widget, opts, resources) {
3469 $widget.empty();
3470 var plusone = true; //always show plusone on carousel
3471
3472 $widget.addClass('resource-card slideshow-container')
3473 .append($('<a>').addClass('slideshow-prev').text('Prev'))
3474 .append($('<a>').addClass('slideshow-next').text('Next'));
3475
3476 var css = { 'width': $widget.width() + 'px',
3477 'height': $widget.height() + 'px' };
3478
3479 var $ul = $('<ul>');
3480
3481 for (var i = 0; i < resources.length; ++i) {
Dirk Doughertyc3921652014-05-13 16:55:26 -07003482 var $card = $('<a>')
Robert Lye7eeb402014-06-03 19:35:24 -07003483 .attr('href', cleanUrl(resources[i].url))
Dirk Doughertyc3921652014-05-13 16:55:26 -07003484 .decorateResourceCard(resources[i],plusone);
3485
3486 $('<li>').css(css)
3487 .append($card)
3488 .appendTo($ul);
3489 }
3490
3491 $('<div>').addClass('frame')
3492 .append($ul)
3493 .appendTo($widget);
3494
3495 $widget.dacSlideshow({
3496 auto: true,
3497 btnPrev: '.slideshow-prev',
3498 btnNext: '.slideshow-next'
3499 });
3500 };
3501
Robert Lye7eeb402014-06-03 19:35:24 -07003502 /* Initializes a Resource Card Stack Widget (column-based layout)
3503 Modified by LFL 6/6/14
3504 */
Dirk Doughertyc3921652014-05-13 16:55:26 -07003505 function drawResourcesStackWidget($widget, opts, resources, sections) {
3506 // Don't empty widget, grab all items inside since they will be the first
3507 // items stacked, followed by the resource query
3508 var plusone = true; //by default show plusone on section cards
3509 var cards = $widget.find('.resource-card').detach().toArray();
3510 var numStacks = opts.numStacks || 1;
3511 var $stacks = [];
3512 var urlString;
3513
3514 for (var i = 0; i < numStacks; ++i) {
3515 $stacks[i] = $('<div>').addClass('resource-card-stack')
3516 .appendTo($widget);
3517 }
3518
3519 var sectionResources = [];
3520
3521 // Extract any subsections that are actually resource cards
Robert Lye7eeb402014-06-03 19:35:24 -07003522 if (sections) {
3523 for (var i = 0; i < sections.length; ++i) {
3524 if (!sections[i].sections || !sections[i].sections.length) {
3525 // Render it as a resource card
3526 sectionResources.push(
3527 $('<a>')
3528 .addClass('resource-card section-card')
3529 .attr('href', cleanUrl(sections[i].resource.url))
3530 .decorateResourceCard(sections[i].resource,plusone)[0]
3531 );
Dirk Doughertyc3921652014-05-13 16:55:26 -07003532
Robert Lye7eeb402014-06-03 19:35:24 -07003533 } else {
3534 cards.push(
3535 $('<div>')
3536 .addClass('resource-card section-card-menu')
3537 .decorateResourceSection(sections[i],plusone)[0]
3538 );
3539 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003540 }
3541 }
3542
3543 cards = cards.concat(sectionResources);
3544
3545 for (var i = 0; i < resources.length; ++i) {
Robert Lye7eeb402014-06-03 19:35:24 -07003546 var $card = createResourceElement(resources[i], opts);
smain@google.com95948b82014-06-16 19:24:25 -07003547
Robert Lye7eeb402014-06-03 19:35:24 -07003548 if (opts.resourceStyle.indexOf('related') > -1) {
3549 $card.addClass('related-card');
3550 }
smain@google.com95948b82014-06-16 19:24:25 -07003551
Dirk Doughertyc3921652014-05-13 16:55:26 -07003552 cards.push($card[0]);
3553 }
3554
Robert Lye7eeb402014-06-03 19:35:24 -07003555 if (opts.stackSort != 'false') {
3556 for (var i = 0; i < cards.length; ++i) {
3557 // Find the stack with the shortest height, but give preference to
3558 // left to right order.
3559 var minHeight = $stacks[0].height();
3560 var minIndex = 0;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003561
Robert Lye7eeb402014-06-03 19:35:24 -07003562 for (var j = 1; j < numStacks; ++j) {
3563 var height = $stacks[j].height();
3564 if (height < minHeight - 45) {
3565 minHeight = height;
3566 minIndex = j;
3567 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003568 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003569
Robert Lye7eeb402014-06-03 19:35:24 -07003570 $stacks[minIndex].append($(cards[i]));
3571 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003572 }
3573
3574 };
smain@google.com95948b82014-06-16 19:24:25 -07003575
3576 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003577 Create a resource card using the given resource object and a list of html
3578 configured options. Returns a jquery object containing the element.
3579 */
smain@google.com95948b82014-06-16 19:24:25 -07003580 function createResourceElement(resource, opts, plusone) {
Robert Lye7eeb402014-06-03 19:35:24 -07003581 var $el;
smain@google.com95948b82014-06-16 19:24:25 -07003582
Robert Lye7eeb402014-06-03 19:35:24 -07003583 // The difference here is that generic cards are not entirely clickable
3584 // so its a div instead of an a tag, also the generic one is not given
3585 // the resource-card class so it appears with a transparent background
3586 // and can be styled in whatever way the css setup.
3587 if (opts.resourceStyle == 'generic') {
3588 $el = $('<div>')
3589 .addClass('resource')
3590 .attr('href', cleanUrl(resource.url))
3591 .decorateResource(resource, opts);
3592 } else {
3593 var cls = 'resource resource-card';
smain@google.com95948b82014-06-16 19:24:25 -07003594
Robert Lye7eeb402014-06-03 19:35:24 -07003595 $el = $('<a>')
3596 .addClass(cls)
3597 .attr('href', cleanUrl(resource.url))
3598 .decorateResourceCard(resource, plusone);
3599 }
smain@google.com95948b82014-06-16 19:24:25 -07003600
Robert Lye7eeb402014-06-03 19:35:24 -07003601 return $el;
3602 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003603
3604 /* Initializes a flow widget, see distribute.scss for generating accompanying css */
3605 function drawResourcesFlowWidget($widget, opts, resources) {
3606 $widget.empty();
3607 var cardSizes = opts.cardSizes || ['6x6'];
3608 var i = 0, j = 0;
3609 var plusone = true; // by default show plusone on resource cards
3610
3611 while (i < resources.length) {
3612 var cardSize = cardSizes[j++ % cardSizes.length];
3613 cardSize = cardSize.replace(/^\s+|\s+$/,'');
Dirk Doughertyc3921652014-05-13 16:55:26 -07003614 // Some card sizes do not get a plusone button, such as where space is constrained
3615 // or for cards commonly embedded in docs (to improve overall page speed).
3616 plusone = !((cardSize == "6x2") || (cardSize == "6x3") ||
3617 (cardSize == "9x2") || (cardSize == "9x3") ||
3618 (cardSize == "12x2") || (cardSize == "12x3"));
3619
3620 // A stack has a third dimension which is the number of stacked items
3621 var isStack = cardSize.match(/(\d+)x(\d+)x(\d+)/);
3622 var stackCount = 0;
3623 var $stackDiv = null;
3624
3625 if (isStack) {
3626 // Create a stack container which should have the dimensions defined
3627 // by the product of the items inside.
3628 $stackDiv = $('<div>').addClass('resource-card-stack resource-card-' + isStack[1]
3629 + 'x' + isStack[2] * isStack[3]) .appendTo($widget);
3630 }
3631
3632 // Build each stack item or just a single item
3633 do {
3634 var resource = resources[i];
Dirk Doughertyc3921652014-05-13 16:55:26 -07003635
Robert Lye7eeb402014-06-03 19:35:24 -07003636 var $card = createResourceElement(resources[i], opts, plusone);
smain@google.com95948b82014-06-16 19:24:25 -07003637
3638 $card.addClass('resource-card-' + cardSize +
Robert Lye7eeb402014-06-03 19:35:24 -07003639 ' resource-card-' + resource.type);
smain@google.com95948b82014-06-16 19:24:25 -07003640
Dirk Doughertyc3921652014-05-13 16:55:26 -07003641 if (isStack) {
3642 $card.addClass('resource-card-' + isStack[1] + 'x' + isStack[2]);
3643 if (++stackCount == parseInt(isStack[3])) {
3644 $card.addClass('resource-card-row-stack-last');
3645 stackCount = 0;
3646 }
3647 } else {
3648 stackCount = 0;
3649 }
3650
Robert Lye7eeb402014-06-03 19:35:24 -07003651 $card.appendTo($stackDiv || $widget);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003652
3653 } while (++i < resources.length && stackCount > 0);
3654 }
3655 }
3656
3657 /* Build a site map of resources using a section as a root. */
3658 function buildSectionList(opts) {
3659 if (opts.section && SECTION_BY_ID[opts.section]) {
3660 return SECTION_BY_ID[opts.section].sections || [];
3661 }
3662 return [];
3663 }
3664
3665 function buildResourceList(opts) {
3666 var maxResults = opts.maxResults || 100;
3667
3668 var query = opts.query || '';
3669 var expressions = parseResourceQuery(query);
3670 var addedResourceIndices = {};
3671 var results = [];
3672
3673 for (var i = 0; i < expressions.length; i++) {
3674 var clauses = expressions[i];
3675
3676 // build initial set of resources from first clause
3677 var firstClause = clauses[0];
3678 var resources = [];
3679 switch (firstClause.attr) {
3680 case 'type':
3681 resources = ALL_RESOURCES_BY_TYPE[firstClause.value];
3682 break;
3683 case 'lang':
3684 resources = ALL_RESOURCES_BY_LANG[firstClause.value];
3685 break;
3686 case 'tag':
3687 resources = ALL_RESOURCES_BY_TAG[firstClause.value];
3688 break;
3689 case 'collection':
3690 var urls = RESOURCE_COLLECTIONS[firstClause.value].resources || [];
3691 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3692 break;
3693 case 'section':
3694 var urls = SITE_MAP[firstClause.value].sections || [];
3695 resources = urls.map(function(url){ return ALL_RESOURCES_BY_URL[url]; });
3696 break;
3697 }
3698 // console.log(firstClause.attr + ':' + firstClause.value);
3699 resources = resources || [];
3700
3701 // use additional clauses to filter corpus
3702 if (clauses.length > 1) {
3703 var otherClauses = clauses.slice(1);
3704 resources = resources.filter(getResourceMatchesClausesFilter(otherClauses));
3705 }
3706
3707 // filter out resources already added
3708 if (i > 1) {
3709 resources = resources.filter(getResourceNotAlreadyAddedFilter(addedResourceIndices));
3710 }
3711
3712 // add to list of already added indices
3713 for (var j = 0; j < resources.length; j++) {
3714 // console.log(resources[j].title);
3715 addedResourceIndices[resources[j].index] = 1;
3716 }
3717
3718 // concat to final results list
3719 results = results.concat(resources);
3720 }
3721
3722 if (opts.sortOrder && results.length) {
3723 var attr = opts.sortOrder;
3724
3725 if (opts.sortOrder == 'random') {
3726 var i = results.length, j, temp;
3727 while (--i) {
3728 j = Math.floor(Math.random() * (i + 1));
3729 temp = results[i];
3730 results[i] = results[j];
3731 results[j] = temp;
3732 }
3733 } else {
3734 var desc = attr.charAt(0) == '-';
3735 if (desc) {
3736 attr = attr.substring(1);
3737 }
3738 results = results.sort(function(x,y) {
3739 return (desc ? -1 : 1) * (parseInt(x[attr], 10) - parseInt(y[attr], 10));
3740 });
3741 }
3742 }
3743
3744 results = results.filter(getResourceNotAlreadyAddedFilter(addedPageResources));
3745 results = results.slice(0, maxResults);
3746
3747 for (var j = 0; j < results.length; ++j) {
3748 addedPageResources[results[j].index] = 1;
3749 }
3750
3751 return results;
3752 }
3753
3754
3755 function getResourceNotAlreadyAddedFilter(addedResourceIndices) {
3756 return function(resource) {
3757 return !addedResourceIndices[resource.index];
3758 };
3759 }
3760
3761
3762 function getResourceMatchesClausesFilter(clauses) {
3763 return function(resource) {
3764 return doesResourceMatchClauses(resource, clauses);
3765 };
3766 }
3767
3768
3769 function doesResourceMatchClauses(resource, clauses) {
3770 for (var i = 0; i < clauses.length; i++) {
3771 var map;
3772 switch (clauses[i].attr) {
3773 case 'type':
3774 map = IS_RESOURCE_OF_TYPE[clauses[i].value];
3775 break;
3776 case 'lang':
3777 map = IS_RESOURCE_IN_LANG[clauses[i].value];
3778 break;
3779 case 'tag':
3780 map = IS_RESOURCE_TAGGED[clauses[i].value];
3781 break;
3782 }
3783
3784 if (!map || (!!clauses[i].negative ? map[resource.index] : !map[resource.index])) {
3785 return clauses[i].negative;
3786 }
3787 }
3788 return true;
3789 }
smain@google.com95948b82014-06-16 19:24:25 -07003790
Robert Lye7eeb402014-06-03 19:35:24 -07003791 function cleanUrl(url)
3792 {
3793 if (url && url.indexOf('//') === -1) {
3794 url = toRoot + url;
3795 }
smain@google.com95948b82014-06-16 19:24:25 -07003796
Robert Lye7eeb402014-06-03 19:35:24 -07003797 return url;
3798 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07003799
3800
3801 function parseResourceQuery(query) {
3802 // Parse query into array of expressions (expression e.g. 'tag:foo + type:video')
3803 var expressions = [];
3804 var expressionStrs = query.split(',') || [];
3805 for (var i = 0; i < expressionStrs.length; i++) {
3806 var expr = expressionStrs[i] || '';
3807
3808 // Break expression into clauses (clause e.g. 'tag:foo')
3809 var clauses = [];
3810 var clauseStrs = expr.split(/(?=[\+\-])/);
3811 for (var j = 0; j < clauseStrs.length; j++) {
3812 var clauseStr = clauseStrs[j] || '';
3813
3814 // Get attribute and value from clause (e.g. attribute='tag', value='foo')
3815 var parts = clauseStr.split(':');
3816 var clause = {};
3817
3818 clause.attr = parts[0].replace(/^\s+|\s+$/g,'');
3819 if (clause.attr) {
3820 if (clause.attr.charAt(0) == '+') {
3821 clause.attr = clause.attr.substring(1);
3822 } else if (clause.attr.charAt(0) == '-') {
3823 clause.negative = true;
3824 clause.attr = clause.attr.substring(1);
3825 }
3826 }
3827
3828 if (parts.length > 1) {
3829 clause.value = parts[1].replace(/^\s+|\s+$/g,'');
3830 }
3831
3832 clauses.push(clause);
3833 }
3834
3835 if (!clauses.length) {
3836 continue;
3837 }
3838
3839 expressions.push(clauses);
3840 }
3841
3842 return expressions;
3843 }
3844})();
3845
3846(function($) {
Robert Lye7eeb402014-06-03 19:35:24 -07003847
smain@google.com95948b82014-06-16 19:24:25 -07003848 /*
Robert Lye7eeb402014-06-03 19:35:24 -07003849 Utility method for creating dom for the description area of a card.
3850 Used in decorateResourceCard and decorateResource.
3851 */
3852 function buildResourceCardDescription(resource, plusone) {
3853 var $description = $('<div>').addClass('description ellipsis');
smain@google.com95948b82014-06-16 19:24:25 -07003854
Robert Lye7eeb402014-06-03 19:35:24 -07003855 $description.append($('<div>').addClass('text').html(resource.summary));
smain@google.com95948b82014-06-16 19:24:25 -07003856
Robert Lye7eeb402014-06-03 19:35:24 -07003857 if (resource.cta) {
3858 $description.append($('<a>').addClass('cta').html(resource.cta));
3859 }
smain@google.com95948b82014-06-16 19:24:25 -07003860
Robert Lye7eeb402014-06-03 19:35:24 -07003861 if (plusone) {
smain@google.com95948b82014-06-16 19:24:25 -07003862 var plusurl = resource.url.indexOf("//") > -1 ? resource.url :
Robert Lye7eeb402014-06-03 19:35:24 -07003863 "//developer.android.com/" + resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07003864
Robert Lye7eeb402014-06-03 19:35:24 -07003865 $description.append($('<div>').addClass('util')
3866 .append($('<div>').addClass('g-plusone')
3867 .attr('data-size', 'small')
3868 .attr('data-align', 'right')
3869 .attr('data-href', plusurl)));
3870 }
smain@google.com95948b82014-06-16 19:24:25 -07003871
Robert Lye7eeb402014-06-03 19:35:24 -07003872 return $description;
3873 }
smain@google.com95948b82014-06-16 19:24:25 -07003874
3875
Dirk Doughertyc3921652014-05-13 16:55:26 -07003876 /* Simple jquery function to create dom for a standard resource card */
3877 $.fn.decorateResourceCard = function(resource,plusone) {
3878 var section = resource.group || resource.type;
smain@google.com95948b82014-06-16 19:24:25 -07003879 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07003880 'assets/images/resource-card-default-android.jpg';
smain@google.com95948b82014-06-16 19:24:25 -07003881
Robert Lye7eeb402014-06-03 19:35:24 -07003882 if (imgUrl.indexOf('//') === -1) {
3883 imgUrl = toRoot + imgUrl;
Dirk Doughertyc3921652014-05-13 16:55:26 -07003884 }
Robert Lye7eeb402014-06-03 19:35:24 -07003885
3886 $('<div>').addClass('card-bg')
smain@google.com95948b82014-06-16 19:24:25 -07003887 .css('background-image', 'url(' + (imgUrl || toRoot +
Robert Lye7eeb402014-06-03 19:35:24 -07003888 'assets/images/resource-card-default-android.jpg') + ')')
Dirk Doughertyc3921652014-05-13 16:55:26 -07003889 .appendTo(this);
smain@google.com95948b82014-06-16 19:24:25 -07003890
Robert Lye7eeb402014-06-03 19:35:24 -07003891 $('<div>').addClass('card-info' + (!resource.summary ? ' empty-desc' : ''))
3892 .append($('<div>').addClass('section').text(section))
3893 .append($('<div>').addClass('title').html(resource.title))
3894 .append(buildResourceCardDescription(resource, plusone))
3895 .appendTo(this);
Dirk Doughertyc3921652014-05-13 16:55:26 -07003896
3897 return this;
3898 };
3899
3900 /* Simple jquery function to create dom for a resource section card (menu) */
3901 $.fn.decorateResourceSection = function(section,plusone) {
3902 var resource = section.resource;
3903 //keep url clean for matching and offline mode handling
3904 var urlPrefix = resource.image.indexOf("//") > -1 ? "" : toRoot;
3905 var $base = $('<a>')
3906 .addClass('card-bg')
3907 .attr('href', resource.url)
3908 .append($('<div>').addClass('card-section-icon')
3909 .append($('<div>').addClass('icon'))
3910 .append($('<div>').addClass('section').html(resource.title)))
3911 .appendTo(this);
3912
3913 var $cardInfo = $('<div>').addClass('card-info').appendTo(this);
3914
3915 if (section.sections && section.sections.length) {
3916 // Recurse the section sub-tree to find a resource image.
3917 var stack = [section];
3918
3919 while (stack.length) {
3920 if (stack[0].resource.image) {
3921 $base.css('background-image', 'url(' + urlPrefix + stack[0].resource.image + ')');
3922 break;
3923 }
3924
3925 if (stack[0].sections) {
3926 stack = stack.concat(stack[0].sections);
3927 }
3928
3929 stack.shift();
3930 }
3931
3932 var $ul = $('<ul>')
3933 .appendTo($cardInfo);
3934
3935 var max = section.sections.length > 3 ? 3 : section.sections.length;
3936
3937 for (var i = 0; i < max; ++i) {
3938
3939 var subResource = section.sections[i];
3940 if (!plusone) {
3941 $('<li>')
3942 .append($('<a>').attr('href', subResource.url)
3943 .append($('<div>').addClass('title').html(subResource.title))
3944 .append($('<div>').addClass('description ellipsis')
3945 .append($('<div>').addClass('text').html(subResource.summary))
3946 .append($('<div>').addClass('util'))))
3947 .appendTo($ul);
3948 } else {
3949 $('<li>')
3950 .append($('<a>').attr('href', subResource.url)
3951 .append($('<div>').addClass('title').html(subResource.title))
3952 .append($('<div>').addClass('description ellipsis')
3953 .append($('<div>').addClass('text').html(subResource.summary))
3954 .append($('<div>').addClass('util')
3955 .append($('<div>').addClass('g-plusone')
3956 .attr('data-size', 'small')
3957 .attr('data-align', 'right')
3958 .attr('data-href', resource.url)))))
3959 .appendTo($ul);
3960 }
3961 }
3962
3963 // Add a more row
3964 if (max < section.sections.length) {
3965 $('<li>')
3966 .append($('<a>').attr('href', resource.url)
3967 .append($('<div>')
3968 .addClass('title')
3969 .text('More')))
3970 .appendTo($ul);
3971 }
3972 } else {
3973 // No sub-resources, just render description?
3974 }
3975
3976 return this;
3977 };
smain@google.com95948b82014-06-16 19:24:25 -07003978
3979
3980
3981
Robert Lye7eeb402014-06-03 19:35:24 -07003982 /* Render other types of resource styles that are not cards. */
3983 $.fn.decorateResource = function(resource, opts) {
smain@google.com95948b82014-06-16 19:24:25 -07003984 var imgUrl = resource.image ||
Robert Lye7eeb402014-06-03 19:35:24 -07003985 'assets/images/resource-card-default-android.jpg';
3986 var linkUrl = resource.url;
smain@google.com95948b82014-06-16 19:24:25 -07003987
Robert Lye7eeb402014-06-03 19:35:24 -07003988 if (imgUrl.indexOf('//') === -1) {
3989 imgUrl = toRoot + imgUrl;
3990 }
smain@google.com95948b82014-06-16 19:24:25 -07003991
Robert Lye7eeb402014-06-03 19:35:24 -07003992 if (linkUrl && linkUrl.indexOf('//') === -1) {
3993 linkUrl = toRoot + linkUrl;
3994 }
3995
3996 $(this).append(
3997 $('<div>').addClass('image')
3998 .css('background-image', 'url(' + imgUrl + ')'),
3999 $('<div>').addClass('info').append(
4000 $('<h4>').addClass('title').html(resource.title),
4001 $('<p>').addClass('summary').html(resource.summary),
4002 $('<a>').attr('href', linkUrl).addClass('cta').html('Learn More')
4003 )
4004 );
4005
4006 return this;
4007 };
Dirk Doughertyc3921652014-05-13 16:55:26 -07004008})(jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004009
4010
Dirk Doughertyc3921652014-05-13 16:55:26 -07004011/* Calculate the vertical area remaining */
4012(function($) {
4013 $.fn.ellipsisfade= function(lineHeight) {
4014 this.each(function() {
4015 // get element text
4016 var $this = $(this);
4017 var remainingHeight = $this.parent().parent().height();
4018 $this.parent().siblings().each(function ()
smain@google.comc91ecb72014-06-23 10:22:23 -07004019 {
smain@google.comcda1a9a2014-06-19 17:07:46 -07004020 if ($(this).is(":visible")) {
4021 var h = $(this).height();
4022 remainingHeight = remainingHeight - h;
4023 }
Dirk Doughertyc3921652014-05-13 16:55:26 -07004024 });
4025
4026 adjustedRemainingHeight = ((remainingHeight)/lineHeight>>0)*lineHeight
4027 $this.parent().css({'height': adjustedRemainingHeight});
4028 $this.css({'height': "auto"});
4029 });
4030
4031 return this;
4032 };
4033}) (jQuery);
Robert Lye7eeb402014-06-03 19:35:24 -07004034
4035/*
4036 Fullscreen Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004037
Robert Lye7eeb402014-06-03 19:35:24 -07004038 The following allows for an area at the top of the page that takes over the
smain@google.com95948b82014-06-16 19:24:25 -07004039 entire browser height except for its top offset and an optional bottom
Robert Lye7eeb402014-06-03 19:35:24 -07004040 padding specified as a data attribute.
smain@google.com95948b82014-06-16 19:24:25 -07004041
Robert Lye7eeb402014-06-03 19:35:24 -07004042 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004043
Robert Lye7eeb402014-06-03 19:35:24 -07004044 <div class="fullscreen-carousel">
4045 <div class="fullscreen-carousel-content">
4046 <!-- content here -->
4047 </div>
4048 <div class="fullscreen-carousel-content">
4049 <!-- content here -->
4050 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004051
Robert Lye7eeb402014-06-03 19:35:24 -07004052 etc ...
smain@google.com95948b82014-06-16 19:24:25 -07004053
Robert Lye7eeb402014-06-03 19:35:24 -07004054 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004055
Robert Lye7eeb402014-06-03 19:35:24 -07004056 Control over how the carousel takes over the screen can mostly be defined in
4057 a css file. Setting min-height on the .fullscreen-carousel-content elements
smain@google.com95948b82014-06-16 19:24:25 -07004058 will prevent them from shrinking to far vertically when the browser is very
Robert Lye7eeb402014-06-03 19:35:24 -07004059 short, and setting max-height on the .fullscreen-carousel itself will prevent
smain@google.com95948b82014-06-16 19:24:25 -07004060 the area from becoming to long in the case that the browser is stretched very
Robert Lye7eeb402014-06-03 19:35:24 -07004061 tall.
smain@google.com95948b82014-06-16 19:24:25 -07004062
Robert Lye7eeb402014-06-03 19:35:24 -07004063 There is limited functionality for having multiple sections since that request
4064 was removed, but it is possible to add .next-arrow and .prev-arrow elements to
4065 scroll between multiple content areas.
4066*/
4067
4068(function() {
4069 $(document).ready(function() {
4070 $('.fullscreen-carousel').each(function() {
4071 initWidget(this);
4072 });
4073 });
4074
4075 function initWidget(widget) {
4076 var $widget = $(widget);
smain@google.com95948b82014-06-16 19:24:25 -07004077
Robert Lye7eeb402014-06-03 19:35:24 -07004078 var topOffset = $widget.offset().top;
4079 var padBottom = parseInt($widget.data('paddingbottom')) || 0;
4080 var maxHeight = 0;
4081 var minHeight = 0;
4082 var $content = $widget.find('.fullscreen-carousel-content');
4083 var $nextArrow = $widget.find('.next-arrow');
4084 var $prevArrow = $widget.find('.prev-arrow');
4085 var $curSection = $($content[0]);
smain@google.com95948b82014-06-16 19:24:25 -07004086
Robert Lye7eeb402014-06-03 19:35:24 -07004087 if ($content.length <= 1) {
4088 $nextArrow.hide();
4089 $prevArrow.hide();
4090 } else {
4091 $nextArrow.click(function() {
4092 var index = ($content.index($curSection) + 1);
4093 $curSection.hide();
4094 $curSection = $($content[index >= $content.length ? 0 : index]);
4095 $curSection.show();
4096 });
smain@google.com95948b82014-06-16 19:24:25 -07004097
Robert Lye7eeb402014-06-03 19:35:24 -07004098 $prevArrow.click(function() {
4099 var index = ($content.index($curSection) - 1);
4100 $curSection.hide();
4101 $curSection = $($content[index < 0 ? $content.length - 1 : 0]);
4102 $curSection.show();
4103 });
4104 }
4105
4106 // Just hide all content sections except first.
4107 $content.each(function(index) {
4108 if ($(this).height() > minHeight) minHeight = $(this).height();
4109 $(this).css({position: 'absolute', display: index > 0 ? 'none' : ''});
4110 });
4111
4112 // Register for changes to window size, and trigger.
4113 $(window).resize(resizeWidget);
4114 resizeWidget();
4115
4116 function resizeWidget() {
4117 var height = $(window).height() - topOffset - padBottom;
4118 $widget.width($(window).width());
smain@google.com95948b82014-06-16 19:24:25 -07004119 $widget.height(height < minHeight ? minHeight :
Robert Lye7eeb402014-06-03 19:35:24 -07004120 (maxHeight && height > maxHeight ? maxHeight : height));
4121 }
smain@google.com95948b82014-06-16 19:24:25 -07004122 }
Robert Lye7eeb402014-06-03 19:35:24 -07004123})();
4124
4125
4126
4127
4128
4129/*
4130 Tab Carousel
smain@google.com95948b82014-06-16 19:24:25 -07004131
Robert Lye7eeb402014-06-03 19:35:24 -07004132 The following allows tab widgets to be installed via the html below. Each
4133 tab content section should have a data-tab attribute matching one of the
4134 nav items'. Also each tab content section should have a width matching the
4135 tab carousel.
smain@google.com95948b82014-06-16 19:24:25 -07004136
Robert Lye7eeb402014-06-03 19:35:24 -07004137 HTML:
smain@google.com95948b82014-06-16 19:24:25 -07004138
Robert Lye7eeb402014-06-03 19:35:24 -07004139 <div class="tab-carousel">
4140 <ul class="tab-nav">
4141 <li><a href="#" data-tab="handsets">Handsets</a>
4142 <li><a href="#" data-tab="wearable">Wearable</a>
4143 <li><a href="#" data-tab="tv">TV</a>
4144 </ul>
smain@google.com95948b82014-06-16 19:24:25 -07004145
Robert Lye7eeb402014-06-03 19:35:24 -07004146 <div class="tab-carousel-content">
4147 <div data-tab="handsets">
4148 <!--Full width content here-->
4149 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004150
Robert Lye7eeb402014-06-03 19:35:24 -07004151 <div data-tab="wearable">
4152 <!--Full width content here-->
4153 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004154
Robert Lye7eeb402014-06-03 19:35:24 -07004155 <div data-tab="tv">
4156 <!--Full width content here-->
4157 </div>
4158 </div>
4159 </div>
smain@google.com95948b82014-06-16 19:24:25 -07004160
Robert Lye7eeb402014-06-03 19:35:24 -07004161*/
4162(function() {
4163 $(document).ready(function() {
4164 $('.tab-carousel').each(function() {
4165 initWidget(this);
4166 });
4167 });
4168
4169 function initWidget(widget) {
4170 var $widget = $(widget);
4171 var $nav = $widget.find('.tab-nav');
4172 var $anchors = $nav.find('[data-tab]');
4173 var $li = $nav.find('li');
4174 var $contentContainer = $widget.find('.tab-carousel-content');
4175 var $tabs = $contentContainer.find('[data-tab]');
4176 var $curTab = $($tabs[0]); // Current tab is first tab.
4177 var width = $widget.width();
4178
4179 // Setup nav interactivity.
4180 $anchors.click(function(evt) {
4181 evt.preventDefault();
4182 var query = '[data-tab=' + $(this).data('tab') + ']';
smain@google.com95948b82014-06-16 19:24:25 -07004183 transitionWidget($tabs.filter(query));
Robert Lye7eeb402014-06-03 19:35:24 -07004184 });
smain@google.com95948b82014-06-16 19:24:25 -07004185
Robert Lye7eeb402014-06-03 19:35:24 -07004186 // Add highlight for navigation on first item.
4187 var $highlight = $('<div>').addClass('highlight')
4188 .css({left:$li.position().left + 'px', width:$li.outerWidth() + 'px'})
4189 .appendTo($nav);
smain@google.com95948b82014-06-16 19:24:25 -07004190
Robert Lye7eeb402014-06-03 19:35:24 -07004191 // Store height since we will change contents to absolute.
4192 $contentContainer.height($contentContainer.height());
smain@google.com95948b82014-06-16 19:24:25 -07004193
Robert Lye7eeb402014-06-03 19:35:24 -07004194 // Absolutely position tabs so they're ready for transition.
4195 $tabs.each(function(index) {
4196 $(this).css({position: 'absolute', left: index > 0 ? width + 'px' : '0'});
4197 });
smain@google.com95948b82014-06-16 19:24:25 -07004198
Robert Lye7eeb402014-06-03 19:35:24 -07004199 function transitionWidget($toTab) {
4200 if (!$curTab.is($toTab)) {
4201 var curIndex = $tabs.index($curTab[0]);
4202 var toIndex = $tabs.index($toTab[0]);
4203 var dir = toIndex > curIndex ? 1 : -1;
smain@google.com95948b82014-06-16 19:24:25 -07004204
Robert Lye7eeb402014-06-03 19:35:24 -07004205 // Animate content sections.
4206 $toTab.css({left:(width * dir) + 'px'});
4207 $curTab.animate({left:(width * -dir) + 'px'});
4208 $toTab.animate({left:'0'});
smain@google.com95948b82014-06-16 19:24:25 -07004209
Robert Lye7eeb402014-06-03 19:35:24 -07004210 // Animate navigation highlight.
smain@google.com95948b82014-06-16 19:24:25 -07004211 $highlight.animate({left:$($li[toIndex]).position().left + 'px',
Robert Lye7eeb402014-06-03 19:35:24 -07004212 width:$($li[toIndex]).outerWidth() + 'px'})
smain@google.com95948b82014-06-16 19:24:25 -07004213
Robert Lye7eeb402014-06-03 19:35:24 -07004214 // Store new current section.
4215 $curTab = $toTab;
4216 }
4217 }
smain@google.com95948b82014-06-16 19:24:25 -07004218 }
Robert Lye7eeb402014-06-03 19:35:24 -07004219})();